Custom Providers

PRO Custom Providers are a Timelines Pro feature.

Custom providers allow you to create timeline items through custom Apex code by implementing the tln_TimelineItemProvider interface. This gives you complete flexibility to source timeline data from external systems, perform complex calculations, or aggregate data from multiple objects.

The tln_TimelineItemProvider Interface

The interface is simple but powerful:

global interface tln_TimelineItemProvider {
	List<afp.tln_TimelineItem> getItems(Id recordId, Datetime minDate, Datetime maxDate, String ownerId);
}

Interface Parameters

  • recordId: The ID of the record where the timeline is being displayed
  • minDate: The earliest date for items to display (based on timeline date filter)
  • maxDate: The latest date for items to display (based on timeline date filter)
  • ownerId: The ID of the user viewing the timeline (used for "My Items" filtering)

tln_TimelineItem Class

global with sharing class tln_TimelineItem implements Comparable {
	// Constructor
	global tln_TimelineItem();

	// Getter methods
	global String getDescription();
	global String getTitle();
	global String getSubtitle();
	global String getRecordId();
	global String getIconName();
	global Boolean getRich();
	global String getLink();
	global Datetime getLastOpenedDate();
	global Boolean getHasAttachments();
	global Boolean getHighPriority();
	global List<tln_TimelineAction> getActions();
	global Date getDate();
	global Datetime getDateTime();

	// Setter methods
	global void setDescription(String descr);
	global void setTitle(String title);
	global void setSubtitle(String subtitle);
	global void setRecordId(String recordId);
	global void setIconName(String iconName);
	global void setRich(Boolean rich);
	global void setLink(String link);
	global void setLastOpenedDate(Datetime lastOpenedDate);
	global void setHasAttachments(Boolean hasAttachments);
	global void setHighPriority(Boolean highPriority);
	global void addAction(tln_TimelineAction action);
	global void setDate(Date value);
	global void setDate(Datetime value);

	// Comparable implementation
	global Integer compareTo(Object compareTo);
}

tln_TimelineAction Class

global with sharing class tln_TimelineAction implements Comparable {
	// Constructor
	global tln_TimelineAction();

	// Getter methods
	global String getLabel();
	global String getScreenFlowApiName();
	global String getAutoLaunchedFlowApiName();
	global String getApexCallableName();
	global String getLightningEventType();
	global Decimal getOrder();

	// Setter methods
	global void setLabel(String label);
	global void setScreenFlowApiName(String screenFlowApiName);
	global void setAutoLaunchedFlowApiName(String autoLaunchedFlowApiName);
	global void setApexCallableName(String apexCallableName);
	global void setLightningEventType(String lightningEventType);
	global void setOrder(Decimal order);

	// Comparable implementation
	global Integer compareTo(Object compareTo);
}

Creating a Custom Provider

Step 1: Implement the Interface

Create an Apex class that implements afp.tln_TimelineItemProvider:

global with sharing class MyCustomProvider implements afp.tln_TimelineItemProvider {
	global List<afp.tln_TimelineItem> getItems(Id recordId, Datetime minDate, Datetime maxDate, String ownerId) {
		List<afp.tln_TimelineItem> items = new List<afp.tln_TimelineItem>();

		// Your custom logic here
		// Query external systems, aggregate data, etc.

		return items;
	}
}

Step 2: Build Timeline Items

Create tln_TimelineItem objects using the available setter methods:

afp.tln_TimelineItem item = new afp.tln_TimelineItem();

// Required fields
item.setRecordId(recordId);                    // ID of the related record
item.setTitle('My Custom Timeline Item');      // Main title
item.setDate(Datetime.now());                  // Timeline date/time

// Optional display fields
item.setSubtitle('Additional context');        // Subtitle text
item.setDescription('Detailed description');   // Main description
item.setRich(true);                            // Enable HTML in description
item.setIconName('standard:event');            // Salesforce icon name
item.setLink(recordId);                        // Navigation link or Id of the record to link to
item.setLastOpenedDate(Datetime.now());        // Last viewed timestamp
item.setHasAttachments(true);                  // Show attachment indicator
item.setHighPriority(true);                    // Show priority indicator

Step 3: Add Actions (Optional)

Timeline items can include interactive actions:

// Create an action
afp.tln_TimelineAction action = new afp.tln_TimelineAction();
action.setLabel('Custom Action');
action.setApexCallableName('MyCallableClass:executeAction');
item.addAction(action);

// Other action types
action.setScreenflowApiName('My_Screen_Flow');           // Launch screen flow
action.setAutoLaunchedflowApiName('My_Auto_Flow');       // Launch auto flow
action.setLightningEventType('CustomEventType');         // Publish Lightning event

Complete Example

Here's a complete example that creates timeline items from external API data:

global with sharing class ExternalApiProvider implements afp.tln_TimelineItemProvider {
	global List<afp.tln_TimelineItem> getItems(Id recordId, Datetime minDate, Datetime maxDate, String ownerId) {
		List<afp.tln_TimelineItem> items = new List<afp.tln_TimelineItem>();

		try {
			// Get account email for external API lookup
			Account acc = [SELECT Email__c FROM Account WHERE Id = :recordId LIMIT 1];
			if (String.isBlank(acc.Email__c)) {
				return items;
			}

			// Call external API (implement your HTTP callout)
			List<ExternalEvent> externalEvents = callExternalAPI(acc.Email__c, minDate, maxDate);

			// Convert external events to timeline items
			for (ExternalEvent event : externalEvents) {
				afp.tln_TimelineItem item = new afp.tln_TimelineItem();

				item.setRecordId(recordId);
				item.setTitle(event.eventName);
				item.setSubtitle('External System Event');
				item.setDescription(buildDescription(event));
				item.setRich(true);
				item.setDate(event.eventDate);
				item.setIconName('standard:event');
				item.setLink(event.externalUrl);

				// Add custom action
				afp.tln_TimelineAction syncAction = new afp.tln_TimelineAction();
				syncAction.setLabel('Sync to Salesforce');
				syncAction.setApexCallableName('ExternalEventSync:syncEvent');
				item.addAction(syncAction);

				items.add(item);
			}
		} catch (Exception e) {
			// Handle errors gracefully
			System.debug('Error in ExternalApiProvider: ' + e.getMessage());

			// Optionally create an error timeline item
			afp.tln_TimelineItem errorItem = new afp.tln_TimelineItem();
			errorItem.setRecordId(recordId);
			errorItem.setTitle('External API Error');
			errorItem.setDescription('Failed to load external events: ' + e.getMessage());
			errorItem.setDate(Datetime.now());
			errorItem.setIconName('utility:error');
			items.add(errorItem);
		}

		return items;
	}

	private List<ExternalEvent> callExternalAPI(String email, Datetime minDate, Datetime maxDate) {
		// Implement your external API call here
		// Return parsed external events
		return new List<ExternalEvent>();
	}

	private String buildDescription(ExternalEvent event) {
		return '<p><strong>Event Type:</strong> ' +
			event.eventType +
			'</p>' +
			'<p><strong>Details:</strong> ' +
			event.details +
			'</p>' +
			'<p><a href="' +
			event.externalUrl +
			'" target="_blank">View in External System</a></p>';
	}

	// Inner class for external event data
	public class ExternalEvent {
		public String eventName;
		public String eventType;
		public String details;
		public Datetime eventDate;
		public String externalUrl;
	}
}

Configuring Custom Providers

Setup Configuration

  1. Navigate to the Timeline Setup page
  2. Create a new timeline setup or edit an existing one
  3. Add a new setup item
  4. Select "Custom Apex Class" as the provision type
  5. Choose your provider class from the dropdown

When configuring the setup item through Salesforce Setup > custom metadata instead, the Item Object field should use the format:

apex:namespace.YourProviderClassName

Best Practices

Performance Considerations

  • Limit data volume: Return only essential timeline items within the date range
  • Use efficient queries: Optimize any SOQL queries in your provider
  • Handle timeouts: External API calls should have appropriate timeout handling
  • Cache when possible: Consider caching external data to improve performance

Error Handling

global List<afp.tln_TimelineItem> getItems(Id recordId, Datetime minDate, Datetime maxDate, String ownerId) {
    List<afp.tln_TimelineItem> items = new List<afp.tln_TimelineItem>();

    try {
        // Your provider logic

    } catch (CalloutException e) {
        // Handle API timeouts/failures
        System.debug('Callout failed: ' + e.getMessage());
        // create an error timeline item
        afp.tln_TimelineItem errorItem = new afp.tln_TimelineItem();
        errorItem.setRecordId(recordId);
        errorItem.setTitle('External API Error');
        errorItem.setDescription('Failed to load external events: ' + e.getMessage());
        errorItem.setDate(Datetime.now());
        errorItem.setIconName('utility:error');
        items.add(errorItem);
    } catch (Exception e) {
        // Handle other errors
        System.debug('Provider error: ' + e.getMessage());
		// create an error timeline item
        afp.tln_TimelineItem errorItem = new afp.tln_TimelineItem();
        errorItem.setRecordId(recordId);
        errorItem.setTitle('My Item Error');
        errorItem.setDescription('Failed to load timeline items: ' + e.getMessage() + '\n' + e.getStackTraceString());
        errorItem.setDate(Datetime.now());
        errorItem.setIconName('utility:error');
        items.add(errorItem);
    }

    return items; // Always return a list, even if empty
}

Security

  • Use with sharing: Ensure your provider class respects sharing rules
  • Validate parameters: Check that recordId and other parameters are valid
  • Sanitize external data: Clean any data from external sources before display
  • Permission checks: Verify user permissions for sensitive operations

Date Filtering

Respect the provided date parameters to ensure good performance:

// If possible filter the items in SOQL as much as possible
String queryString =
    'SELECT ... ' +
    'FROM MyObject__c ' +
    'WHERE (MyDateField__c >= :minDate AND MyDateField__c <= :maxDate) ' +
	(String.isNotBlank(ownerId) ? 'AND OwnerId = :ownerId ' : '') +
    'AND ... SomeOtherField__c = :recordId ... ' +
    'ORDER BY MyDateField__c ASC ' +
    'LIMIT 100 ';
for (MyObject__c record: Database.query(queryString)) {
    // Create the timeline item
}

// When not using SOQL, filter your data source by the provided date range
if (myItem.eventDate >= minDate && myItem.eventDate <= maxDate) {
    // Include myItem
}


Owner Filtering

When ownerId is provided, filter items to show only those relevant to that user:

// Filter in SOQL as shown in the example above, or
// Example: only show items where the user is the owner or participant
if (String.isBlank(ownerId) || myItem.OwnerId == ownerId) {
    // Include myItem
}

Testing Custom Providers

Create test classes to ensure your provider works correctly:

@isTest
public class ExternalApiProviderTest {
	@isTest
	static void testGetItems() {
		// Setup test data
		Account testAccount = new Account(Name = 'Test Account', Email__c = 'test@example.com');
		insert testAccount;

		// Test the provider
		Test.startTest();
		ExternalApiProvider provider = new ExternalApiProvider();
		List<afp.tln_TimelineItem> items = provider.getItems(
			testAccount.Id,
			Datetime.now().addDays(-30),
			Datetime.now().addDays(30),
			UserInfo.getUserId()
		);
		Test.stopTest();

		// Verify results
		Assert.isNotNull(items, 'Items list should not be null');
		// Add more specific assertions based on your logic
	}
}

Troubleshooting

Provider Not Found Error

If you see "Invalid provider class" errors:

  • Verify the class name is spelled correctly in the setup
  • Ensure the class implements afp.tln_TimelineItemProvider
  • Check that the class is global and active
  • Confirm proper namespace prefix if applicable

Empty Timeline

If your provider returns no items:

  • Check debug logs for any exceptions in your provider
  • Verify the date range parameters are being respected
  • Ensure your provider logic matches the test data
  • Test your provider directly in anonymous Apex

Performance Issues

If the timeline loads slowly:

  • Review your provider's query efficiency
  • Consider limiting the number of items returned
  • Add appropriate indexes to custom objects
  • Optimize external API calls and add caching