I like using Chatter Files for Attachments because they are prettier and searchable. The downside is that, when you add one to an Opporutunity, you don't see it as an Attachment on the Account like an old-skool Attachment would.

So I am working on a trigger where, if a user posts a Chatter File to an Opportunity, it also links it up to the Account. Sample code is below. I am successfully checking for it being a Chatter File (ContentPost) and only running when linked to an Opportunity, but the trigger runs whether I am adding a new file and also if I am associating an existing file to an Opportunity.

Any ideas how to determine whether the ContentPost is using a new Chatter File or an Existing one?

for (FeedItem f: fis){

    String parentIdString = String.valueOf(f.parentId);

    if (parentIdString.startsWith(Opportunity.sObjectType.getDescribe().getKeyPrefix()) && f.type == 'ContentPost'){
        Opportunity o = [select id, accountId from Opportunity where id = :f.ParentId limit 1][0];
        if(o.AccountId != null){
            FeedItem newFI = new FeedItem();
                newFI.Type = 'ContentPost';
                newFI.RelatedRecordId = f.RelatedRecordId;
                newFI.ParentId = o.AccountId;
                newFI.title = f.title;
            insert newFI;



NOTE: I am already aware of the issue with querying and DML in the loop. I will fix that later. I am mostly concerned with getting the scenario correctly handled.

Thanks, StackExchange!

4 Answers 4


Are you writing your trigger on a FeedItem? If so, you should be able to leverage the after insert parameter to only have the trigger get invoked on creation.

trigger FeedItem_afterInsert on FeedItem (after insert)
    //define list of FeedItems connected to Account to create
    List<FeedItem> feedItemsToInsert = new List<FeedItem>();
    for (FeedItem f : [SELECT Id, RelatedRecordId FROM FeedItem WHERE Id =: Trigger.new])
        //check if Id record ID matches an opportunity. If so, do work

    insert feedItemsToInsert;

I figured it out and blogged about it at my blog. The approach is to...

  • Create an AFTER INSERT trigger on FeedItem
  • Call an @future event that only works for new ContentPosts
  • The new file is linked to via the RelatedRecordId field on the FeedItem
  • Gather the IDs of other places this file should be linked be new ContentPosts.
  • Eliminate IDs that are already linked to the file
  • Create new FeedItems

The blog post has all the code. There are some other good best practices in there for wrapped @future calls in triggers and it uses the technique to prevent recursion of a trigger.


From my analysis, the best I can think of is to compare the following 3 dates either to be the same or maybe within a practical range of each other (1 second?):

  • Create Date on FeedItem
  • Create Date on ContentVersion (obtained by querying it using FeedItem.RelatedRecordId)
  • Create Date on ContentDocument (obtained by querying it using ContentVersion.ContentDocumentId)

Thoughts? Wish there was a flag I could check, but this seems to be the only way I can find.

  • Realistically, could only check ContentVersion. Maybe make sure IsLatest == true and/or VersionNumber == 1 for extra certainty.
    – hemmeter
    Aug 31, 2012 at 17:59
  • This is not a good solution. Went with another.
    – hemmeter
    Nov 7, 2012 at 16:56

I think I have come up with a better approach. However, there is one issue that I read was a bug getting fixed in Winter 13. We'll see about that.

The new approach is not to rely on Create Dates. That seemed a little hackish (in a bad way) anyway.

Instead, the new approach is to trigger on AFTER INSERT for FeedItem, use the RelatedRecordId value to query for ContentVersion and then use the ContentVersion.ContentDocumentId to query ContentDocumentLink. ContentDocumentLink is the table holding the linkage between a piece of Content and records.

The code will then use the records that content is already linked to and compare that with IDs we potentially want to link to (e.g. the account for an Opportunity, the contact and the account from a Case, etc.) For any IDs where there is no linkage, we'll create one by adding a new FeedItem record against that ID.

This approach seems clean. The only thing stopping it from working now is that the ContentVersion query never comes up with any records even though I know the records are there. I found some reference somewhere that this is a bug and is being fixed.

Thus, I'll wait for my Sandbox to be updated to Winter 13 and, when it is, I'll see if it's fixed. If so, I'll deploy after Winter 13 comes out.

  • I went with this approach, but will post in a new response.
    – hemmeter
    Nov 7, 2012 at 16:56

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.