SharePoint Attachments Operations with JavaScript

SharePoint attachments — files uploaded directly onto a list item, not into a document library — are common in forms and ticketing-style lists. Here’s how to create, read, and delete them with plain JavaScript against the REST API, no PnPjs required.


In this post: Uploading an attachment · Reading attachments · Deleting an attachment · Uploading several files at once · When would you actually use this? · Related reading


Uploading an attachment
function uploadAttachment(itemId, file) {
    var reader = new FileReader();
    reader.onload = function (e) {
        var arrayBuffer = e.target.result;
        fetch(`/sites/mysite/_api/web/lists/getbytitle('MyList')/items(${itemId})/AttachmentFiles/add(FileName='${file.name}')`, {
            method: 'POST',
            body: arrayBuffer,
            headers: {
                'Accept': 'application/json;odata=verbose',
                'X-RequestDigest': document.getElementById('__REQUESTDIGEST').value,
                'Content-Type': file.type
            }
        })
        .then(response => response.json())
        .then(data => console.log('File uploaded successfully', data))
        .catch(error => console.error('Upload failed', error));
    };
    reader.readAsArrayBuffer(file);
}

The item already has to exist — attachments are added to an existing list item, not created alongside a new one in the same call.


Reading attachments
function getAttachments(itemId) {
    fetch(`/sites/mysite/_api/web/lists/getbytitle('MyList')/items(${itemId})/AttachmentFiles`, {
        method: 'GET',
        headers: { 'Accept': 'application/json;odata=verbose' }
    })
    .then(response => response.json())
    .then(data => console.log(data.d.results))
    .catch(error => console.error('Error retrieving attachments', error));
}

Each entry in the returned array includes a ServerRelativeUrl — that’s the actual download link for the file, not just metadata about it.


Deleting an attachment
function deleteAttachment(itemId, fileName) {
    fetch(`/sites/mysite/_api/web/lists/getbytitle('MyList')/items(${itemId})/AttachmentFiles/getByFileName('${fileName}')`, {
        method: 'DELETE',
        headers: {
            'Accept': 'application/json;odata=verbose',
            'X-RequestDigest': document.getElementById('__REQUESTDIGEST').value
        }
    })
    .then(() => console.log('Attachment deleted successfully'))
    .catch(error => console.error('Error deleting attachment', error));
}

Uploading several files at once

A loop that awaits each upload one at a time works, but it’s not actually faster than doing them individually — each request still waits for the previous one to finish before starting. Running them concurrently with Promise.all is the real improvement, since the requests overlap instead of queueing:

function uploadAttachmentAsync(itemId, file) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = (e) => {
            fetch(`/sites/mysite/_api/web/lists/getbytitle('MyList')/items(${itemId})/AttachmentFiles/add(FileName='${file.name}')`, {
                method: 'POST',
                body: e.target.result,
                headers: {
                    'Accept': 'application/json;odata=verbose',
                    'X-RequestDigest': document.getElementById('__REQUESTDIGEST').value,
                    'Content-Type': file.type
                }
            })
            .then(response => response.json())
            .then(resolve)
            .catch(reject);
        };
        reader.readAsArrayBuffer(file);
    });
}

async function uploadAllAttachments(itemId, files) {
    const results = await Promise.all(
        files.map(file => uploadAttachmentAsync(itemId, file))
    );
    console.log(`Uploaded ${results.length} files`);
    return results;
}

Worth a caution: firing dozens of concurrent requests risks SharePoint’s throttling limits kicking in. For a handful of files (a form with 3-5 attachments) this is fine as-is; for genuinely large batches, cap the concurrency (process in chunks of 5-10 rather than all at once) instead of firing everything simultaneously.


A loop that awaits each upload one at a time isn’t actually faster than doing them individually — Promise.all is what makes the requests genuinely overlap.

When would you actually use this?
  • You’re building a custom form (a ticketing system, an intake form) where attachments belong to a specific list item rather than a shared document library.
  • You need file operations working in a plain JavaScript environment without pulling in PnPjs for just this one feature.
  • A migration or integration script needs to move attachments between list items or environments programmatically.


That covers create, read, delete, and doing it in bulk. What’s your setup using this for? Comment below.



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 *