When two Accounts, Contacts, or Leads are merged together then the related records of the merged (deleted) record get re-parented to the surviving (master) record. This is great as we don’t lose sight of the related records and all the data carries over to the master record.

However, as of this writing, Salesforce does not preserve Chatter Files during standard merge operation of Accounts, Contacts, or Leads. Chatter Files include documents uploaded via Chatter or documents that users add to a record via the Files related list, which is replacement for the classic Notes & Attachments related list.

After a merge operation, the Chatter Files related to the merged (deleted) record become orphaned, they do not automatically become shared to the surviving (master) record.

Inspired by Gorav Seth‘s complaint of this on Success Community, I developed simple triggers to demonstrate how to preserve and carry over the Chatter Files to the master record post-merge.


You can get the apex trigger code from my GitHub repo at https://github.com/DouglasCAyers/sfdc-preserve-chatter-files-on-merge

You can contribute to our struggle by voting on the IdeaExchange for Merge Chatter Feed as Part of the Record Merging Process.

How Does this Code Work?

When the standard merge operation occurs, the record being merged into the surviving record (aka master record) is deleted. This fires two events that our Apex triggers can listen for: before delete and after delete.

During the before delete trigger event we learn which records are being deleted. But since the records haven’t been deleted yet then we have a chance to query related records, like Chatter Files, to know if the records being deleted had any documents shared to them. It is during this phase of trigger execution that our code takes note of which ContentDocuments (API Object Name of Chatter Files) are being left behind. We then use a trick with trigger executions to store this data in a static variable so that we can reference them again in the next trigger event, after delete, since at that point the records will have been deleted and any relationships between them and the Chatter Files will have been severed leaving the Files orphaned.

// account id => list of content documents
private static Map<ID, List<ContentDocumentLink>> contentDocumentLinksMap = new Map<ID, List<ContentDocumentLink>>();
public void handleBeforeDelete( List<Account> accounts ) {

  Set<ID> accountIds = new Set<ID>();
  for ( Acccount acct : accounts ) {
    accountIds.add( acct.id );

  for ( ContentDocumentLink cdl : [
      id, linkedEntityId, contentDocumentId, shareType
      linkedEntityId IN :accountIds ] ) {

    List<ContentDocumentLink> cdls = contentDocumentLinksMap.get( cdl.linkedEntityId );
    if ( cdls == null ) {
      cdls = new List<ContentDocumentLink>();
    cdls.add( cdl );

    contentDocumentLinksMap.put( cdl.linkedEntityId, cdls );




During the after delete trigger event Salesforce has now populated the MasterRecordId field on the Account, Contact, or Lead records that were deleted due to the standard merge operation. We now know who the master record is that we need to re-parent the Chatter Files to so that they don’t just sit there orphaned. In this code section, we re-use the static map of ContentDocumentLinks we built during the before delete trigger event to create a list of new ContentDocumentLinks whose LinkedEntityId field will now point to the MasterRecordId field value of each merged record.

public void handleAfterDelete( List<Account> accounts ) {

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

  for ( Account acct : accounts ) {
    if ( String.isNotBlank( acct.masterRecordId ) {
      List<ContentDocumentLink> cdls = contentDocumentLinksMap.get( acct.id );
      if ( cdls != null ) {
        for ( ContentDocumentLink cdl : cdls ) {
          contentDocumentLinksToInsert.add( new ContentDocumentLink(
            linkedEntityId = acct.masterRecordId,
            contentDocumentId = cdl.contentDocumentId,
            shareType = cdl.shareType

  if ( contentDocumentLinksToInsert.size() > 0 ) {
    insert contentDocumentLinksToInsert;