Skip to main content

Recording Entries

Every Chronicle entry is an explicit developer decision. This page documents the full EntryBuilder API.

Starting a record

use Chronicle\Facades\Chronicle;

Chronicle::record()
->actor($user)
->action('order.created')
->subject($order)
->commit();

Chronicle::record() returns an EntryBuilder. Call commit() to validate, hash, and persist the entry.

Required fields

Three fields are required. Missing any one throws an exception at commit() time:

MethodException thrown
actor()Chronicle\Exceptions\MissingActorException
action()Chronicle\Exceptions\MissingActionException
subject()Chronicle\Exceptions\MissingSubjectException

Full API reference

actor(mixed $actor)

Sets the actor responsible for the action. Accepts an Eloquent model, any object resolvable by your ReferenceResolver, or a string.

The string 'system' is a special case — it records as type system / id system without going through the resolver:

->actor($user) // Eloquent model
->actor('system') // built-in system actor
->actor('cli-worker') // arbitrary string (resolved as-is)

action(string $action)

Sets the action name. Use dot-notation for domain clarity:

->action('invoice.sent')
->action('user.email_changed')
->action('order.payment_captured')

The built-in ActionValidator enforces a maximum byte length (default 255, configurable via CHRONICLE_ACTION_MAX_LENGTH).

subject(mixed $subject)

Sets the entity the action was performed on. Accepts the same types as actor():

->subject($order)
->subject($invoice)

metadata(array $metadata)

Attaches domain-specific data to the entry. Anything you want to preserve as part of the audit event:

->metadata([
'total' => 14900,
'currency' => 'EUR',
'items' => 3,
])

context(array $context)

Attaches execution context — environment or runtime data that describes where the action occurred rather than what it was:

->context([
'request_id' => request()->header('X-Request-Id'),
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
])

Context resolvers (configured under extensions) can populate this automatically.

diff(array $diff)

Attaches a structured before/after diff. Each key must be a field name with old and new sub-keys:

->diff([
'status' => ['old' => 'pending', 'new' => 'paid'],
'amount' => ['old' => 1000, 'new' => 950],
])

Keys are sorted alphabetically; each entry is normalised to {old, new}.

change(string $field, mixed $old, mixed $new)

Adds or merges a single field into the diff:

->change('status', 'pending', 'paid')

Can be chained multiple times. Keys are kept sorted.

tags(array $tags)

Attaches searchable labels to the entry. Tags are normalised before storage:

  • converted to lowercase
  • trimmed of whitespace
  • duplicates removed
  • sorted alphabetically
->tags(['Orders', ' Checkout ', 'orders'])
// stored as: ['checkout', 'orders']

The built-in TagsValidator enforces per-tag length (default 50 chars) and total tag count (default 10), both configurable.

correlation(string $correlationId)

Manually assigns a correlation id. Usually you use Chronicle::transaction() instead, but this is available for cases where you already have an id:

->correlation('batch-2026-06-03')

modelDiff(Model $model)

Derives a diff from the model's dirty attributes. Ignores created_at and updated_at automatically:

$order->status = 'paid';

Chronicle::record()
->actor($user)
->action('order.status_changed')
->subject($order)
->modelDiff($order)
->commit();

If the model has no dirty attributes, the diff is omitted.

modelChanges(Model $model) (deprecated)

An alias for modelDiff(). Deprecated since 1.x; will be removed in 2.0. Use modelDiff() instead.

build() vs commit()

MethodWhat it does
build()Validates the builder state and returns the raw payload array. Does not persist.
commit()Calls build(), then passes the payload through the Chronicle pipeline (hash, chain, persist).

Use build() when you need to inspect or transform the payload before committing. In the normal case, call commit() directly.

Example: full entry

Chronicle::record()
->actor($user)
->action('order.status_changed')
->subject($order)
->metadata(['new_status' => 'paid'])
->context(['ip_address' => request()->ip()])
->modelDiff($order)
->tags(['orders', 'payments'])
->commit();

See also