Patrick Galbraith

Web developer - Adelaide, Australia

Uploading files to Salesforce using jsForce

There aren’t many examples of how you can upload files (aka ContentVersion entities) to Salesforce using the jsForce library. So I wanted to document what I found while working with the library.

Simplest way

The easiest way to upload a file is to base64 encode the contents and pass it to the create method. Unfortunately this method is limited to around 35MB.

const uploadContentVersion = (fileName: string, file: Buffer): Promise<jsforce.RecordResult> =>
  connection.sobject('ContentVersion').create({
    PathOnClient : fileName,
    VersionData : file.toString('base64')
  })

Multipart form data

The trick here is that you need to encode the request correctly as per the example in the documentation.

Example request body

--boundary_string
Content-Disposition: form-data; name="entity_content";
Content-Type: application/json
 
{  
    "Name" : "Marketing Brochure Q1 - Sales",
    "Keywords" : "sales, marketing, first quarter"
}
 
--boundary_string
Content-Type: application/pdf
Content-Disposition: form-data; name="Body"; filename="2011Q1MktgBrochure.pdf"
 
Updated document binary data goes here.
 
--boundary_string--

Using the nodejs request library

The popular nodejs `request` library provides a formData option which can be used like below:

import request from 'request'
import jsForce from 'jsforce'

const uploadContentVersion = (metadata: ContentVersionMetadata, file: Buffer): Promise<jsForce.RecordResult> =>
  new Promise((resolve, reject) => {
    const connection: jsForce.Connection = // ...create jsForce connection

    request.post({
      url: connection.instanceUrl + '/services/data/v49.0/sobjects/ContentVersion',
      auth: {
        bearer: connection.accessToken
      },
      formData: {
        entity_content: {
          value: JSON.stringify(metadata),
          options: {
            contentType: 'application/json'
          }
        },
        VersionData: {
          value: file,
          options: {
            filename: metadata.PathOnClient,
            contentType: 'application/octet-stream'
          }
        }
      }
    }, (err, response) => {
      if (err)
        reject(err)

      resolve(JSON.parse(response.body))
    })
  })

Example using `form-data` library

You could also create the formData object manually like this.

  const formData = new FormData()

  formData.append('entity_content', JSON.stringify(metadata), { contentType: 'application/json' })
  formData.append('VersionData', file, {
    filename: metadata.PathOnClient,
    contentType: 'application/octet-stream'
  })

  // then make request using formData object

Full example attaching to Case

export const addFileToCase = async (caseId: string, fileName: string, file: Buffer): Promise<string> => {
  // We use the helper method created previously
  const uploadResult = await uploadContentVersion(file, {
    PathOnClient: fileName,

    // For updates you can also add the following params
    // ContentDocumentId: '',
    // ReasonForChange: ''
  })

  if (!uploadResult.success) {
    throw new Error(`Failed to upload file for case "${caseId}"`)
  }

  const contentDocument = await connection.sobject<{
    Id: string,
    ContentDocumentId: string
  }>('ContentVersion').retrieve(uploadResult.id)

  const linkResult = await connection.sobject<TRes>('ContentDocumentLink').create({
    ContentDocumentId: contentDocument.ContentDocumentId,
    LinkedEntityId: caseId,
    ShareType: 'V'
  })

  if (!linkResult.success) {
    throw new Error(`Failed to link content document for case "${caseId}"`)
  }

  return uploadResult.id
})

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>