Uploading Attachments to SharePoint Lists Using REST
In a previous post, I walked through Uploading Attachments to SharePoint Lists Using SPServices. Well, in the “modern” world, I want to use REST whenever I can, especially with SharePoint Online.
I ran into a challenge figuring out how to make an attachment upload work in an AngularJS project. There are dozens of blog posts and articles out there about uploading files to SharePoint, and some of them even mention attachments. But I found that not a single one of the worked for me. It was driving me nuts. I could upload a file, but it was corrupt when it got there. Images didn’t look like images, Excel couldn’t open XLSX files, etc.
I reached out to Julie Turner (@jfj1997) and asked for help, but as is often the case, when you’re not as immersed in something it’s tough to get the context right. She gave me some excellent pointers, but I ended up traversing some of the same unfruitful ground with her.
Finally I decided to break things down into the simplest example possible, much like I did in my earlier post with SOAP. I created a test page called UploadTEST with a Content Editor Web Part pointing to UploadText.html, below:
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.js"></script> <input id="my-attachments" type="file" fileread="run.AttachmentData" fileinfo="run.AttachmentInfo" /> <script type="text/javascript" src="/sites/MySiteCollection/_catalogs/masterpage/_MyProject/js/UploadTEST.js"></script>
Like I said, I wanted to keep it simple. Here’s what I’m doing:
- In line 1, I load a recent version of jQuery from cdnjs. On the theory that Angular just adds another layer of complexity, I wanted to try just jQuery, which is useful for its $.ajax function, among many other things.
- In line 3, I’ve got an input field with type=”file”. In “modern” browsers, this gives us a familiar file picker.
- In line 5, I’m loading my script file called UploadTest.js, below:
$(document).ready(function() { var ID = 1; var listname = "UploadTEST"; $("#my-attachments").change(function() { var file = $(this)[0].files[0]; var getFileBuffer = function(file) { var deferred = $.Deferred(); var reader = new FileReader(); reader.onload = function(e) { deferred.resolve(e.target.result); } reader.onerror = function(e) { deferred.reject(e.target.error); } reader.readAsArrayBuffer(file); return deferred.promise(); }; getFileBuffer(file).then(function(buffer) { $.ajax({ url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('" + listname + "')/items(" + ID + ")/AttachmentFiles/add(FileName='" + file.name + "')", method: 'POST', data: buffer, processData: false, headers: { "Accept": "application/json; odata=verbose", "content-type": "application/json; odata=verbose", "X-RequestDigest": document.getElementById("__REQUESTDIGEST").value, "content-length": buffer.byteLength } }); }); }); });
Here’s how this works – and yes, it does work!
- I’ve got everything wrapped in a $(document).ready() to ensure the page is fully loaded before my script runs.
- Lines 3-4 just set up some variables I could use for testing to keep things simple.
- In line 6, I bind to the change event for the input field. Whenever the user chooses a file in the dialog, this event will fire.
- In line 8, I’m getting the information about the file selected from the input element.
- Lines 10-26 is the same getFileBuffer function I used with SOAP; it’s where we use a FileReader to get the contents of the selected file into a buffer.
- The function in lines 28-42 runs when the file contents have been fully read. The getFileBuffer function returns a promise, and that promise is resolved when the function has gotten all of the file contents. With a large file, this could take a little while, and by using a promise we avoid getting ahead of ourselves. Here we make the REST call that uploads the attachment.
- The URL ends up looking something like: “/my/site/path/_api/web/lists/getbytitle(‘UploadTEST’)/items(1)/AttachmentFiles/add(FileName=’boo.txt’)”
- The method is a POST because we’re writing data to SharePoint
- The data parameter contains the “payload”, which is the buffer returned from the getFileBuffer function.
- The headers basically tell the server what we’re up to:
- Accept doesn’t really come into play here unless we get a result back, but it says “send me back json and be verbose”
- content-type is similar, but tells the server what it is getting from us
- X-RequestDigest is the magic string from the page that tells the server we are who it thinks we are
- content-length tells the server how many bytes it should expect
This worked for me on a basic Custom List (UploadTEST). So now I knew it was possible to upload an attachment using REST.
I took my logic and shoved it back into my AngularJS page, and it still worked! However, when I switched from $.ajax to AngularJS’s $http, I was back to corrupt files again. I’m still not sure why that is the case, but since I’m loading jQuery for some other things, anyway, I’ve decided to stick with $.ajax for now instead. If anyone reading this has an idea why $http would cause a problem, I’d love to hear about it.
Once again, I’m hoping my beating my head against this saves some folks some time. Ideally there would be actual documentation for the REST endpoints that would explain how to do things. Unfortunately, if there is for this, I can’t find it. I’d also love to know if /add allows any other parameters, particularly whether I can overwrite the attachment if one of the same name is already there. Maybe another day.
Hello @Marc ,
I want to know a little bit about
fileread=”run.AttachmentData”
fileinfo=”run.AttachmentInfo”.
Is they playing a major role in this ?
Hello Sir, could you please help me to understand that how to save jsPDF output to sharepoint document library ?
i’ll we really thank full to you if you could help me …
There’s no way the example code above works for non-text files. I’ve tried it over and over for a PNG file and the content is constantly corrupted. Yes, I’ve tried many different files that I know are good. The length of the content that appears on the server is different than what is retrieved from the buffer. Also, the content-length header is considered unsafe and is not allowed by the browser via the $.ajax call. It would be nice to see this content revisited or someone else to chime in and verify they are able to upload a non-text file using the code in this blog VERBATIM.
Well, I take some of what I said back about it not working hehe The content-length header is definitely against the rules but after removing some conflicting ajax options and eliminating the base64 encoding which all of the MS documentation I’ve seen insists is necessary, it does work. Thanks Marc! You got me over the final hurdle.
@Sir:
Glad to hear you got it working!
M.
i am having the same issue while trying to upload an attachment through REST api using Ruby on rails technology, when i post an image, it successfully upload the image but when i check the image in sharepoint i get corrupted image.
Hi Marc, I’ve used this code for my multiple upload function on my site. What i did was create the item first then all attachments will be POST as an update. There are reports from some of my users that they uploaded for example 4 files but only 3 got through. It seems there are instances that don’t upload all my attachments. But my error handler on ajax doesn’t return any error and been testing on my side these never occur. Do you have any idea why?