Salesforce File Storage limits are a common pain point for organizations. However, there are business use cases that call for Files to appear against multiple documents for the sake of convenience or speed of access. We will quickly look at a simple way to solve for this business use case without increasing the hit against File Storage.
We have a request that when a File is added to an Opportunity, that File should also appear against the File list for that Opportunity’s Account. Obviously, we don’t want to duplicate the File and attach it to the Account, and thankfully there is a much easier way to accomplish this.
First, you should understand the File object model as it can be a touch confusing, but is a very common area in which developers must work. The objects to check are ContentDocument, ContentVersion, and ContentDocumentLink. When we insert a File against the Opportunity, we will end up with a ContentDocument (the main record of the File), a ContentVersion (the specific version of the ContentDocument, and holds content of the File), and then a ContentDocumentLink which is a junction object that links the ContentDocument to a record (via the LinkedEntityId). The key piece of information here is that we can have multiple ContentDocumentLinks in order to associate/share the File with mutiple different records – even of different objects, as the LinkedEntityId is a polymorphic relationship.
In fact, if you’re observant you may have noticed already that when you load a File to a record, and then navigate to the created ContentDocument via ‘View File Details’, you’ll see that the File is actually shared with you as the Owner, as well as with the record you’ve loaded the File against. Here I have loaded a test document to an Opportunity ‘Virtual Meeting for Training Team’.
So we actually have two ContentDocumentLinks to start with, as you can see with a simple query against the ContentDocumentLink object where the parent ContentDocument is the File we just loaded. We’ll extend this logic to associate the File against additional records as well.
SELECT id, linkedentityid FROM ContentDocumentLink WHERE ContentDocumentId = 'YourContentDocumentIdHere'
One File, Many Records
We can associate the File to other records in many ways, but we’ll use anonymous Apex to show the concept, and you can take it from there. Here we get the Id from the File (ContentDocumentId) and the Id from the Account on the Opportunity (LinkedEntityId). We create the ContentDocumentLink using those two Ids.
ContentDocumentLink cdl = new ContentDocumentLink();
cdl.ContentDocumentId = '0695e0000080jSKAAY'; //Id from File ContentDocument
cdl.LinkedEntityId = '0015e000006jjMQAAY'; //Id from Account
Once complete, we can view the File and see that it is shared with the Account now via the ContentDocumentLink.
Re-running our query, we now see there are 3 ContentDocumentLinks for the File that we loaded.
Navigating to the Account, we see the File in the Files related list. Remember we didn’t actually increase our File Storage, but get the same outcome that we need.
Probably the most typical way to employ this approach would be in the trigger against the ContentDocumentLink, where if a certain relationship record exists on the LinkedEntityId, then we’d also want to create a ContentDocumentLink against that related record. For example, if we load a File against the Opportunity, we check the SObjectType of the LinkedEntityId, find that it is an Opportunity, and then collect the Account from that Opportunity, and finally create an additional ContentDocumentLink against that Account as a LinkedEntityId.
While this post is primarily looking at using Apex to satisfy the requirement, community expert Gidi Abramovich points out that he’s solved similar business use cases in Flows. In one, he used a record triggered flow with a scheduled path (to prevent File insert failure in case of any issues) to create ContentDocumentLink references against child records from their parent. In another, he built a user-driven cloning Flow through which the user can define to where an existing File can be referenced via ContentDocumentLink. This second scenario seems ripe for a dedicated post as Gidi is one of the top Flow aficionados out there!
Pretty simple one here, but I’m not sure it’s as widely known as it could be, so hopefully is of some help for those of you facing a similar scenario! Thanks for reading.