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
Ryan GreeneRyan Greene 

add note with apex trigger

Hello,
I created a very simple trigger to add a Note when a Lead is created. The Note is not getting added, what am I doing wrong? (Code below) Is it not working because the Lead is not yet created? With an After Insert this shouldn't matter though. Notes are turned on and are working to manually create them.
Thanks,
Ryan
trigger AddNote on Lead (after insert) {
	List<Note> addnte = new List<Note>();
        for(Lead lds : Trigger.new){
            Note addedntes = new Note();
            addedntes.ParentId = lds.Id;
            addedntes.Body = lds.Sales_Notes__c;
            addedntes.Title = 'Creation Note';
            addnte.add(addedntes);
        }
    if(addnte.size()>0){
        insert addnte;
    }
}

 
Best Answer chosen by Ryan Greene
Ryan GreeneRyan Greene
Took a little more research but I figured it out! SF has a few different objects controlling the New Notes (AKA Content Notes). Some documentation here: https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_erd_contentnote.htm

Basically you need to create the ContentDocumentLink to link it to both the ContentNote and the sObject.
trigger AddNote on Lead (after insert) {
    List<ContentNote> nte = new List<ContentNote>();
    List<ContentDocumentLink> lnk = new List<ContentDocumentLink>();
    for(Lead lds : Trigger.new){
        ContentNote cnt = new ContentNote();
        cnt.Content = Blob.valueof(lds.Sales_Notes__c);
        cnt.Title = 'Creation Note';
        nte.add(cnt);
    }
    
    if(nte.size()>0){
        insert nte;
    }
    for(Lead lds : Trigger.new){
        ContentDocumentLink clnk = new ContentDocumentLink();
        clnk.LinkedEntityId = lds.Id;
        clnk.ContentDocumentId = nte[0].Id;
        clnk.ShareType = 'I';
        lnk.add(clnk);
    }
    
    if(nte.size()>0){
        insert lnk;
    }
}

 

All Answers

Manjul SharmaManjul Sharma
Hello Ryan, your code works like a charm for me and the note is visible under "Notes & Attachments" related list (NOT under "Notes" related list).
v varaprasadv varaprasad
Hi Ryan,

Your code is working fine.Please check once the trigger is active or not.

Thanks
Varaprasad
@For Support: varaprasad4sfdc@gmail.com
devedeve
Hi,

This code is working fine. When you add new Lead record then it will add equal number of Note record.

Thanks
Ryan GreeneRyan Greene
Thanks guys, I was looking only at the Notes list, didn't even have Notes & Attachments on the page.

Anyway I can add a Note to just the Notes list? 
Ryan GreeneRyan Greene
Took a little more research but I figured it out! SF has a few different objects controlling the New Notes (AKA Content Notes). Some documentation here: https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_erd_contentnote.htm

Basically you need to create the ContentDocumentLink to link it to both the ContentNote and the sObject.
trigger AddNote on Lead (after insert) {
    List<ContentNote> nte = new List<ContentNote>();
    List<ContentDocumentLink> lnk = new List<ContentDocumentLink>();
    for(Lead lds : Trigger.new){
        ContentNote cnt = new ContentNote();
        cnt.Content = Blob.valueof(lds.Sales_Notes__c);
        cnt.Title = 'Creation Note';
        nte.add(cnt);
    }
    
    if(nte.size()>0){
        insert nte;
    }
    for(Lead lds : Trigger.new){
        ContentDocumentLink clnk = new ContentDocumentLink();
        clnk.LinkedEntityId = lds.Id;
        clnk.ContentDocumentId = nte[0].Id;
        clnk.ShareType = 'I';
        lnk.add(clnk);
    }
    
    if(nte.size()>0){
        insert lnk;
    }
}

 
This was selected as the best answer
Amar Mazhar 11Amar Mazhar 11
Thanks Ryan for sharing this code, its very usefull.

I am trying to use your code with slight change to create notes only when a field chnage on the object. But its creating 3 entries for the notes. please can you help me why its creating duplicates?

trigger AddNote_Workorder on WorkOrder (Before update) {
    List<ContentNote> nte = new List<ContentNote>();
    List<ContentDocumentLink> lnk = new List<ContentDocumentLink>();
    for(WorkOrder lds : Trigger.new){
    if(Trigger.oldmap.get(lds.id).Next_Action_Notes__c!= Trigger.newmap.get(lds .id).Next_Action_Notes__c){
        ContentNote cnt = new ContentNote();
        cnt.Content = Blob.valueof(lds.Next_Action_Notes__c);
        cnt.Title =lds.CreatedDate + ' Creation Note';
        nte.add(cnt);
    }
    }
    if(nte.size()>0){
        insert nte;
    }
    for(WorkOrder lds : Trigger.new){
    if(Trigger.oldmap.get(lds.id).Next_Action_Notes__c!= Trigger.newmap.get(lds .id).Next_Action_Notes__c){
        ContentDocumentLink clnk = new ContentDocumentLink();
        clnk.LinkedEntityId = lds.Id;
        clnk.ContentDocumentId = nte[0].Id;
        clnk.ShareType = 'I';
        lnk.add(clnk);
    }
    }
    if(nte.size()>0){
        insert lnk;
    }
   
}

 
murdocmurdoc
I have a similar issue except the note is uploaded via a csv file and the notes in Column z needs to create a new note on the custom object, Exhibitor Details, Thoughts?
public with sharing class ExhibhitorListImportController {
    
    private String[] csvFileLines;
    list<Campaign> camlst = new list<Campaign>();
    public List<Campaign_Market_Sales_Plan__c> CMPlist;
    public Exhibitor_List__c objExhibitor {get;set;}
    Public Campaign objCampaign;

    public Attachment attachment {
        get {
            if(attachment == null)
                attachment = new Attachment();
            return attachment;
        }
        set;
    }
    /*public exhibhitorListImportController(){
        csvFileLines = new String[]{};
        VElist = New List<Exhibitor_List__c>(); 
    }*/
    
    public ExhibhitorListImportController(ApexPages.StandardController stdController) {
        
        string campaignId = ApexPages.currentPage().getParameters().get('campaignId');
        csvFileLines = new String[]{}; 
        CMPlist = new List<Campaign_Market_Sales_Plan__c>();
        objExhibitor = new  Exhibitor_List__c(Campaign__c = (Id)campaignId);
        objCampaign = new Campaign() ;
        
    } 
    
    public PageReference importCSVFile(){
        Blob csvFileBody;
        string csvAsString;

        try{
            csvAsString = attachment.Name;
            csvFileBody = attachment.Body;
            
            if(string.isBlank(csvAsString)){
                ApexPages.Message errormsg = new ApexPages.Message(ApexPages.severity.ERROR,'Please select csv file to upload');
                ApexPages.addMessage(errormsg);
                return null;
            }
            csvAsString = csvFileBody.toString(); 
            
            list<list<string>> lstStringRowsoflstColumns = new list<list<string>>();
            lstStringRowsoflstColumns = parseCSV(csvAsString);
            list<Exhibitor_List__c> VElist =New List<Exhibitor_List__c>();
            map<string, integer> mapduplicateKeyIntergerCount = new map<string, integer>();
            //system.debug('##--lstStringRowsoflstColumns.size(): '+ lstStringRowsoflstColumns.size());
            set<string> setUniqueKeys = new set<string>();
            Campaign_Market_Sales_Plan__c camplan = new Campaign_Market_Sales_Plan__c();
            camplan = retrieveExhibitorCampaignPlan(objExhibitor.Campaign__c); //new Campaign_Market_Sales_Plan__c();
            camplan.ListUploaded_to_Salesforce__c = TRUE;
            camplan.VEL_Import_Status__c = 'Upload Complete';
            camplan.VEL_Uploaded_By__c = Userinfo.getUserId();
            for(Integer i=1;i<lstStringRowsoflstColumns.size();i++){
                
                list<string> csvRecordData = lstStringRowsoflstColumns[i];
                system.debug('##--csvRecordData: '+ csvRecordData); 
                Exhibitor_List__c velObj = new Exhibitor_List__c() ; 
                
                if(!string.isBlank(csvRecordData[21])){
                    velObj.Company__c = csvRecordData[21];
                }
                if(csvRecordData.size() > 25 && !string.isBlank(csvRecordData[25])){
                    velObj.Notes__c = csvRecordData[25];
                }
                if(csvRecordData.size() > 26 && !string.isBlank(csvRecordData[26])){
                    velObj.Prospect_Owner__c = csvRecordData[26];
                }
                if(csvRecordData.size() > 27 && !string.isBlank(csvRecordData[27])){
                    velObj.Status__c = csvRecordData[27];
                }
                velObj.Address__c = csvRecordData[1] + '\r\n'+ csvRecordData[2] ;
                velObj.City__c = csvRecordData[3];
                velObj.State__c = csvRecordData[4];
                velObj.Postal_Code__c = csvRecordData[5];
                velObj.Country__c = csvRecordData[6];
                velObj.First_Name__c = csvRecordData[7];
                velObj.Last_Name__c = csvRecordData[8];
                velObj.Job_Title__c = csvRecordData[9];
                velObj.Name = csvRecordData[8] +', '+ csvRecordData[7];
                velObj.Phone_Number__c = csvRecordData[10];
                velObj.Email_Address__c = csvRecordData[15];
                velObj.Fax__c = csvRecordData[13];
                velObj.Booth_Stand_Number__c = csvRecordData[16];
                velObj.Booth_Stand_Width__c = csvRecordData[17];
                velObj.Booth_Stand_Depth__c = csvRecordData[18];
                velObj.Booth_Type__c = csvRecordData[19]; 
                velObj.Campaign__c = objExhibitor.Campaign__c;
                velObj.Status__c = 'New';
                string uniqueKey = (objExhibitor.Campaign__c+'_'+ velObj.Email_Address__c + '_' + velObj.Company__c).toLowerCase(); 
                system.debug('##--uniqueKey: '+ uniqueKey);
                if(setUniqueKeys.contains(uniqueKey)){ 
                    if(mapduplicateKeyIntergerCount.get(uniqueKey) == null){
                        mapduplicateKeyIntergerCount.put(uniqueKey, 1);
                        uniqueKey = uniqueKey+'_dup1';
                    }else{
                        integer counter = mapduplicateKeyIntergerCount.get(uniqueKey);
                        mapduplicateKeyIntergerCount.put(uniqueKey, counter+1);
                        uniqueKey = uniqueKey+'_dup'+ counter+1;
                    }
                    //velObj.Duplicate__c = TRUE; 
                    //uniqueKey = uniqueKey+'_dup';
                    velObj.Unique_Key__c = uniqueKey; 
                    setUniqueKeys.add(uniqueKey);
                }else{
                    velObj.Unique_Key__c = uniqueKey;
                    setUniqueKeys.add(uniqueKey);
                } 
                VElist.add(velObj);   
            }// end of for-each
             
            system.debug('##--VElist: '+ VElist);
            if(VElist.size() > 0){ 
                upsert VElist Unique_Key__c;  
                if(!string.isBlank(camplan.Id)){
                    update camplan;
                } 
            }
            BatchMatchContactsOrLeads batch = new BatchMatchContactsOrLeads(objExhibitor.Campaign__c);
            database.executeBatch(batch, 1); 
            objExhibitor.Campaign__c = null;  
            ApexPages.Message errorMessage = new ApexPages.Message(ApexPages.severity.Info, 'Succesfully Uploaded '+ (lstStringRowsoflstColumns.size()-1)+' rows. You will be notified once the jobs are completed.');
            ApexPages.addMessage(errorMessage);
            attachment = null;
            
       }catch (Exception e) {
            system.debug('##--: '+ e.getLineNumber());
            ApexPages.Message errorMessage = new ApexPages.Message(ApexPages.severity.ERROR,'Error : ' + e.getmessage() + e.getLineNumber());
            ApexPages.addMessage(errorMessage);
            return null; 
        }
        PageReference objPageReference = new PageReference('/apex/ExhibhitorListImport');
        return objPageReference;
    }
    // Return a 2D String array with headers on first row
    public static List<List<String>> parseCSV(String contents) {
        
        System.debug('## parseCSV called with contents = ' + contents);
        // Determine if it is split by newLine(\n) or return carriage(\r) or a \r\nn
        Boolean hasNewLine = false;
        Boolean hasCarrReturn = false;
        
        //First check for a \r\n char
        if(contents.contains('\r\n')) {
            System.debug('## Contains at least one \'\\r\\n\' character');
            hasNewLine = true;
            hasCarrReturn = false;
        }
        else {
            //If not then check for either a /r or /n
            if(contents.contains('\n')) {
                System.debug('## Contains at least one newline character');
                hasNewLine = true;
            }
            if(contents.contains('\r')) {
                System.debug('## Contains at least one carriage return character');
                hasCarrReturn = true;
            }
        }
        
        List<List<String>> allFields = new List<List<String>>();
        if(hasNewLine && hasCarrReturn) {
            addError('The file contains both newline and carriage returns');
        }
        else {
            
            String splitBy = '\n';
            if(hasCarrReturn) {
                splitBy = '\r';
            }
            
            // replace instances where a double quote begins a field containing a comma
            // in this case you get a double quote followed by a doubled double quote
            // do this for beginning and end of a field
            contents = contents.replaceAll(',"""',',"DBLQT').replaceall('""",','DBLQT",');
            // now replace all remaining double quotes - we do this so that we can reconstruct
            // fields with commas inside assuming they begin and end with a double quote
            contents = contents.replaceAll('""','DBLQT');
            // we are not attempting to handle fields with a newline inside of them
            // so, split on newline to get the spreadsheet rows
            List<String> lines = new List<String>();
            try {
                lines = contents.split(splitBy);
            } catch (System.ListException e) {
                System.debug('## Limits exceeded?' + e.getMessage());
            }
            System.debug('## About to check ' + lines.size() + ' lines...');
            Integer num = 0;
            
            for(String line : lines) {
                //System.debug('## Parsing line: ' + line);
                // Deal with lines where 
                //if (line.replaceAll(',','').trim().length() == 0) continue; 
                
                
                // TODO: Deal with lines where all fields empty. Currently splits into an empty array.
                
                List<String> fields = line.split(',', -1);  
                List<String> cleanFields = new List<String>();
                String compositeField;
                Boolean makeCompositeField = false;
                //System.debug('## About to read ' + fields.size() + ' fields...');
                for(String field : fields) {
                    if (field.startsWith('"') && field.endsWith('"')) {
                        cleanFields.add(field.replaceAll('DBLQT','"'));
                    } else if (field.startsWith('"')) {
                        makeCompositeField = true;
                        compositeField = field;
                    } else if (field.endsWith('"')) {
                        compositeField += ',' + field;
                        cleanFields.add(compositeField.replaceAll('DBLQT','"'));
                        makeCompositeField = false;
                    } else if (makeCompositeField) {
                        compositeField +=  ',' + field;
                    } else {
                        cleanFields.add(field.replaceAll('DBLQT','"'));
                    }
                }
                
                // Remove double quotes (if present) from start and end of each field
                List<String> noQuoteFields = new List<String>();
                for(String field : cleanFields) {
                    system.debug('##--field: '+ field);
                    if(field.startsWith('"') && field.endsWith('"') && field.length() > 1) {
                        field = field.substring(1, field.length() - 1);
                    }
                    noQuoteFields.add(field);
                }
                allFields.add(noQuoteFields);
            }
        }
        
        // Remove any rows before header row so that header row is first row:
        Integer headerRow = -1;
        for(Integer i=0; (i < allFields.size() && headerRow == -1) ; i++) {
            // Determine if all fields in this row are non-blank:
            List<String> row = allFields.get(i);
            Boolean isHeaderRow = true;
            
            for(String field : row) {
                if(field == '') { // field is blank
                    isHeaderRow = false;
                }
            }
            
            if(isHeaderRow) {
                headerRow = i;
            }
        }
                System.debug('## headerRow ' +headerRow);
        
        for(Integer i=0; i < headerRow; i++) {
            allFields.remove(0);
        }

        System.debug('## About to return ' + allFields.size() + ' lines...');
        return allFields;
    }
    
    public static Campaign_Market_Sales_Plan__c retrieveExhibitorCampaignPlan(id CampaignId){
        
        Campaign_Market_Sales_Plan__c objCampaignPlan = new Campaign_Market_Sales_Plan__c();
        
        list<Campaign_Market_Sales_Plan__c> lstExhibitorCampaignPlan = [SELECT Id, Campaign_Plan_Type__c FROM Campaign_Market_Sales_Plan__c
                                                                                WHERE Event_Campaign_Name__c =:CampaignId limit 100];
        if(lstExhibitorCampaignPlan.size() >0 ){ 
            objCampaignPlan = lstExhibitorCampaignPlan[0]; 
        }           
        return objCampaignPlan;
        
    }// end of retrieveExhibitorCampaignPlan
    
    public static void addError(String error) {
        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, error));
    }
}

 
SujitMandalSujitMandal
Below Code will work...
List<ContentVersion> addnotes = new List<ContentVersion>();
        for(Lead lds : Trigger.new){
            ContentVersion objCntNote = new ContentVersion();
            objNote.Title = 'Creation Note';
            objNote.PathOnClient = objNote.Title + '.snote';
            objNote.VersionData = Blob.valueOf(lds.Sales_Notes__c);
            objNote.FirstPublishLocationId = lds.Id;  // ParentId
            addnotes.add(objNote);
        }
    if(addnotes.size()>0){
        insert addnotes;
    }
Pavani BhasutkarPavani Bhasutkar
Hi Ryan Greene, 

facing issues when wrote a test class for the below code ! let me know if anyone can resolve the issue

Task Trigger : 
trigger cga_trigger_Task on Task (after insert,after update) { 

 if(Trigger.isAfter){ 
                if(Trigger.isInsert){
                cm_ctrl_utilities.createNotes(Trigger.new);
            }
            
            List<Task> lstTskDespChange = new List<Task>();
            for(Task tsk : Trigger.new){
                if(Trigger.isUpdate && (Trigger.oldMap.get(tsk.Id).Description != Trigger.newMap.get(tsk.Id).Description)){
                    lstTskDespChange.add(tsk);
                }
            }
            if(!lstTskDespChange.isEmpty()){
                cm_ctrl_utilities.createNotes(lstTskDespChange); 
            }
 }       
}

cm_ctrl_utilities (Apex class):
public without sharing class cm_ctrl_utilities {
public static void createNotes(List<Task> taskLst){
        List<ContentNote> cntNteLst = new List<ContentNote>();
        List<ContentDocumentLink> cdLnkLst = new List<ContentDocumentLink>();
        for(Task tsk : taskLst){
            ContentNote cntNte = new ContentNote();
            cntNte.Content = Blob.valueof(tsk.Description);
            cntNte.Title = tsk.Subject;
            cntNteLst.add(cntNte);
        }
        if(cntNteLst.size()>0){
            insert cntNteLst;
        }
        // to create a note related to case & task, when task is created.
        for(Task task : taskLst){
            for(ContentNote cntNte : cntNteLst){
                ContentDocumentLink clnk = new ContentDocumentLink();
                ContentDocumentLink clnk1 = new ContentDocumentLink();
                
                clnk.LinkedEntityId = task.WhatId;
                clnk.ContentDocumentId = cntNte.Id;
            
                clnk1.LinkedEntityId = task.Id;
                clnk1.ContentDocumentId = cntNte.Id;
            
                cdLnkLst.add(clnk);
                cdLnkLst.add(clnk1);
            }
        }
        if(cdLnkLst.size()>0){
            insert cdLnkLst;
            }
        }
    }
}

Testclass :

@isTest
public class cm_ctrl_utilities_TEST {
@isTest
    static void test1CreateNotes(){
        Test.startTest();
            Case obj = new Case();
            obj.Type = 'Case Management';
            obj.Origin = 'Fax';
            obj.Subject = 'cm_ctrl_utilities_TEST2';
            obj.Description = 'cm_ctrl_utilities_TEST2';
            insert obj;
            List<Task> lstTask = new List<Task>();
            
            for(Integer i=0;i<200;i++){
                Task tsk = new Task();
                tsk.Subject = 'testTask1';
                tsk.Description = 'cm_ctrl_utilities_TEST';
                tsk.WhatId = obj.Id;
                lstTask.add(tsk); 
            }
            
            insert lstTask;
            
              List<ContentDocumentLink> cntNtelst = new List<ContentDocumentLink>();
              cntNtelst = [SELECT Id,LinkedEntityId FROM ContentDocumentLink WHERE LinkedEntityId=:obj.Id];
              Test.stopTest();
             System.assertEquals(cntNtelst.size(), 200, 'Notes not equal to 200');
        }
    }
}


Not able insert multiple task at a time 
Ryan GreeneRyan Greene
I would try breaking out the Task creating loop. Setting up the Task list will try to insert all 200 at once, SF wont let you do this. First off, for testing do you really need 200 test Task records? If not I would create maybe 3 or 4, like this:
Task tsk1 = new Task();
tsk.Subject = 'testTask1';
tsk.Description = 'cm_ctrl_utilities_TEST';
tsk.WhatId = obj.Id;
insert tsk1;

Task tsk2 = new Task();
tsk.Subject = 'testTask2';
tsk.Description = 'cm_ctrl_utilities_TEST';
tsk.WhatId = obj.Id;
insert tsk2;

Task tsk3 = new Task();
tsk.Subject = 'testTask3';
tsk.Description = 'cm_ctrl_utilities_TEST';
tsk.WhatId = obj.Id;
insert tsk3;

If you do need 200 Tasks for testing, you maybe be able to put the entire Task creator in a loop like this: (I havent tried it, not sure if it works)
for(Integer i=0;i<200;i++){
Task tsk = new Task();
tsk.Subject = 'testTask'+i;
tsk.Description = 'cm_ctrl_utilities_TEST';
tsk.WhatId = obj.Id;
insert tsk;
}
Saiful Islam 1Saiful Islam 1
The following code will be helpful bulk records on trigger
 
trigger AddNote on Lead (after insert) {
    
    Map<Id, ContentNote> contentNoteMap = new Map<Id, ContentNote>();
    List<ContentDocumentLink> lnk = new List<ContentDocumentLink>();
    
    for(Lead lds : Trigger.new){
        ContentNote cnt = new ContentNote();
        cnt.Content = Blob.valueof(lds.Sales_Notes__c);
        cnt.Title = 'Creation Note';
        contentNoteMap.put(lds.Id, cnt);
    }
    
    if(contentNoteMap.values().size()>0){
        insert contentNoteMap.values();
    }

    for(String leadId: contentNoteMap.keySet()){

        ContentDocumentLink clnk = new ContentDocumentLink();
        clnk.LinkedEntityId = contentNoteMap.get(leadId).Id;
        clnk.ContentDocumentId = leadId;
        clnk.ShareType = 'I';
        lnk.add(clnk);

    }
    
    if(lnk.size()>0){
        insert lnk;
    }
}