SharePoint REST API File Uploads Using JavaScript and PnPjs

Two ways to upload a file to a SharePoint document library via code — raw REST calls, or PnPjs — plus what changes once the file is big enough that a single request isn’t the right approach anymore.


In this post: Option 1: raw REST API · Option 2: PnPjs · Large files: chunked uploads · A React upload component · When would you actually use this? · Related reading


Option 1: raw REST API
function uploadFileToSharePoint(file) {
    const siteUrl = "https://yourtenant.sharepoint.com/sites/yoursite";
    const libraryName = "Documents"; // Update with your library name
    const fileName = file.name;

    fetch(`${siteUrl}/_api/web/getfolderbyserverrelativeurl('${libraryName}')/files/add(overwrite=true, url='${fileName}')`, {
        method: 'POST',
        headers: {
            'Accept': 'application/json;odata=verbose',
            'X-RequestDigest': document.getElementById('__REQUESTDIGEST').value
        },
        body: file
    })
    .then(response => response.json())
    .then(data => console.log("File uploaded successfully", data))
    .catch(error => console.error("Error uploading file: ", error));
}

No dependencies, works in any JavaScript environment (SPFx, classic pages, standalone apps), and gives full control over the request. The tradeoff is that you’re handling the digest token, error handling, and (as covered below) large-file chunking entirely yourself.


Option 2: PnPjs
import { spfi } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/folders";
import "@pnp/sp/files";

const sp = spfi(/* your configured instance */);

async function uploadFileWithPnPjs(file) {
    const libraryName = "Documents";
    try {
        const response = await sp.web.getFolderByServerRelativePath(libraryName)
            .files.addUsingPath(file.name, file, { Overwrite: true });
        console.log("File uploaded successfully", response);
    } catch (error) {
        console.error("Error uploading file: ", error);
    }
}

Less boilerplate, and digest/auth handling is done for you. The real reason to reach for this over raw REST, though, is what’s covered next — large files.


Large files: chunked uploads

The single-request pattern above works fine for typical documents, but SharePoint’s upload limits and general reliability start working against you on bigger files — generally somewhere past 10MB is where a single request gets risky, more so on a slow or unreliable connection. With the raw REST approach, handling that means implementing chunked upload yourself against SharePoint’s StartUpload/ContinueUpload/FinishUpload endpoints. PnPjs wraps this behind one method:

async function uploadLargeFile(file) {
    const libraryName = "Documents";
    try {
        await sp.web.getFolderByServerRelativePath(libraryName)
            .files.addChunked(
                file.name,
                file,
                data => {
                    console.log(`Progress: block ${data.blockNumber}, stage ${data.stage}`);
                },
                true // overwrite
            );
        console.log("Large file uploaded successfully");
    } catch (error) {
        console.error("Error uploading large file: ", error);
    }
}

The default chunk size is 10MB, adjustable via an extra parameter if you’re seeing timeouts on a slower connection (mobile uploads, in particular). The progress callback (the third argument above) is what makes this genuinely useful for a real UI — you get a block number and stage on every chunk, enough to drive an actual progress bar instead of a spinner that doesn’t move for however long a 200MB upload takes.


PnPjs’s addChunked progress callback gives you a block number and stage on every chunk — enough to drive a real progress bar, not just a spinner.

A React upload component

Putting the PnPjs approach behind a basic file input and upload button:

import React, { useState } from 'react';
import { spfi } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/folders";
import "@pnp/sp/files";

const sp = spfi(/* your configured instance */);

const FileUpload = () => {
    const [file, setFile] = useState(null);

    const handleFileChange = (e) => {
        setFile(e.target.files[0]);
    };

    const handleUpload = async () => {
        if (file) {
            try {
                await sp.web.getFolderByServerRelativePath("Documents")
                    .files.addUsingPath(file.name, file, { Overwrite: true });
                alert('File uploaded successfully!');
            } catch (error) {
                alert('Error uploading file. Check console for details.');
                console.error(error);
            }
        }
    };

    return (
        
); }; export default FileUpload;

For anything beyond a quick internal tool, swap addUsingPath for addChunked here too and wire the progress callback into component state — users uploading anything beyond a small file benefit from seeing that something’s actually happening.


When would you actually use this?
  • You’re building a custom form (job applications, expense reports) that needs to land an attached file directly in a SharePoint library.
  • You’re automating a backup or migration process and need to push files into SharePoint programmatically rather than dragging them through the browser.
  • Users are uploading files large enough that a single-request upload times out or fails inconsistently — that’s the signal to switch to addChunked.


That covers both approaches and the large-file case. Questions about your specific setup? Drop them in the comments.



App Catalog Authentication Automation Backup Compliance Content Type CSS Flows Google Javascript Limitations List Metadata MFA Microsoft Node NodeJs O365 OneDrive Permissions PnP PnPJS Policy PowerApps Power Automate PowerAutomate PowerPlatform PowerShell React ReactJs Rest API Rest Endpoint Security Send an HTTP Request to SharePoint SharePoint SharePoint List SharePoint Modern SharePoint Online SPFX SPO Sync Tags Teams Termstore Versioning

Leave a Comment

Your email address will not be published. Required fields are marked *