Activity Timeline
How to put the Flourish activity timeline on a record page, read it, and configure what it shows.
Overview
The activity timeline puts everything that has happened on a record into one stream, newest first: Chatter posts, email, tasks, logged calls, events, field changes and files, alongside records from the other Flourish products you have installed. You can filter it, search it, open any entry in place, and post or create from the top of it without leaving the page.
It goes on any record page and on Experience Cloud pages, and it works the moment you drop it on, before anyone has configured anything.
It never shows a reader more than they could already see. Every query runs as the person looking at the page, so record access and field permissions decide what appears. Two people on the same record can see different timelines, and that is correct.
Adding it to a page
In the Lightning App Builder, drag Flourish Core: Activity Timeline onto a record page. Three properties are worth setting:
| Property | Does |
|---|---|
| Density | Comfortable gives roomier rows and a one-line preview of each entry. Compact drops the preview and tightens the rows. |
| Records Per Page | How many entries each load brings back. 30 by default, between 5 and 100. |
| Custom Style (JSON) | Height overrides, if the default sizing does not suit the column you put it in. |
Custom Style takes a small JSON object scoped by the part of the component you are sizing. Values are ordinary CSS lengths such as 620px, 40rem or 60vh, and a bare number is read as pixels:
{
"timeline": { "minHeight": "200px", "maxHeight": "50vh" },
"row": { "minHeight": "60px" },
"detail": { "maxHeight": "400px" }
}
Leave it blank for the built-in sizing. Anything the component cannot make sense of is reported in a notice on the component itself rather than ignored quietly.
The component sizes itself to the space it is given, not to the browser window, so the same timeline reads properly in a narrow sidebar on a wide screen and in a full-width tab.
What it shows
Seven kinds of entry are on by default:
| Type | What appears |
|---|---|
| Chatter | Posts on the record's feed |
| Email related to the record. On a person record it also picks up mail the record was a recipient of. | |
| Task | Open and completed tasks, except those a logged call or an email produced |
| Other Activity | Logged calls |
| Event | Calendar events, including ones still to come |
| Field Change | Tracked field history, with the old and new values |
| File | Files attached to the record, one entry per version |
Files show one entry per version rather than one per file, so uploading a document and later replacing it are two entries. The first is marked Added and each later one New version, with whatever reason was given for the change.
Anything from another Flourish product is added on top of these, as its own type with its own color, and is set up in configuration below.
If one type cannot be read for some reason, only that type drops out. The rest of the stream still renders, and the component says what was missed.
Reading the timeline
Entries are grouped under Upcoming, Today, Yesterday, Earlier this week, and then by month. Each heading collapses the group under it. Upcoming always leads, whichever way the stream is sorted, because events in the future belong above today rather than at the bottom.
Filtering and searching
A row of chips across the top, one per type, with a count beside each. Click a chip to switch that type off, click it again to bring it back. A chip's count keeps telling you how many entries of that type there are even while it is switched off, so you can see what turning it back on would bring.
Hold F and click a chip to show only that type. The chips change color while F is held so you can see the click will do something different, and F-clicking the type you already singled out puts everything back. It is its own undo.
Type in Search this timeline to narrow the stream to matching entries. The counts on the chips narrow with it, so under a search each chip tells you how many of that type matched.
Clear filters resets everything you have switched off.
Your chip choices and collapsed groups are remembered per object, so if you never want to see field changes on contacts, you switch them off once. Search text is deliberately not remembered: coming back to a filtered page with a query you do not remember typing looks like missing data.
Opening an entry
Click a row to expand it. The full body opens underneath, with the details laid out in a grid beside it. More than one row can be open at once. Expand all opens everything on the page and turns into Collapse all.
An expanded row gets a small Open Record link at the end of its title, which takes you to the record itself.
The expanded view never repeats what the collapsed row already told you. If the row header names who posted something, the detail does not say it again.
Links to records come through as names. Where a body mentions another record, by link or by ID, it renders as the record's name and is clickable. If the reader cannot see that record, the text is left exactly as it was written: no pill, no placeholder, and no dead link.
Sorting and refreshing
The sort button flips between Newest first and Oldest first, and the date groups reverse with it. Refresh reloads the stream.
You rarely need Refresh, because the timeline picks up new and changed entries on its own every couple of minutes. It does that without disturbing you: rows you have open stay open, your filters and search are untouched, and your place on the page is held even when new entries land above where you are reading. Anything new arrives with a marker down its edge that fades out on its own. It also waits rather than interrupting, so nothing reloads while you are part-way through writing a post.
Posting and creating from the timeline
Buttons at the top of the timeline open a composer over the stream. What is on offer is set in configuration, and typically includes posting to Chatter and creating a task, an event or a call record.
On a narrow layout the buttons collapse into a menu, and on a phone they become a scrolling row.
Posting to Chatter
Post opens a box for an update. Type @ and a couple of letters to mention a person or a group; pick from the list and the name is inserted where you were typing. If you edit a mention after inserting it, it posts as ordinary text rather than a mention, so what the box shows is what goes out.
Attach files adds files to the post. Each one is attached to the record as it uploads, which means abandoning the post still leaves the file on the record, where the File entries will show it.
Creating a record
Every other action opens the fields that action normally carries, in a panel the timeline draws itself. Your own field permissions, record types and validation rules apply exactly as they would anywhere else, so you never see a field you cannot fill in or a picklist value you cannot pick. On a task or an event, the record you are on is filled in for you.
Saving reloads the top of the stream so the new record appears in its proper place.
Configuring it
The timeline configuration is a single block of JSON in the Timeline Config field on the Flourish Core Setting record described in Setup. One block covers every object.
Leave it blank and every record page gets the seven standard types with the default settings. You only write configuration to change something: to add a type, remove one, rename or recolor one, or offer actions in the composer.
The shape
{
"schemaVersion": 1,
"global": { "pageSize": 30, "density": "comfortable" },
"objects": {
"Contact": {
"sources": [ ],
"actions": [ ]
}
}
}
global sets defaults for every object. Each entry under objects is keyed by the object's API name and can override any of them for that object alone. Object names are matched without regard to case. An object you have not named gets the defaults.
When you list sources for an object, that list is the whole list. It is not merged with the defaults, so a type you leave out is left out. That is what lets you switch one off.
Settings
These can go in global, in an object's entry, or both:
| Setting | Does |
|---|---|
pageSize | Entries per load. 30 by default, capped at 100. |
density | comfortable or compact |
newestFirst | true by default |
showPreview | Whether rows carry a one-line preview. true by default. |
previewLength | How long that preview is, in characters. 240 by default, held between 40 and 500. |
An object entry also takes "enabled": false, which turns the timeline off for that object entirely.
Sources
Each entry in sources is one type of thing in the stream and one chip in the filter row:
| Key | Means |
|---|---|
key | An identifier for the source, unique within the object. Required. |
type | What it reads. One of FeedItem, EmailMessage, Task, LoggedCall, Event, History, Files, or Custom. |
label | What the chip and the row say |
color | The chip dot and the row's accent color |
icon | A Salesforce utility icon name, for example utility:email |
enabled | false leaves the source configured but switched off |
The seven standard sources look like this:
"sources": [
{ "key": "chatter", "type": "FeedItem", "label": "Chatter", "color": "#65ccb8", "icon": "utility:chat" },
{ "key": "email", "type": "EmailMessage", "label": "Email", "color": "#0d9dda", "icon": "utility:email" },
{ "key": "task", "type": "Task", "label": "Task", "color": "#4bc076", "icon": "utility:task" },
{ "key": "call", "type": "LoggedCall", "label": "Other Activity", "color": "#48c3cc", "icon": "utility:call" },
{ "key": "event", "type": "Event", "label": "Event", "color": "#eb7092", "icon": "utility:event" },
{ "key": "files", "type": "Files", "label": "File", "color": "#e8a33d", "icon": "utility:file" },
{ "key": "history", "type": "History", "label": "Field Change", "color": "#8199af", "icon": "utility:change_record_type" }
]
Chips are shown in alphabetical order by label, not in the order you write them here. A filter row is scanned by name.
Email options
An EmailMessage source takes two extra keys.
relatedFields lets one email appear on more than one record. Salesforce relates an email to a single record, so a product that needs it on several carries the extra IDs in columns of its own. Rapport does exactly that, and naming its columns here is what makes a captured email show up on all of the records it concerns:
{ "key": "email", "type": "EmailMessage", "label": "Email",
"relatedFields": ["toflourish__Rap_Addt_Related_1__c",
"toflourish__Rap_Addt_Related_2__c",
"toflourish__Rap_Addt_Related_3__c"] }
Each name is checked against your org before it is used, so an org without Rapport can carry the same configuration and simply contributes nothing.
collapseDuplicates deals with one email reaching several mailboxes you are capturing. Each mailbox records its own copy, so the same message can appear two or three times. Set it to true and they collapse into a single entry, which merges every recipient the copies knew about and says how many mailboxes saw it. It is off by default, because whether two records are the same email is a judgement about your own mail rather than something to decide for everyone.
Records from other Flourish products
A Custom source pulls records from another package into the stream. Nothing has to be installed for the configuration to be valid: an object your org does not have drops out of the stream and out of the chip row on its own.
{ "key": "submissions", "type": "Custom", "label": "Submission",
"color": "#9050e9", "icon": "utility:form",
"sobject": "Some_Record__c",
"relationshipField": "Contact__c",
"dateField": "CreatedDate",
"titleField": "Name",
"bodyField": "Summary__c",
"namespacePill": "Chronicle",
"fields": ["Status__c", "Submitted_On__c"] }
Use the API names as they appear in your own org, prefix and all. Every one of them is checked against the object before it is used.
| Key | Means |
|---|---|
sobject | The object to read. Required. |
relationshipField | The field on that object pointing back at the record the page is on. Required. |
dateField | What puts the entry in the stream. Defaults to the created date. |
titleField | What the row is called. Defaults to the record's name. |
bodyField | The text that opens when the row is expanded |
fields | Fields to list in the expanded detail, with their own labels and formatting |
namespacePill | A small badge on the row saying which product the entry came from |
Actions
actions is what the composer offers at the top of the timeline:
"actions": [
{ "apiName": "Chatter", "label": "Post", "type": "Chatter",
"color": "#65ccb8", "icon": "utility:chat" },
{ "apiName": "NewTask", "label": "New Task", "type": "Create", "targetObject": "Task",
"color": "#4bc076", "icon": "utility:task",
"fields": ["Subject", "WhoId", "WhatId", "ActivityDate", "Status", "Priority", "Description"] }
]
| Key | Means |
|---|---|
apiName | The quick action this came from |
label | The button text |
type | Chatter for the post composer, otherwise the action's own type |
targetObject | What gets created |
fields | Which fields the composer shows, in this order |
relateField | The field to fill in with the record the page is on |
color, icon | How the button looks |
enabled | false leaves it configured but hidden |
An action whose object is missing, or that a reader cannot create, is dropped for that reader rather than opening onto a form that cannot save. The same goes for individual fields, which is why one configured action serves everybody: each person's own access narrows it.
Saving configuration
Paste the JSON into Timeline Config on the Flourish Core Setting record and save.
Saving custom metadata is processed in the background, so give it a few seconds and reload before deciding a change did not take. If the JSON cannot be read at all, the timeline says so on the page and falls back to the standard types, so a bad paste never leaves a blank record page.