Essentials

Refining the Timeline

Filters, sorting, and deduplication.

Every refinement is a chainable method on TimelineBuilder. Compose only what you need - each section below covers one concern.

Filtering

Narrow the entries by date window, type, or event. All four filters stack and are cumulative.

$record->timeline()
    ->between(now()->subMonth(), now())         // CarbonInterface|null on each side
    ->ofType(['related_model', 'activity_log']) // type allow-list
    ->exceptType(['custom'])                    // type deny-list
    ->ofEvent(['email_sent', 'task_completed']) // event allow-list
    ->exceptEvent(['draft_saved']);             // event deny-list

Sorting

Order the combined stream after sources are merged.

$record->timeline()
    ->sortByDateDesc(); // default; use sortByDateAsc() for ascending

Deduplication

Entries sharing a dedupKey collapse to the highest sourcePriority (first occurrence wins on ties). Disable entirely, or override the key when the default identity isn't right for your use case.

$record->timeline()
    ->deduplicate(true) // default: true - pass false to keep every entry
    ->dedupKeyUsing(fn ($entry) =>
        "{$entry->type}:{$entry->event}:{$entry->occurredAt->toDateString()}"
    );

Running the query

After filters/sort/dedup are set, one of these methods executes and returns entries.

MethodReturns
get()Collection<int, TimelineEntry> - all entries up to the internal 10 000 cap.
paginate(?int $perPage, int $page = 1)LengthAwarePaginator<int, TimelineEntry>. Uses activity-log.default_per_page if $perPage is null.
count()int (runs get()).
Copyright © 2026