Refining the timeline
Every method on this page is a chainable call on Relaticle\ActivityLog\Timeline\TimelineBuilder between source registration and result consumption. For source registration see /essentials/sources; for the lifecycle and dedup model see /concepts/how-it-works.
Filtering
Five filters are available — one window, two type filters (allow/deny), two event filters (allow/deny):
$record->timeline()
->between(now()->subMonth(), now())
->ofType(['activity_log'])
->exceptType(['custom'])
->ofEvent(['created', 'updated'])
->exceptEvent(['draft_saved'])
->get();
| Method | Purpose |
|---|---|
between(?CarbonInterface $from, ?CarbonInterface $to) | Passes through to each source's Window. Either side may be null for an open-ended range. |
ofType(array $types) | Entry-type allow-list. Anything not in the list is dropped. |
exceptType(array $types) | Entry-type deny-list. Anything in the list is dropped. |
ofEvent(array $events) | Event-name allow-list. |
exceptEvent(array $events) | Event-name deny-list. |
Type and event filters are mirrored onto every source's Window so sources can push them into SQL where possible. The builder also re-applies them post-yield as a safety net, so a source that ignores the Window filters still produces correct output.
activity_log, related_model, custom. Entries from RelatedActivityLogSource carry type='activity_log' — calling ->ofType(['related_activity_log']) will silently match nothing. See the type taxonomy for the full breakdown.Sorting
$record->timeline()->sortByDateDesc()->get(); // default — newest first
$record->timeline()->sortByDateAsc()->get(); // oldest first
Sorting happens after sources merge, so individual sources may yield in any order. The sort key is $entry->occurredAt->getTimestamp().
Deduplication
$record->timeline()->deduplicate(false)->get(); // disable dedup entirely
Override the dedup key with a closure that receives each TimelineEntry:
$record->timeline()
->dedupKeyUsing(fn ($entry) => "{$entry->type}:{$entry->event}:{$entry->occurredAt->toDateString()}");
The default dedup key is {class}:{id}:{occurredAt-iso}. See /concepts/how-it-works#dedup-behavior for what wins when keys collide (highest sourcePriority; ties broken by registration order).
Running the query
| Method | Returns | Notes |
|---|---|---|
get() | Collection<int, TimelineEntry> | Caps at 10000 entries internally. |
paginate(?int $perPage = null, int $page = 1) | LengthAwarePaginator<int, TimelineEntry> | $perPage = null falls back to default_per_page config (20). |
count() | int | Runs get() internally — no separate COUNT query. Use sparingly on large datasets. |
$entries = $record->timeline()->fromActivityLog()->get();
$page = $record->timeline()->fromActivityLog()->paginate(perPage: 25, page: 2);
$total = $record->timeline()->fromActivityLog()->count();
paginate() for you. Direct calls are useful for tests, queue workers, exports, and other non-UI consumers.Trait helper: paginateTimeline()
The Relaticle\ActivityLog\Concerns\InteractsWithTimeline trait exposes paginateTimeline(?int $perPage = null, int $page = 1) as a pass-through to $this->timeline()->paginate(...). It's required by the Relaticle\ActivityLog\Contracts\HasTimeline interface, so consumers can paginate without going through the builder explicitly:
$page = $opportunity->paginateTimeline(perPage: 20, page: 1);
This is the entry point the Livewire timeline component uses internally. Override timeline() on the model to customize source registration once and have every caller — including paginateTimeline() — pick it up.
Pagination buffer mechanics
Pagination uses an over-fetch strategy controlled by the pagination_buffer config key (default 2):
cap = perPage × (page + buffer)
Each source is asked for at most cap entries via Window::cap. After dedup and post-source filtering drop entries, the remaining set is sliced down to the requested page. Over-fetching keeps deeper pages from coming up short when entries are removed during the pipeline.
Trade-off:
- Higher
buffer— more memory, more rows scanned per source, but exact pagination at deep pages even when sources collide heavily. - Lower
buffer— less memory, fewer rows scanned, but risk of a deep page returning fewer thanperPageentries when dedup removes a lot.
When to lower it: sources rarely collide (no overlapping dedupKeys) and your filters are permissive. The default 2 is overkill for single-source timelines.
When to raise it: sources overlap heavily — for example, a custom source that re-emits events also covered by spatie activity log — and you need exact page sizes at high page numbers.
See /essentials/configuration for the config key.