Edit: I've built an after insert trigger on ContentDocumentLink. On cropredy's direction, I've bulkified my code and I think improved it generally. It "works" in that it accomplishes my business requirement, however my insert relies on a try-catch handler that I should learn how to do without.

The goal: whenever someone creates a ContentNote or attaches a File (a ContentDocument), I want to automatically share it with a Chatter group that I created, called ContentNote Administration. This ensures we'll have back-end visibility on all notes and files.

Here are the steps I want my code to execute:

  1. When someone creates a new Document (like a File upload or a ContentNote), Salesforce inserts a record into ContentDocumentLink, relating that record (say, ContentNote) with the owner. Then, I want the trigger to fire.
  2. The trigger passes the ContentDocumentLink sObject, along with the name of the link-to object type and name (in this case, a CollaborationGroup named 'ContentNote Administration').
  3. If a ContentDocumentLink record already exists relating this ContentDocumentId with this LinkedEntityId, don't insert.
  4. If the current insert relates this ContentDocumentId with this LinkedEntityId, don't insert - I believe this is necessary to avoid infinite loop.
  5. Else, build a new ContentDocumentLink record and insert it.

Here is my trigger:

trigger vwContentDocumentLinkAction on ContentDocumentLink (before delete, before insert, before update, after delete, after insert, after update) {

    if (Trigger.isAfter) {

        if (Trigger.isInsert) {

            List<ContentDocumentLink> cdLinks = new List<ContentDocumentLink>();

            SObject cGID = [SELECT Id FROM CollaborationGroup WHERE Name = 'ContentNote Administration' LIMIT 1];

                for (ContentDocumentLink cdl : Trigger.new) {
                    if(vwContentNoteVisible.shouldShare(cdl, cGID)) { // don't add if it's a recursive trigger

            vwContentNoteVisible.shareWithCollaborationGroup(cdLinks, cGID);


Here is my class:

public class vwContentNoteVisible {

    public static void shareWithCollaborationGroup(List<ContentDocumentLink> cdlNeedLinks, SObject collabGroup) {

        for (ContentDocumentLink cdl : cdlNeedLinks) {
            ContentDocumentLink cdlNewLink = new ContentDocumentLink();

            cdlNewLink.ContentDocumentId = cdl.ContentDocumentId;
            cdlNewLink.LinkedEntityId = collabGroup.Id;
            cdlNewLink.ShareType = 'V';
            cdlNewLink.Visibility = 'AllUsers';

            // exception handler: catches DML Field_Integrity_Exception on recursion trigger
            // This is because ContentDocumentLink doesn't permit duplicates.
            try {


            } catch(DmlException e) { System.debug('Exception occurred on insert: ' + e.getMessage()); }
            // do without this try catch, once you build your set of DocumentIDs

    public static Boolean shouldShare(ContentDocumentLink thisLink, SObject collabGroup) {

        if(thisLink.LinkedEntityId == collabGroup.Id) {
            return false;
        } else {
            return true;

My try-catch handles the following Field Integrity Exception that results because the insert recursively calls this trigger:

caused by: System.DmlException: Insert failed. First exception on row 0; >first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, >vwContentDocumentLink_sharing: execution of AfterInsert

caused by: System.DmlException: Insert failed. First exception on row 0; >first error: FIELD_INTEGRITY_EXCEPTION, Document with ID: >0690j0000008nkf is already linked with the entity with ID: >0F90j0000008T0T: Linked Entity ID: [LinkedEntityId]

What are the best strategies for handling this recursive insert without relying on error handling? Any help would be appreciated.

  • 1
    Tim - note that you are doing soql inside the for loop on Trigger.new. This is hence not bulkified and will cause issues later down the road. You should rewrite the handler class/method to accept a list of CDL ids and query accordingly. Once you do that, edit your post with the new code and you'll get more help from the Community – cropredy Mar 30 '17 at 5:28
  • Thank you for the suggestion, I will fix it accordingly! – tim shores Mar 30 '17 at 13:28

while researching a problem similar to yours, I found an answer you might find useful:

Document already Linked

Basically the idea is to introduce a check on whether there is an existing ContentDocumentLink with the same LinkedEntityId before inserting. However, the check avoids the error but prevents insertion, which is after all not that different from your try / catch block. I still haven't found a solution to avoid the error AND insert the record. I'll keep you posted if I find it.

  • I found the problem. In my case, I was creating a ContentVersion CV record with this field: cv.FirstPublishLocationId = parentId; (where parentId was the record to which I wanted to attach the file). So that created the connection between the record and the attachment. The ContentDocumentLink record insert was failing because that connection had already been established. Once I removed that field, it worked perfectly. You might want to check if something similar happens before your class executes. – AlessioBortone Mar 26 at 11:35

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.