How it works
This page is the canonical reference for the package's mental model: the pipeline, the building blocks, the type taxonomy, and the dedup/priority rules. Read it once before configuring sources, writing renderers, or debugging missing entries.
The pipeline
$record->timeline() [TimelineBuilder]
↓ resolves
TimelineSource(s) per source [resolve(subject, Window)]
↓ yields
TimelineEntry stream [filter → dedup → sort]
↓ paginated
LengthAwarePaginator → Renderer → Blade view
A call to $record->timeline() returns a fluent TimelineBuilder. Each registered source is asked to resolve() entries inside a shared Window. The combined stream is filtered, deduplicated, sorted, and either paginated or returned whole. Renderers turn each TimelineEntry into HTML at view time.
Core building blocks
Subject. Any Eloquent model implementing Relaticle\ActivityLog\Contracts\HasTimeline. In practice you get this for free by using the Relaticle\ActivityLog\Concerns\InteractsWithTimeline trait, which provides timeline(), paginateTimeline(), and forgetTimelineCache(). The subject is passed into every source's resolve() call.
Source. A class implementing Relaticle\ActivityLog\Contracts\TimelineSource. Sources own the data: they query the database (or any other store) and yield TimelineEntry instances. The package ships four built-ins, registered via the builder's helpers:
$record->timeline()
->fromActivityLog() // ActivityLogSource
->fromActivityLogOf(['comments']) // RelatedActivityLogSource
->fromRelation('invoices', fn ($s) => $s->title(...)) // RelatedModelSource
->fromCustom(fn ($subject, $window) => yield ...); // CustomEventSource
See /essentials/sources for full source configuration.
Entry. Relaticle\ActivityLog\Timeline\TimelineEntry — an immutable readonly value object. It carries id, type, event, occurredAt, dedupKey, sourcePriority, optional subject/causer/relatedModel, plus presentation hints (title, description, icon, color, renderer, properties). Sources construct entries; the builder, dedup, sort, and renderer only consume them.
Renderer. A class implementing Relaticle\ActivityLog\Contracts\TimelineRenderer. Given a TimelineEntry, it returns a View or HtmlString. Renderers are looked up by the entry's renderer field (explicit), then its event, then its type, falling back to DefaultRenderer. See /essentials/customization.
The Window value object
Relaticle\ActivityLog\Timeline\Window is the read-only context passed to every source's resolve(). It carries:
from/to— the date range set via->between($from, $to)(both nullable).cap— the per-source over-fetch limit. The builder computescap = perPage * (page + buffer)for paginated reads, and uses a hard10000ceiling for->get().typeAllow/typeDeny/eventAllow/eventDeny— the active filters, mirrored so sources can push them down into queries (otherwise the builder applies them post-yield).
Sources should respect cap to keep memory bounded — RelatedActivityLogSource, for example, calls ->limit($window->cap) on its underlying query. The builder constructs the Window via makeWindow($cap).
Type taxonomy
$entry->type) has 3 values today:activity_logrelated_modelcustom
source_priorities config keys) has 4 keys:activity_logrelated_activity_logrelated_modelcustom
type='activity_log' (not'related_activity_log'). Filtering with ->ofType(['related_activity_log']) will never match anything. To distinguish own-log entries from related-log entries today, inspect $entry->relatedModel (it's null for ActivityLogSource entries and an Eloquent model for RelatedActivityLogSource entries) after calling ->get() — there is no builder-level filter for source-of-origin.See /troubleshooting ("Type filter doesn't match anything") and issue #11.Source priorities
Defaults live in config/activity-log.php under source_priorities. Higher priority wins on dedup ties.
| Source | Default priority | Config key |
|---|---|---|
ActivityLogSource | 10 | source_priorities.activity_log |
RelatedActivityLogSource | 10 | source_priorities.related_activity_log |
RelatedModelSource | 20 | source_priorities.related_model |
CustomEventSource | 30 | source_priorities.custom |
Override per-call by passing the second argument to any builder helper:
$record->timeline()->fromCustom($resolver, priority: 100);
Dedup behavior
Entries sharing a dedupKey collapse to a single entry: the one with the highest sourcePriority wins. On equal priority, first-seen wins (sources are resolved in the order they were registered).
The default dedupKey is generated by AbstractTimelineSource::dedupKeyFor():
{class}:{id}:{occurredAt-iso}
Override per builder:
$record->timeline()
->dedupKeyUsing(fn (TimelineEntry $entry): string => "{$entry->type}:{$entry->event}:{$entry->occurredAt->toDateString()}");
Disable dedup entirely with ->deduplicate(false).
Lifecycle
- The builder calls each registered source's
resolve($subject, $window). - Yielded entries pass through
passesFilters()—typeAllow/typeDeny/eventAllow/eventDeny. - Dedup is applied if enabled (default:
true, overridable in config viadeduplicate_by_default). - The collection is sorted —
sortByDateDesc()is the default;sortByDateAsc()flips it. - Results are returned via
paginate(perPage, page)(the standard Filament/Livewire path) orget()(capped at 10000 entries).