Requirement: If a note is added to either an account, contact, opportunity, task and event for particular record types and the current user is not an administrator and not a user mentioned in system label, add error!

Seems like a simple and fair enough requirement doesn't it? However, I cannot for the life of me figure out how to acheive this. I have read on Salesforce SOAP API Developer Guide -  https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_contentnote.htm and in the url it says 'ContentNote is built on ContentVersion, and so it has many of the same usages.' so I figured perhaps a trigger on ContentVersion might do the trick but I was wrong.

I am wondering if someone could help me please? When I delete a note the below is not firing!

Could I write a trigger on Note object instead?

Below is my trigger, System Labels and helper class:

System Labels: NL_Record_Type_Labels = 'Lead','NL Lead','Account','NL Account','Contact','NL Contact','Opportunity','NL Opportunity','Task','NL Task','Event','NL Event'

NL_Sales_Default_User = 0059EXXXXXXXXXX {user id of a user}

trigger ContentVersionTrigger on ContentVersion (before delete) {
    if(trigger.isBefore && trigger.isDelete){
        System.debug('In ContentVersionTrigger');

Helper Class

public class ContentNoteCustomClass {
    public static void preventDeletion(list<ContentVersion> conVersion){
        Id UserAllowedToDelete = System.Label.NL_Sales_Default_User; 
        Set<id> conDoc = new Set<id>();
        Set<id> NL_Notes = new Set<id>();
        for(ContentVersion cv: conVersion){
            if(cv.filetype == 'SNOTE'){
                System.debug('S Note added with content doc id '+cv.ContentDocumentId);
        List<ContentDocumentLink> conDocLink = [Select id, LinkedEntityId, LinkedEntity.RecordTypeId, ContentDocumentId from ContentDocumentLink where ContentDocumentId in: conDoc];
        System.debug('conDocLink '+conDocLink);

        Map<id,String> NLRecordTypes = new Map<id,String>();

        String[] objRecTypeArr = System.Label.NL_Record_Type_Labels.split(',');

        for(Integer i=1;i<objRecTypeArr.size();i+2){
            NLRecordTypes.put(getRecordTypeIdForObject(objRecTypeArr[i-1], objRecTypeArr[i]),objRecTypeArr[i]);
        System.debug('NLRecordTypes map: '+NLRecordTypes);

        for(ContentDocumentLink cdl: conDocLink){
            if(NLRecordTypes.containsKey(cdl.LinkedEntity.RecordTypeId) && UserInfo.getUserId().substring(0,15) <> UserAllowedToDelete && UserInfo.getProfileId().substring(0,15) <> '00e20000000pwXc'){
                System.debug('Added to NL_Notes '+NL_Notes);

        for(ContentVersion cv: conVersion){
            if(cv.filetype == 'SNOTE' && NL_Notes.contains(cv.ContentDocumentId)){
                System.debug('Will throw error');
                cv.addError('Only default sales user can delete notes');


    private static Id getRecordTypeIdForObject(String objName, String recTypeName) {
    return ((SObject)Type.forName(objName).newInstance())
  • See @CharlesT's suggestion of using a trigger on ContentDocumentLink. I've used triggers on this and it's worked for me in the past. – Brian Miller Apr 23 '19 at 19:06

Object model reminder: https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_erd_contentnote.htm

Notice that while it does use ContentVersion and ContentDocumentLink it does NOT use ContentDocument.

Also you might want to consider putting the trigger on ContentDocumentLink because otherwise you allow a possible scenario where a non-admin user unlinks the Note first from the record and then deletes it.

Finally I would advise against hard-coding any IDs into the Apex.

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.