I'm using a @RemoteAction to upload files to Salesforce, saving them as ContentVersion (Chatter files). I'm uploading the base64 Blob of the file.
This works great for small (<1MB) files.
I am NOT using lightning components for this. This is our Angular app calling a remote action with a file that the user uploaded.
Specifically the error I am getting is:

POST https://<instance>.visual.force.com/apexremote net:: ERR_CONNECTION_RESET
Visualforce Remoting Exception: Unable to connect to the server (communication failure).

From what I can tell, this is because of the HTTP POST size limit of 1MB.
Now, if you use the Salesforce-y method (directly uploading a file or embedding Chatter in a VF page) then you're good for 2GB.
How can I do that from my Angular app? Do I need to chunkify the blob myself? Is there a way for me to create a ContentVersion piecemeal?


Well, I solved this.
Sort of.
My solution was to forgo the use of a @RemoteAction and instead use Salesforce's REST services.
For those following at home home, there are two ways to do this:
1. The easy way, use JSForce.
2. The stupid way (which I ended up using for reasons), roll your own XmlHttpRequest calls.
Both are essentially the same (behind the scenes JSForce uses XmlHttpRequest to do its thing), so the limit seems to be 25MB.
EDIT: For the adventurous, here's some boilerplate code.

let targetUrl = baseUrl + '/services/data/v37.0/sobjects/ContentVersion';
let xhr = new XMLHttpRequest();
xhr.open('POST', targetUrl, true);
xhr.setRequestHeader('Accept', '*/*');
xhr.setRequestHeader('Authorization', 'Bearer ' + sessionId);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = myCallbackFunction;
xhr.send(JSON.stringify({PathOnClient : fileName, VersionData : base64data});

EDIT September 2019

While searching for a solution to a similar problem, I encountered this question, and can now add functionality to allow uploading large files (>25MB).
Essentially, the best thing to do would be to use a multipart/form-data request. However if you blithely do the following you'll encounter a problem:

let formData = new FormData()
let metadata = {
    PathOnClient: fileName, 
    ContentDocumentId: docId, 
    ReasonForChange : changeReason
formData.append('entity_content', JSON.stringify(metadata));
formData.append('VersionData', fileField.files[0]);

You'll get a request built something like this:

Content-Disposition: form-data; name="entity_content";

    "ContentDocumentId" : "069XXXXXXXXXXXX",
    "ReasonForChange" : "Marketing materials updated",
    "PathOnClient" : "Q1 Sales Brochure.pdf"

Content-Type: application/octet-stream
Content-Disposition: form-data; name="VersionData"; filename="Q1 Sales  Brochure.pdf"

Binary data goes here.


Now that is very similar to Salesforce's example in the docs except that it's missing a crucial line: Content-Type: application/json in the first part.
You'll get an error like so: {message: "Multipart message must include a non-binary part", errorCode: "INVALID_MULTIPART_REQUEST"} After much searching and anguish it turns out that this isn't Salesforce's fault! It's a general problem with the FormData object.

The solution is to generate your own request and send it. Like so:

<input type="file" #fileField name="fileName" />

let boundary = '----boundaryString';
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
xhr.send(new Blob([this.getRequestDataPart1(boundary), this.fileField.files[0], this.getRequestDataPart2(boundary)]));

getRequestDataPart1(boundary: string): string{
    let data =
    Content-Disposition: form-data; name="entity_content";
    Content-Type: application/json

        "ContentDocumentId" : "069XXXXXXXXXXXX", //leave blank for new ContentDocument
        "ReasonForChange" : "Marketing materials updated", // not actually required
        "PathOnClient" : "${this.fileName}"

    Content-Type: ${this.mimeType}
    Content-Disposition: form-data; name="VersionData"; filename="${this.fileName}"

    return data;

  getRequestDataPart2(boundary: string): string{
    let data =

    return data;

I really hope that this helps someone. Maybe me in a year's time.

| improve this answer | |
  • I tried using JSForce. It seems limit is just 10lakh characters approx 3-4mb. I'm really stuck on this. – Rohan Shenoy Nov 7 at 15:39

The solution proposed by AvailableName works but I have spent multiple hours debugging because of how salesforce handles end of line symbols. I was getting an error saying that my data is in an incorrect format (maybe because of the editor I was using). To solve this I had to build the request like so:

sendXHR: function(component, helper, files, url, sessionId) {
    var xhr = new XMLHttpRequest();
    xhr.open('POST', url, true);
    xhr.setRequestHeader('Accept', '*/*');
    xhr.setRequestHeader('Cache-Control', 'no-cache');      
    xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary="boundary_string"');
    xhr.setRequestHeader('Authorization',  sessionId);
    var payloadPart1 = "--boundary_string" +
    "\r\nContent-Disposition: form-data; name=\"entity_content\";" +
    "\r\nContent-Type: application/json" +
    "\r\n\r\n{ \"FirstPublishLocationId\" : \"" + component.get('v.Id') + "\", \"PathOnClient\" : \"" + file.name.replace(/\.[^/.]+$/, "") + '.' + file.name.substr(file.name.lastIndexOf('.') + 1) + "\"}" +
    "\r\n\r\n--boundary_string" +
    "\r\nContent-Type: application/octet-stream" +
    "\r\nContent-Disposition: form-data; name=\"VersionData\"; filename=\"" + file.name + " \"" +
    var payloadPart2 = "\r\n\r\n--boundary_string--";
    xhr.send(new Blob([payloadPart1, file, payloadPart2]));


| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.