In short
- Do the data work in Apex: query Tasks, custom activity records and external data, then return one sorted list.
- Sort by the date the activity happened, not the date it was created in Salesforce.
- Keep a pinned checkbox on each activity type, and show pinned items first.
- Render long text with its line breaks, and refresh the feed after a pin or a new activity.
Why build a custom activity feed in LWC
Reps want one place to see everything that happened with a person. Calls, texts, emails, notes, documents. In Salesforce that history is spread across Tasks, custom objects and systems outside Salesforce.
Task and Event cannot hold long text, so calls with transcripts go to a custom activity object. Notes from an external case system may live there too. A custom Lightning Web Component pulls them into one feed.
1. Build the Apex controller for the activity feed
Query Tasks
Select Tasks where WhatId is the opportunity or account, or WhoId is the lead or contact. Include subject, description, activity date, type and the pinned field.
Query custom activity records
Select calls, texts and synced notes from the custom activity object by their lookup to the same record.
Add external data
For data that stays outside Salesforce, call the external API from Apex through a named credential, or read records your sync already brought in.
Return one list
Map every item to one wrapper class: id, type, direction, title, body, date, pinned. Sort it in Apex, so the component only renders.
2. Sort the activity feed by the real date
Sort by when the activity happened. CreatedDate is when the record reached Salesforce, which is wrong for imported history and synced notes. Store the original date in its own field on import, and sort on that, newest first.
We have fixed feeds that showed activities out of order, and imports that did not keep the original dates. Both come down to which date field the feed uses.
3. Add pinned items and a pin toggle
- Add a Pinned checkbox to Task and to each custom activity object.
- Show pinned items in their own section at the top, then the rest by date.
- A pin icon on each card calls an Apex method that flips the checkbox.
- After the save, call refreshApex so the feed moves the item without a page reload.
- Backfill the field on historical records, or older items will not show the pin.
Talk through your project
A free call about your integration, your CRM or whatever you are stuck on. Honest advice, even if you never hire us. No obligation.
4. Render rich descriptions with line breaks
Call summaries and notes come in with line breaks. HTML drops them unless you tell it not to. Set white-space: pre-line on the body element in the component CSS, and the text keeps its paragraphs.
- Show the first lines of long text, with a "Show more" toggle.
- Mark each item inbound or outbound with an icon.
- Add a type icon: call, text, email, note, document.
- For calls, add a recording player. For documents, an inline preview.
5. Handle external data with care
- Decide what to sync into Salesforce and what to read live. Synced data is fast to show. Live data saves storage.
- Files can stay in external cloud storage and show in Salesforce through a preview component.
- Use an external ID on synced records, so a second sync updates instead of duplicating.
- Show a clear message when the external system does not answer, instead of an empty feed.
6. Put the activity feed on the record page
Expose the component for lightning__RecordPage in its meta file, and add it to the Lead, Contact, Account and Opportunity pages in Lightning App Builder. Test with the profiles your reps use, so field access matches what they see.
Want help with this?
Book a free call about your CRM, integrations or reporting, or map your systems first with our free tech stack diagram builder.
