Every site with more than one person editing it eventually hits the same question: who changed that, and what did it say before? The Activity Log answers both — it records logins, content changes, media uploads, and settings updates on a filterable timeline, shows exactly what changed on every edit, and can restore a previous value with one click. It also emails you the moment something sensitive happens, like a user being promoted to admin or an API key being swapped.
What it does
- A filterable activity timeline (admin-only, at Dashboard → Activity Log) covering five categories: logins & logouts (including failed sign-in attempts), profile & user updates, content changes, media uploads, and settings changes. Filter by category, date range, or free-text search.
- A change viewer on every edit: updates store the before/after value of each changed field. Click View changes on any entry to see a word-level diff — deleted words struck through in red, added words highlighted in green — or a side-by-side before/after for longer values.
- One-click restore: any captured previous value can be written back to the record it came from, straight from the change viewer. The restore itself is logged as a fresh entry, so the audit trail stays complete.
- Email alerts on sensitive events: get an email immediately when a user's role changes, a feature is toggled on or off, or a credential-shaped setting (API key, token, secret) is set, changed, or removed. Alerts work even when the matching log category is turned off.
- Per-category logging toggles — turn any of the five categories off without losing the others.
- Automatic retention: entries older than the configured window (30 days to 1 year, or forever) are pruned by a daily background job. A Clear activity log button wipes everything on demand.
Secrets never reach the log
Credential-shaped values are redacted before they are stored: passwords and other hidden model attributes are never captured at all, and any setting whose key looks like a secret (ai.openai_key, mail.postmark_token, stripe.secret, recovery codes, licenses, …) has both its old and new value replaced with •••••••• in the log. Redacted values can't be restored — by design — and the alert email about a changed key deliberately names only the key, never the value.
Oversized values (over 8 KB) are truncated for storage and likewise marked non-restorable.
Email alerts
Configure under Activity Log → Settings → Email alerts:
| Rule | Fires when | Default |
|---|---|---|
| User role changes | A user is promoted or demoted to a different role | On |
| Feature toggles | A feature is turned on or off from the Features page | On |
| Credential & key changes | A secret-shaped setting is set, changed, or removed | On |
Alerts send to a single configured address through the same mail transport the Marketing feature uses (server mailer or Postmark/Resend/Mailgun/SendGrid). No address configured = no alerts. Delivery is best-effort: a mail failure never blocks the change that triggered it.
Alerts are deliberately independent of the logging toggles — an admin can turn off settings logging (perhaps for noise) and still be emailed when an API key changes.
Restore semantics
Restore writes the raw previous value back through the model layer, so everything that normally reacts to an edit — search indexing, page cache invalidation, the activity log itself — fires as if the value had been edited by hand. Restoring a value on a record that has since been deleted shows a friendly error instead of failing.
Retention & pruning
A daily activity-log:prune job (LazyCron-scheduled, no server cron needed) deletes entries older than the configured retention window. Setting retention to Forever disables pruning. High-frequency bookkeeping writes (cron heartbeats) are excluded from logging entirely so they can't flood the timeline.
For developers
- Module:
app/Features/ActivityLog/— built onspatie/laravel-activitylogwith a thinActivityLoggerwrapper that funnels every write through the feature flag and category toggles. - Model events for
ContentItem,MediaItem,Setting, andUserare captured byLogModelEvents; auth events byLogAuthEvents. Adding a model to theSUBJECTSmap is enough to start logging it. - Change capture (
ActivityDiff::capture) stores raw DB values in theattribute_changescolumn, so restores are cast-safe (enums, JSON bags, dates). Redaction and truncation happen at capture time. - Alert rules live in
ActivityAlerts::inspect, called from the model-event listener before the category gate. - Settings keys:
activity_log.log_{auth,profile,content,media,settings},activity_log.retention_days,activity_log.alert_email,activity_log.alert_{role_changes,feature_toggles,sensitive_settings}.