function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Tarek Zaheer 1Tarek Zaheer 1 

Updating a checkbox field if a File is uploaded through ContentDocumentLink trigger

Hi everyone,

Here is my business requirement:

On the standard Quote object, I have a custom checkbox field called PO_Attached__c. If a File is uploaded to a Quote record, PO_Attached__c should be updated to true. I currently achieve this functionality using the Attachments functionality by use of an Apex trigger.

However, we are now migrating to Files. I have updated my code block as below. It saves fine, but the checkbox is never updated. Any ideas why? Thanks in advance.
 
trigger quoteAttachment2 on ContentDocumentLink (after insert) {

                        List<Quote> listOfQuote = [select id from Quote where id =: Trigger.New[0].LinkedEntityId];
                        if(listOfQuote.size()>0)
                        {
                                    listOfQuote[0].PO_Attached__c = true;
                                    update listOfQuote;
                        }                                                                   
            }

 
Best Answer chosen by Tarek Zaheer 1
Douglas C. AyersDouglas C. Ayers
Your trigger needs to be bulkified. Minimally two content document links get created when uploading a file to a record: one to the create user and one to the record itself. 

You will need something like this (untested):

Each time a CDL is inserted, update, or deleted then queries the related quotes and determines if they have at least one file shared to them. Be aware, Enhanced Notes shared to records use this same object so unless you filter the ContentDocumentLink.ContentDocument.FileType != ‘snote’ then that may give you false positives. 
 
trigger ContentDocumentLinkTrigger on ContentDocumentLink ( after insert, after update, after delete ) {

    List<ContentDocumentLink> cdls = ( Trigger.new == null ? Trigger.old : Trigger.new );

    Set<ID> parentIds = Set<ID>();

    for ( ContentDocumentLink cdl : cdls ) {
        parentIds.add( cdl.LinkedEntityId );
}

    for ( List<Quote> quotes : [ SELECT Id, ( SELECT Id FROM ContentDocumentLinks LIMIT 1 ) FROM Quote WHERE Id IN :parentIds ORDER BY OpportunityId ] ) {
        
        for ( Quote q : quotes ) {
            q.PO_Attached__c = ( q.ContentDocumentLinks.size() > 0 );
        }

        update quotes;

    }

}

 

All Answers

Douglas C. AyersDouglas C. Ayers
Your trigger needs to be bulkified. Minimally two content document links get created when uploading a file to a record: one to the create user and one to the record itself. 

You will need something like this (untested):

Each time a CDL is inserted, update, or deleted then queries the related quotes and determines if they have at least one file shared to them. Be aware, Enhanced Notes shared to records use this same object so unless you filter the ContentDocumentLink.ContentDocument.FileType != ‘snote’ then that may give you false positives. 
 
trigger ContentDocumentLinkTrigger on ContentDocumentLink ( after insert, after update, after delete ) {

    List<ContentDocumentLink> cdls = ( Trigger.new == null ? Trigger.old : Trigger.new );

    Set<ID> parentIds = Set<ID>();

    for ( ContentDocumentLink cdl : cdls ) {
        parentIds.add( cdl.LinkedEntityId );
}

    for ( List<Quote> quotes : [ SELECT Id, ( SELECT Id FROM ContentDocumentLinks LIMIT 1 ) FROM Quote WHERE Id IN :parentIds ORDER BY OpportunityId ] ) {
        
        for ( Quote q : quotes ) {
            q.PO_Attached__c = ( q.ContentDocumentLinks.size() > 0 );
        }

        update quotes;

    }

}

 
This was selected as the best answer
Tarek Zaheer 1Tarek Zaheer 1
Works like a charm. Thanks a million, Doug!
Rick HaagRick Haag
Hi Doug,

Thank you for posting this. I have configured this to update a Case field and written a test class w/100% coverage. I have a question expanding this to address multiple objects in the trigger. How would you address this if you wanted to include Accounts and Opportunity objects in the same trigger vs. writing one for each.

TRIGGER
trigger ContentDocumentLinkTriggerCase on ContentDocumentLink ( after insert, after update, after delete ) {

    List<ContentDocumentLink> cdls = ( Trigger.new == null ? Trigger.old : Trigger.new );

   
    set<id> parentIds = new set<id>();

    for ( ContentDocumentLink cdl : cdls ) {
        parentIds.add( cdl.LinkedEntityId );
}

    for ( List<Case> cases : [ SELECT Id, ( SELECT Id FROM ContentDocumentLinks LIMIT 1 ) FROM Case WHERE Id IN :parentIds ORDER BY AccountId ] ) {
        
        for ( Case c : cases ) {
            c.Custom_Field__c = ( c.ContentDocumentLinks.size() > 0 );
        }

        update cases;

    }

}

TEST CLASS
@isTest
private class ContentDocumentLinkTriggerTest
{
    @isTest
    static void itShould()
    {
        Account acc = new Account(name='test acc');
        insert acc;
        Contact con = new Contact(lastname='test cont',accountid=acc.id);
        insert con;
        //Opportunity opp = new Opportunity(name='testoppty',AccountId=acc.id,stageName='Qualification',closedate=system.today());
        //insert opp;
        Case cas = new Case(AccountId=acc.id,ContactId=con.id,Subject='This is a test',Status='New',Priority='Low',Type='Problem',Origin='Email');
        insert cas;

        ContentVersion content=new ContentVersion(); 
            content.Title='Header_Picture1'; 
            content.PathOnClient='/' + content.Title + '.jpg'; 
            Blob bodyBlob=Blob.valueOf('Unit Test ContentVersion Body'); 
            content.VersionData=bodyBlob; 
            //content.LinkedEntityId=sub.id;
            content.origin = 'H';
        insert content;
        ContentDocumentLink contentlink=new ContentDocumentLink();
            contentlink.LinkedEntityId=cas.id;
            contentlink.contentdocumentid=[select contentdocumentid from contentversion where id =: content.id].contentdocumentid;
            contentlink.ShareType = 'V';
            test.starttest();
        insert contentlink;
test.stoptest();

    }
}

 
Rishi Kumar 135Rishi Kumar 135
Hi Thanks for sharing the code, i tried at my end but it is not working if we delete the file. below is my code.

trigger ContentDocumentLinkTrigger on ContentDocumentLink ( after insert, after update, after delete ) {

    List<ContentDocumentLink> cdls = ( Trigger.new == null ? Trigger.old : Trigger.new );

    Set<ID> parentIds = New Set<ID>();

    for ( ContentDocumentLink cdl : cdls ) {
        parentIds.add( cdl.LinkedEntityId );
}

    for ( List<Opportunity> oppsToUpdate: [ SELECT Id, ( SELECT Id FROM ContentDocumentLinks LIMIT 1 ) FROM Opportunity WHERE Id IN :parentIds ] ) {
        
        for ( Opportunity q : oppsToUpdate) {
            q.Count__c = ( q.ContentDocumentLinks.size() > 0 );
        }

        update oppsToUpdate;

    }

}
Lewis HowelLewis Howel
Hi all,

I need to something similar, but with content on service appointment instead.

The field I want to check as true is "Service_Report_PDF_Generated__c"

Below is my adaptation of the code (I still need to add fileter in to only run if ContentDocument name starts with "SA-" by the way...)

trigger ContentDocumentLinkTrigger on ContentDocumentLink ( after insert, after update, after delete ) {

    List<ContentDocumentLink> cdls = ( Trigger.new == null ? Trigger.old : Trigger.new );

    Set<ID> parentIds = Set<ID>();

    for ( ContentDocumentLink cdl : cdls ) {
        parentIds.add( cdl.LinkedEntityId );
}

    for ( List<ServiceAppointment> Serviceappointments : [ SELECT Id, ( SELECT Id FROM ContentDocumentLinks LIMIT 1 ) FROM ServiceAppointment WHERE Id IN :parentIds ORDER BY ServiceAppointmentId ] ) {
        
        for ( ServiceAppointment q : Serviceappointments ) {
            q.Service_Report_PDF_Generated__c = ( q.ContentDocumentLinks.size() > 0 );
        }

        update quotes;

    }

}

The error I'm receiving when trying to save is "Error: Compile Error: Unexpected token '<'. at line 5 column 8"

This is my very first attempt at writing a trigger btw :)
Deepak Patil 16Deepak Patil 16
A 'new' is missing on that line.
so the corrected line would be:
Set<ID> parentIds = new Set<ID>();