Skip to content

Getting Started

Welcome to Tracer, a Laravel package for tracking model revisions and managing staged changes with approval workflows. This guide will help you install, configure, and start using Tracer in your application.

Install Tracer via Composer:

Terminal window
composer require cline/tracer

Publish the configuration file:

Terminal window
php artisan vendor:publish --tag=tracer-config

This creates config/tracer.php with the following structure:

return [
'primary_key_type' => env('TRACER_PRIMARY_KEY_TYPE', 'id'),
'morph_type' => env('TRACER_MORPH_TYPE', 'string'),
'table_names' => [
'revisions' => 'revisions',
'staged_changes' => 'staged_changes',
'staged_change_approvals' => 'staged_change_approvals',
],
'diff_strategies' => [
'snapshot' => SnapshotDiffStrategy::class,
'attribute' => AttributeDiffStrategy::class,
],
'default_diff_strategy' => SnapshotDiffStrategy::class,
'approval_strategies' => [
'simple' => SimpleApprovalStrategy::class,
'quorum' => QuorumApprovalStrategy::class,
],
'default_approval_strategy' => SimpleApprovalStrategy::class,
'quorum' => [
'approvals_required' => 2,
'rejections_required' => 1,
],
'untracked_attributes' => [
'id', 'created_at', 'updated_at', 'deleted_at', 'remember_token',
],
'unstageable_attributes' => [
'id', 'created_at', 'updated_at', 'deleted_at',
],
];

Tracer supports three primary key types for its tables:

TypeDescription
idAuto-incrementing integer (default)
uuidUUID v4 strings
ulidULID strings (time-sortable)

Set via environment variable:

TRACER_PRIMARY_KEY_TYPE=ulid

Configure how polymorphic IDs are stored:

TypeDescription
stringStandard string column (default)
uuidUUID-specific column type
ulidULID-specific column type
TRACER_MORPH_TYPE=uuid

Configure model-specific settings in config/tracer.php:

'models' => [
App\Models\Article::class => [
'tracked_attributes' => ['title', 'content', 'status'],
'untracked_attributes' => ['internal_notes'],
'revision_diff_strategy' => AttributeDiffStrategy::class,
'stageable_attributes' => ['title', 'content'],
'unstageable_attributes' => ['admin_only'],
'staged_diff_strategy' => SnapshotDiffStrategy::class,
'approval_strategy' => QuorumApprovalStrategy::class,
],
],

Or configure at runtime via the Tracer facade:

use Cline\Tracer\Tracer;
Tracer::configure(Article::class)
->trackAttributes(['title', 'content'])
->untrackAttributes(['internal_notes'])
->revisionDiffStrategy(AttributeDiffStrategy::class)
->stageableAttributes(['title', 'content'])
->approvalStrategy(QuorumApprovalStrategy::class);

Publish and run the migrations:

Terminal window
php artisan vendor:publish --tag=tracer-migrations
php artisan migrate

This creates three tables:

ColumnTypeDescription
idconfigurablePrimary key
traceable_typestringPolymorphic model type
traceable_idconfigurablePolymorphic model ID
versionintegerRevision version number
actionstringcreated, updated, deleted, restored, reverted, force_deleted
old_valuesjsonPrevious attribute values
new_valuesjsonNew attribute values
diff_strategystringStrategy used to calculate diff
causer_typestringUser/entity who made the change
causer_idconfigurableID of the causer
metadatajsonAdditional context
ColumnTypeDescription
idconfigurablePrimary key
stageable_typestringPolymorphic model type
stageable_idconfigurablePolymorphic model ID
original_valuesjsonCurrent attribute values
proposed_valuesjsonProposed new values
diff_strategystringDiff strategy identifier
approval_strategystringApproval workflow strategy
statusstringpending, approved, rejected, applied, cancelled
reasontextReason for the change
rejection_reasontextWhy the change was rejected
approval_metadatajsonApproval workflow data
author_typestringUser/entity proposing the change
author_idconfigurableID of the author
metadatajsonAdditional context
applied_attimestampWhen the change was applied
ColumnTypeDescription
idconfigurablePrimary key
staged_change_idconfigurableForeign key to staged_changes
approver_typestringUser/entity who approved/rejected
approver_idconfigurableID of the approver
approvedbooleantrue = approved, false = rejected
commenttextApproval/rejection comment
sequenceintegerOrder of approval

Add the HasRevisions trait to any model you want to track:

use Cline\Tracer\Concerns\HasRevisions;
use Cline\Tracer\Contracts\Traceable;
use Illuminate\Database\Eloquent\Model;
class Article extends Model implements Traceable
{
use HasRevisions;
protected $fillable = ['title', 'content', 'status'];
}

That’s it! Changes are automatically tracked:

// Create - tracked automatically
$article = Article::create([
'title' => 'Hello World',
'content' => 'My first article',
'status' => 'draft',
]);
// Update - tracked automatically
$article->update(['status' => 'published']);
// View revisions
$article->revisions; // Collection of all revisions
$article->latestRevision(); // Most recent revision
// Revert to previous version via facade
Tracer::revisions($article)->revertTo(1); // Revert to version 1

Add the HasStagedChanges trait for approval workflows:

use Cline\Tracer\Concerns\HasRevisions;
use Cline\Tracer\Concerns\HasStagedChanges;
use Cline\Tracer\Contracts\Stageable;
use Cline\Tracer\Contracts\Traceable;
use Illuminate\Database\Eloquent\Model;
class Article extends Model implements Traceable, Stageable
{
use HasRevisions;
use HasStagedChanges;
protected $fillable = ['title', 'content', 'status'];
}

Stage changes for approval:

use Cline\Tracer\Tracer;
// Stage a change
$stagedChange = Tracer::staging($article)->stage(
['title' => 'Updated Title'],
'Fixing typo in title'
);
// Approve the change
Tracer::approve($stagedChange, auth()->user(), 'Looks good!');
// Apply approved changes
Tracer::staging($article)->applyApproved();
// Or reject
Tracer::reject($stagedChange, auth()->user(), 'Title too long');

Tracer provides a fluent facade API:

use Cline\Tracer\Tracer;
// Work with revisions
$revisions = Tracer::revisions($article)->all();
$latest = Tracer::revisions($article)->latest();
Tracer::revertTo($article, 3);
// Work with staged changes
$staged = Tracer::staging($article)->pending();
Tracer::stage($article, ['title' => 'New Title'], 'Reason');
Tracer::approve($stagedChange, $approver);
Tracer::reject($stagedChange, $rejector, 'Not appropriate');
Tracer::apply($stagedChange);

Tracer provides two distinct but complementary systems:

  • Purpose: Automatic history of all changes
  • When: Changes tracked immediately on save
  • Use Case: Audit logs, history viewing, reverting to past states
  • Trait: HasRevisions
  • Purpose: Queue changes for review before persisting
  • When: Changes held until explicitly approved and applied
  • Use Case: Content moderation, maker-checker workflows, sensitive data changes
  • Trait: HasStagedChanges

You can use either system alone or both together:

// Only revisions (audit trail)
class AuditedModel extends Model implements Traceable
{
use HasRevisions;
}
// Only staging (approval workflow without history)
class ModeratedModel extends Model implements Stageable
{
use HasStagedChanges;
}
// Both systems (full audit trail + approval workflow)
class FullModel extends Model implements Traceable, Stageable
{
use HasRevisions;
use HasStagedChanges;
}

Now that you have Tracer installed, explore more features: