A simple SPFx file upload by drag&drop including PDF conversion

A simple SPFx file upload by drag&drop including PDF conversion

Microsoft Graph offers the possibility to convert a bunch of supported file types (csv, doc, docx, odp, ods, odt, pot, potm, potx, pps, ppsx, ppsxm, ppt, pptm, pptx, rtf, xls, xlsx) to PDF. In fact this happens “on download” of an existing file from OneDrive or SharePoint.

This blogpost will show you how to create a simple SharePoint Framework (SPFx) webpart that achieves this conversion during the upload process.

The process in total will be

  • Upload the original file to a temporary folder in personal OneDrive
  • Download from ther again but converted to PDF
  • Upload the converted file to the (current site’s) target document library
  • Delete the temporary file

For the upload a simple div as “drag and drop zone” will be implemented:

The webpart with the (empty) drag and drop zone

To implement this we need a simple styled DIV with some event handlers:

const allowDrop = (event) => {
    event.dataTransfer.dropEffect = 'copy';
const enableHighlight = (event) => {
const disableHighlight = (event) => {
const dropFile = (event) => {
    let dt = event.dataTransfer;
    let files =  Array.prototype.slice.call(dt.files); 
    files.forEach(fileToUpload => {
      if (Utilities.validFileExtension(fileToUpload.name)) {

return (
    <div className={ styles.uploadFileAsPdf }>
      Drag your file here:
      <div className={`${styles.fileCanvas} ${highlight?styles.highlight:''}`} 

What is essential here in all cases is the “allowDrop” function which prevents default event handling. Followed by a small highlighter which occurs once the drag zone is entered. And finally of course the drop of a file needs to be handled.

Dropping a file into the webpart

Now the above mentioned process steps need to be implemented. This will be established with Microsoft Graph and a corresponding service. The calling function inside the webpart component first:

const uploadFile = async (file:File) => {
    const graphService: GraphService = new GraphService();
    const initialized = await graphService.initialize(props.serviceScope);
    if (initialized) {
      const tmpFileID = await graphService.uploadTmpFileToOneDrive(file);
      const pdfBlob = await graphService.downloadTmpFileAsPDF(tmpFileID);
      const newFilename = Utilities.getFileNameAsPDF(file.name);
      const fileUrl = await graphService.uploadFileToSiteAsPDF(props.siteID, pdfBlob, newFilename);
        .then(() => {

After the graphService is initialized via ServiceScope it first uploads the file in its original format to a ‘temp’ OneDrive folder:

public async uploadTmpFileToOneDrive (file: File): Promise<string> {
    const apiUrl = `me/drive/root:/TempUpload/${file.name}:/content`;
    const response = await this.uploadFile(apiUrl, file);  
    const fileID = response.id;
    return fileID;
private async uploadFile(apiUrl: string, file: Blob) {
    if (file.size <(4 * 1024 * 1024)) {
      const resp = await this.client
      return resp;
    else {
      // File.size>4MB, refer to https://mmsharepoint.wordpress.com/2020/01/12/an-outlook-add-in-with-sharepoint-framework-spfx-storing-mail-with-microsoftgraph/
      return null;

In the public method a Graph endpoint url is built followed by the upload call of this endpoint and finally the id of the created file is returned (needed for next retrieval as PDF and also for final deletion).

Inside the private upload method a detection takes place if the file is smaller than 4MB in size. For bigger files a specific upload process is needed (upload file chunks in a session and not the file in one “shot”). The difference is skipped her for simplicity but I described this in another blog post.

With the returned ID of the temporary uploaded file it now can be retrieved back again, but with a small change in the endpoint this time as format=pdf.

public async downloadTmpFileAsPDF (fileID: string): Promise<Blob> {
    const apiUrl = `me/drive/items/${fileID}/content?format=PDF`
    const res2 = await this.client
    return res2;

Two things to mention here. First the specific endpoint for the content retrieval with specific pdf format. Next is the responseType=”blob” which immediately presents a File response once done. That is why this “heart” of a function of the whole code is so small.

Having that PDF file retrieved back it can be uploaded again to the final destination. For simplicity reasons this will be done to the default library of the current SharePoint site. If there is a need for browsing sites/libraries/folders refer to another of my samples where I implemented that kind of “navigator”.

public async uploadFileToSiteAsPDF(siteID: string, file: Blob, fileName: string): Promise<string> {  
    const apiUrl = `sites/${siteID}/drive/root:/${fileName}:/content`;
    const response = await this.uploadFile(apiUrl, file);
    return response.webUrl;          

This function is even shorter as it reuses the uploadFile function shown above. This time only the used url endpoint is different AND the return value as for user comfort the final url of the file shall be shown in the frontend.

Last not least for cleanup reasons the temporary file in OneDrive can be deleted now which will be achieved as a last step.

public async deleteTmpFileFromOneDrive(fileID: string) {
    const apiUrl = `me/drive/items/${fileID}`;

Last not least of course this is a very simple solution which does not concentrate on give the most concentration on UI but nevertheless there is a small ProgressComponent that visualizes the finishing of the essential steps.

export const ProgressComponent: React.FunctionComponent<IProgressComponentProps> = (props) => {
  const [percentComplete, setPercentComplete] = React.useState(0);
  const intervalDelay = 3;
  const intervalIncrement = 0.01;

  React.useEffect(() => {
    if (percentComplete < 0.99) {
        setTimeout(() => {setPercentComplete((intervalIncrement + percentComplete) % 1);}, intervalDelay);

  return (
    <ProgressIndicator label={props.header} percentComplete={percentComplete} />

This does not show real-time-progress of the upload of course but is a simple visualization. The result will look like this:

As mentioned this is a simple solution outscoping

  • Find and browse your upload target, for instance specific folders (ref here)
  • The upload of files bigger than 4MB (ref here)
  • A more comfortble UI

The main reason is to point out the Microsoft Graph capability of file conversion to PDF in a very simple way. Therefore SPFx is a great platform. Nevertheless it’s necessary to have several roundtrips (upload / download / upload) from/to “the server”, that is your O365 tenant here. A better approach would be to “outsource” these steps to the backend. I might come back with that scenario, maybe in a small Microsoft Teams app together with a NodeJS backend soon. So stay tuned.

Meanwhile you can access the whole code repository in my github.

Markus is a SharePoint architect and technical consultant with focus on latest technology stack in Microsoft 365 and SharePoint Online development. He loves the new SharePoint Framework as well as some backend stuff around Azure Automation or Azure Functions and also has a passion for Microsoft Graph.
He works for Avanade as an expert for Microsoft 365 Dev and is based in Munich.
Although if partially inspired by his daily work opinions are always personal.

2 thoughts on “A simple SPFx file upload by drag&drop including PDF conversion

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s