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
})
Hi, this is an awesome post!
It is right what I was searching to perform smoothly a file migration I’m doing.
I wonder whether you used LWC or nodejs or what else coding to make the coding above.
Could you shed some light on my doubt?
I will thanks a lot!
This was from a NodeJS web service, written in Typescript.
Thank you! can you please see if you can help here: https://stackoverflow.com/questions/72977524/no-preview-for-the-image-i-uploaded-from-nodejs-code-to-salesforce-system
Hi there! Thank you for the post!! It was really helpful!!
The only problem I’m facing is that the .m4a audio file I upload gets corrupted in the process.
Do you know what the problem might be? Thanks!!
Thanks for the help. Fwiw this:
file.toString(‘base64′)
Tripped me up.
When I read a file (fs.readfile) I don’t specify encoding, it seems it handles the binary encoding without issue. When trying to convert toString my uploads end up corrupted. ( regardless of the specified encoding)
Europe, and in Ancient Russia