Configuration
Every audit system has different requirements. A startup tracking page edits needs different settings than a bank logging financial transactions. The configuration file lets you tune AuditChain to your exact use case — from choosing a dedicated database connection to setting up Slack alerts when chain integrity fails.
After installation, publish the config file if you haven't already:
php artisan vendor:publish --tag="audit-chain-config"
This creates config/audit-chain.php in your application.
Quick Reference
| Key | Default | Description |
|---|---|---|
connection |
null |
DB connection for audit logs (separate recommended) |
table |
audit_logs |
Table name |
state_table |
null |
Sentinel table for chain state (null defaults to _state) |
chain_seed |
env('AUDIT_CHAIN_SEED', 'genesis') |
Secret seed for genesis hash |
queue.enabled |
true |
Dispatch audit recording to queue |
queue.connection |
null |
Queue connection |
queue.queue |
default |
Queue name |
events.log_reads |
false |
Capture retrieved events |
anonymization.replacement |
[ANONYMIZED] |
GDPR anonymization replacement string |
retention.days |
90 |
Days to keep logs (audit:prune) |
notifications.channels |
['mail'] |
Notification channels: mail, webhook |
notifications.mail_to |
[] |
Email addresses for mail alerts |
notifications.webhooks |
[] |
Webhook URLs (Slack, Teams, Discord, custom) |
Database Connection
'connection' => null,
By default, AuditChain uses your application's default database connection. For production systems, a separate database connection is strongly recommended. This isolates audit data from application data, making it harder for a compromised application to tamper with the audit trail.
To use a separate connection, define it in config/database.php and reference it here:
// config/database.php 'connections' => [ 'audit' => [ 'driver' => 'mysql', 'host' => env('AUDIT_DB_HOST', '127.0.0.1'), 'database' => env('AUDIT_DB_DATABASE', 'audit'), 'username' => env('AUDIT_DB_USERNAME', 'audit_user'), 'password' => env('AUDIT_DB_PASSWORD', ''), // ... ], ], // config/audit-chain.php 'connection' => 'audit',
Tip: For maximum immutability, configure the audit database user with INSERT and SELECT only permissions — no UPDATE or DELETE. AuditChain enforces immutability at the Eloquent layer, but database-level restrictions protect against direct SQL tampering.
Table Name
'table' => 'audit_logs',
The database table used to store audit log entries. Change this if audit_logs conflicts with an existing table in your application.
State Table
'state_table' => null, // defaults to '{table}_state'
The sentinel table used to store chain state (last_hash, checkpoint_hash). This single-row table is used by HasAuditTrail to track the latest hash in the chain via SELECT last_hash FOR UPDATE, avoiding the need to scan the audit_logs table. If set to null, it defaults to your audit table name suffixed with _state (e.g., audit_logs_state).
Chain Seed
'chain_seed' => env('AUDIT_CHAIN_SEED', 'genesis'),
The chain seed is used to compute the genesis hash — the first link in the cryptographic hash chain. Set this to a random, secret value in production via your .env file:
AUDIT_CHAIN_SEED=your-random-secret-value-here
You can generate a suitable value with:
php artisan tinker --execute="echo Str::random(64);"
Important: A predictable chain seed weakens the tamper-evidence guarantees of the hash chain. If an attacker knows the seed, they could reconstruct a valid genesis hash. Always set this to a secret value in production.
Note: The chain seed only matters when using
HasAuditTrail(full mode). If you are only usingHasActivityLog(light mode), no hash chain is computed and the seed is not used.
Queue Configuration
'queue' => [ 'enabled' => true, 'connection' => null, 'queue' => 'default', ],
By default, audit log recording is dispatched to a queue to avoid slowing down HTTP requests. This is the recommended setting for production.
| Key | Default | Description |
|---|---|---|
queue.enabled |
true |
When true, audit logs are recorded via a queued job. Set to false to record synchronously. |
queue.connection |
null |
The queue connection to use. null uses your application's default queue connection. |
queue.queue |
default |
The queue name to dispatch audit jobs to. |
If you want audit logs recorded on a dedicated queue (to avoid delays from other jobs), set a specific queue name:
'queue' => [ 'enabled' => true, 'connection' => 'redis', 'queue' => 'auditing', ],
Note: When queue is enabled and you are using
HasAuditTrail, the hash chain is still computed synchronously at dispatch time to maintain chain ordering. The queued job handles the database write.
Events
'events' => [ 'log_reads' => false, ],
Controls which Eloquent events are captured.
| Key | Default | Description |
|---|---|---|
events.log_reads |
false |
When true, captures retrieved events (model reads). |
Warning: Enabling
log_readsis very verbose. Every Eloquent query that loads an auditable model will generate an audit log entry. This can produce a massive volume of records. Only enable this if you have a specific compliance requirement to track data access, such as GDPR Article 15 (right of access) or Article 33 (breach notification — knowing who accessed what data).
Anonymization
'anonymization' => [ 'replacement' => '[ANONYMIZED]', ],
Settings for GDPR anonymization. When you call $model->anonymize(), string personal data fields are replaced with this string followed by a random suffix (via Str::random(8)) to avoid UNIQUE constraint violations. Non-string fields are set to null.
For example, with the default replacement:
$user->anonymize(); // email => '[ANONYMIZED]-a1b2c3d4' // name => '[ANONYMIZED]-x9y8z7w6' // age => null (non-string fields become null)
You can customize the replacement string if needed:
'anonymization' => [ 'replacement' => '[REDACTED]', ],
Retention
'retention' => [ 'days' => 90, ],
Controls automatic pruning of old audit logs via the audit:prune Artisan command.
| Key | Default | Description |
|---|---|---|
retention.days |
90 |
Number of days to retain audit logs. Logs older than this are deleted by audit:prune. |
To prune logs, run the command manually or schedule it:
# Delete logs older than the configured retention period php artisan audit:prune # Override with a custom retention period php artisan audit:prune --days=365 # Prune only a specific model type php artisan audit:prune --type="App\Models\User"
Schedule it in routes/console.php:
Schedule::command('audit:prune')->daily();
Note: Check your regulatory requirements before setting a retention period. GDPR generally requires data minimization (shorter retention), while financial regulations may require longer retention (e.g., 7 years). Set
retention.daysto a value that satisfies your most demanding compliance obligation.
Notifications
'notifications' => [ 'channels' => ['mail'], 'mail_to' => [env('AUDIT_ALERT_EMAIL', '')], 'webhooks' => [], ],
Configures how you are alerted when audit:verify --notify detects chain integrity failures.
| Key | Default | Description |
|---|---|---|
notifications.channels |
['mail'] |
Which channels to use: mail, webhook, or both. |
notifications.mail_to |
[] |
Array of email addresses to receive mail alerts. |
notifications.webhooks |
[] |
Array of webhook URLs (Slack, Teams, Discord, or custom endpoints). |
Mail Alerts
Add the AUDIT_ALERT_EMAIL variable to your .env:
AUDIT_ALERT_EMAIL=security@yourcompany.com
Or specify multiple recipients directly in the config:
'notifications' => [ 'channels' => ['mail'], 'mail_to' => [ 'security@yourcompany.com', 'cto@yourcompany.com', ], ],
Webhook Alerts
Webhook payloads are compatible with Slack, Microsoft Teams, Discord, and custom endpoints. Both text and content keys are included for cross-platform compatibility.
'notifications' => [ 'channels' => ['mail', 'webhook'], 'mail_to' => [env('AUDIT_ALERT_EMAIL', '')], 'webhooks' => [ env('AUDIT_ALERT_SLACK_WEBHOOK'), env('AUDIT_ALERT_TEAMS_WEBHOOK'), ], ],
Scheduling Verification
For continuous monitoring, schedule the verification command in routes/console.php:
Schedule::command('audit:verify --notify')->hourly();
This runs chain verification every hour and sends notifications through your configured channels if any integrity failures are detected.
Full Config File
For reference, here is the complete default configuration:
// config/audit-chain.php return [ 'connection' => null, 'table' => 'audit_logs', 'state_table' => null, // defaults to '{table}_state' 'chain_seed' => env('AUDIT_CHAIN_SEED', 'genesis'), 'queue' => [ 'enabled' => true, 'connection' => null, 'queue' => 'default', ], 'events' => [ 'log_reads' => false, ], 'anonymization' => [ 'replacement' => '[ANONYMIZED]', ], 'retention' => [ 'days' => 90, ], 'notifications' => [ 'channels' => ['mail'], 'mail_to' => [env('AUDIT_ALERT_EMAIL', '')], 'webhooks' => [], ], ];
What's Next?
- Chain Verification — Automate integrity checks
- Custom Events — Record business-level events beyond CRUD
- Personal Data Annotation — Annotate fields as personal data