Plugin Components

Timeline plugins are Lightning Web Components that can interact with and control the Timeline component. They use the Lightning Message Service to communicate with the Timeline component and can trigger various actions like refreshing the timeline, applying filters, and responding to timeline events.

Architecture

Timeline plugins work through a bidirectional communication system:

  1. Outbound Events: Plugins can send publish events to the TimelineEvent__c message channel
  2. Inbound Events: Plugins can subscribe to Timeline events via the TimelineEvent__c message channel
  3. Context Sharing: Plugins can share context data with the Timeline component

Message Channel

The Timeline system uses the TimelineEvent__c Lightning Message Channel for communication between components. The message structure includes:

  • recordId: The record ID that the action is executed on
  • parentId: The record ID of the record on which the timeline component is shown
  • setupName: The API name of the timeline setup record
  • context: A context object containing extra information for the action
  • eventType: The type of event being triggered

Plugin Structure

Basic Plugin Setup

A timeline plugin must extend LightningElement and implement the message channel subscription:

import {LightningElement, api, wire} from "lwc";
import {publish, subscribe, unsubscribe, MessageContext} from "lightning/messageService";
import timelineEvent from "@salesforce/messageChannel/afp__TimelineEvent__c";

export default class TimelinePlugin extends LightningElement {
	@api recordId;
	@api objectApiName;

	@wire(MessageContext)
	messageContext;

	subscription = null;

	connectedCallback() {
		this.subscribeToMessageChannel();
		this.setInitialContext();
	}

	disconnectedCallback() {
		this.unsubscribeToMessageChannel();
	}

	subscribeToMessageChannel() {
		if (!this.subscription) {
			this.subscription = subscribe(this.messageContext, timelineEvent, (message) => this.handleMessage(message));
		}
	}

	unsubscribeToMessageChannel() {
		unsubscribe(this.subscription);
		this.subscription = null;
	}

	handleMessage(message) {
		// Handle incoming timeline events
		console.log("Timeline action received:", message);
	}
}

Component Metadata

The plugin component must be exposed in its metadata file:

<?xml version="1.0" encoding="UTF-8" ?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>62.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Timeline Plugin</masterLabel>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
</LightningComponentBundle>

Plugin Events

Outbound Events (Plugin → Timeline)

Plugins can publish messages to the Timeline component using the Lightning Message Channel:

setcontext Event

Sets context data for the plugin:

setInitialContext() {
    publish(this.messageContext, timelineEvent, {
        eventType: "tln__setContext",
        context: {
            pluginName: "ExamplePlugin",
            initialized: new Date().toISOString(),
        },
    });
}

setfilter Event

Applies filters to the timeline:

applyFilter() {
    const filterDetails = {
        dateFilter: "all_time",     // Options: "all_time", "last_7_days", "next_7_days", "last_30_days", "custom"
        myItems: false,             // Show only current user's items
        minDate: null,              // Start date for custom filter
        maxDate: null,              // End date for custom filter
        allowedSetupItems: null,    // Array of allowed setup item names
    };

    publish(this.messageContext, timelineEvent, {
        eventType: "tln__setFilter",
        context: filterDetails,
    });
}

refresh Event

Triggers a timeline refresh:

refreshTimeline() {
    publish(this.messageContext, timelineEvent, {
        eventType: "tln__refresh",
    });
}

Inbound Events (Timeline → Plugin)

The Timeline component publishes various events that plugins can subscribe to:

System Events (prefixed with tln__)

  • tln__initialized: Fired when the Timeline component is initialized

Action Events

Timeline items can trigger custom action events based on their configuration. These events contain:

  • eventType: The type of action (e.g., "view_record", "edit_record")
  • recordId: The ID of the record being acted upon
  • parentId: The ID of the parent record
  • setupName: The timeline setup name
  • context: Additional context data

Outbound events (Plugin → Timeline)

  • tln__refresh: Can be used to manually refresh the timeline
  • tln__setFilter: Can be used to set the filter of the timeline
  • tln__setContext: Can be used to set a context object that will be shared with actions

Filter Options

When applying filters through the tln__setFilter event, use the following message structure:

{
	eventType: "tln__setFilter",
	context: {
		dateFilter: "custom",
		myItems: false,
		minDate: new Date(2025, 0, 1), // In javascript January is month 0
		maxDate: null,
		allowedSetupItems: ["MyProvider"],
	},
}

You can use the following options:

Date Filters
  • "all_time": Show all timeline items regardless of date
  • "last_7_days": Show items from the past 7 days
  • "next_7_days": Show items for the next 7 days
  • "last_30_days": Show items from the past 30 days
  • "custom": Use custom date range with minDate and maxDate

minDate and maxDate are ignored when dateFilter is not "custom".

User Filters
  • myItems: true: Show only items assigned to the current user
  • myItems: false: Show all items
Setup Item Filters
  • allowedSetupItems: ["Provider1", "Provider2"]: Show only items from specific providers. Use API names of TimelineSetupItems as values.
  • allowedSetupItems: null: Show items from all providers

Example Implementation

Here's a complete example of a timeline plugin with filtering capabilities:

import {LightningElement, api, wire} from "lwc";
import {ShowToastEvent} from "lightning/platformShowToastEvent";
import {publish, subscribe, unsubscribe, MessageContext} from "lightning/messageService";
import timelineEvent from "@salesforce/messageChannel/afp__TimelineEvent__c";

export default class ExampleTimelinePlugin extends LightningElement {
	@api recordId;
	@api objectApiName;
	@api setupName;

	@wire(MessageContext)
	messageContext;

	subscription = null;
	selectedFilter = "all";

	filterOptions = [
		{label: "All", value: "all"},
		{label: "My Items", value: "my_items"},
		{label: "Recent", value: "recent"},
	];

	connectedCallback() {
		this.subscribeToMessageChannel();
		this.setInitialContext();
	}

	disconnectedCallback() {
		this.unsubscribeToMessageChannel();
	}

	subscribeToMessageChannel() {
		if (!this.subscription) {
			this.subscription = subscribe(this.messageContext, timelineEvent, (message) => this.handleMessage(message));
		}
	}

	unsubscribeToMessageChannel() {
		unsubscribe(this.subscription);
		this.subscription = null;
	}

	setInitialContext() {
		publish(this.messageContext, timelineEvent, {
			eventType: "tln__setContext",
			context: {
				pluginName: "ExamplePlugin",
				initialized: new Date().toISOString(),
			},
		});
	}

	handleFilterChange(event) {
		this.selectedFilter = event.target.value;
		this.applyFilter();
	}

	applyFilter() {
		const filterDetails = {
			dateFilter: "all_time",
			myItems: false,
			minDate: null,
			maxDate: null,
			allowedSetupItems: null,
		};

		if (this.selectedFilter === "my_items") {
			filterDetails.myItems = true;
		} else if (this.selectedFilter === "recent") {
			filterDetails.dateFilter = "custom";
			filterDetails.minDate = new Date(Date.now() - 864000000); // 10 days ago
		}

		publish(this.messageContext, timelineEvent, {
			eventType: "tln__setFilter",
			context: filterDetails,
		});
	}

	refreshTimeline() {
		publish(this.messageContext, timelineEvent, {
			eventType: "tln__refresh",
		});
		this.showToast("Success", "Timeline refreshed", "success");
	}

	handleMessage(message) {
		if (message.eventType === "tln__initialized") {
			this.setInitialContext();
			return;
		}
		console.log("Timeline action received:", message);

		this.showToast("Timeline Action", `Action ${message.eventType} triggered for record ${message.recordId}`, "info");
	}

	showToast(title, message, variant) {
		this.dispatchEvent(
			new ShowToastEvent({
				title,
				message,
				variant,
			})
		);
	}
}

HTML Template Example

<template>
	<div class="slds-card slds-var-p-around_small">
		<div class="slds-card__header">
			<h2 class="slds-card__header-title">Timeline Controls</h2>
		</div>
		<div class="slds-card__body">
			<lightning-combobox
				name="filter"
				label="Filter Timeline"
				value="{selectedFilter}"
				placeholder="Select Filter"
				options="{filterOptions}"
				onchange="{handleFilterChange}"
			>
			</lightning-combobox>

			<lightning-button
				class="slds-var-m-top_small"
				label="Refresh Timeline"
				onclick="{refreshTimeline}"
				variant="brand"
			>
			</lightning-button>
		</div>
	</div>
</template>

Deployment

To deploy a timeline plugin:

  1. Create the Lightning Web Component with the appropriate structure
  2. Ensure the component metadata exposes it for the Lightning Record Page
  3. Add the plugin component to your Lightning Record Page layout
  4. The plugin will automatically communicate with any Timeline components on the same page

Best Practices

  1. Always subscribe and unsubscribe: Use connectedCallback() and disconnectedCallback() to manage message channel subscriptions
  2. Handle system events: Filter out system events (prefixed with tln__) in your message handler
  3. Set initial context: Since the order in which lightning web components are created on a page is not guaranteed, call setInitialContext() in connectedCallback() and after receiving tln__initialized. The first in case the timeline is initialized before the plugin and the second in case the plugin is initialized before the timeline.