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

Your Answer

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

Browse other questions tagged or ask your own question.