Activity log
What CaptainCore records in its audit trail, the shape of a log row, and every action and entity type currently written.
CaptainCore records user actions across the system for auditing. Administrators see everything from the Activity Logs page in the sidebar. Account members see their own account’s activity from that account’s Activity tab, or over the API at GET /activity-logs.
Rows live in captaincore_activity_logs. They are written by CaptainCore\ActivityLog::log() and read by CaptainCore\ActivityLog::fetch(), both in app/ActivityLog.php.
What a row contains
ActivityLog::log( $action, $entity_type, $entity_id, $entity_name, $description, $context, $account_id );
| Column | Source | Notes |
|---|---|---|
activity_log_id |
Auto increment | Primary key |
user_id |
get_current_user_id() |
0 for system and cron writes, rendered as “System” |
account_id |
Passed in | Nullable. This is what scopes the row to a customer. Indexed |
action |
Passed in | varchar(100). See the table below |
entity_type |
Passed in | varchar(50). Indexed |
entity_id |
Passed in | Nullable. The site, domain or environment id |
entity_name |
Passed in | varchar(255). A snapshot of the name at the time, so the row still reads correctly after the resource is deleted |
description |
Passed in | Human sentence, already interpolated |
context |
Passed in | JSON. Only written when the array is non-empty. Holds details such as old and new values |
ip_address |
$_SERVER['REMOTE_ADDR'] |
varchar(45), wide enough for IPv6. Only written when present |
created_at |
Write time | Indexed |
Optional columns are only added to the insert when they have a value, so a row from cron carries no ip_address key at all rather than a null.
What reading a row adds
ActivityLog::fetch() enriches each item before returning it:
user_nameis resolved fromdisplay_name, or the literal stringSystemwhenuser_idis0.avatar_urlis a 48px Gravatar with anidenticonfallback, empty for system rows.contextis decoded from JSON into an object.created_at_rawkeeps the original datetime string, andcreated_atis replaced with a Unix timestamp.
The response envelope is { items, total, page, pages }.
Filtering
GET /activity-logs accepts action, entity_type, user_id, account_id, date_from, date_to, page and per_page. per_page defaults to 50 and is clamped between 1 and 200. Dates are inclusive YYYY-MM-DD, expanded internally to cover the whole day.
Scoping for non-administrators happens in the endpoint, not the model:
- Asking for an
account_idyou do not belong to returns 403. - Asking for nothing in particular applies an
account_ids IN (...)filter for every account you belong to. - Belonging to no accounts returns an empty result set.
That last case is deliberate. Inside fetch(), an empty account_ids array produces a 1 = 0 clause rather than dropping the condition, because dropping it would return every row in the table.
curl -u user:app-password \
"https://your-manager.example.com/wp-json/captaincore/v1/activity-logs?entity_type=domain&action=updated&per_page=25"
Tracked actions
Every ActivityLog::log() call in the plugin, grouped by entity type.
Sites
| Action | Trigger |
|---|---|
created |
A site is created, a site is imported from a provider, a site is provisioned by the provisioning chain, an environment is added, or an existing staging environment is linked |
updated |
Site settings saved, site details edited, account assignments changed, environment connection settings changed, a provider_site_id auto-resolved, or a remote sync corrected the connection |
deleted |
A site is marked inactive, or an environment record is deleted |
shared |
A site is shared with an account |
unshared |
A site is removed from an account |
requested_removal |
A customer requests removal, by setting details.removed to true |
cancelled_removal |
That request is cancelled |
final_backup_requested |
Final Kinsta backups requested before a delete |
failed |
Provisioning failed at the provider, with the provider’s reason in the description |
requested_audit |
A site audit is requested |
cancelled_audit |
A queued audit request is cancelled |
monitor_on / monitor_off |
The uptime monitor is toggled on an environment |
added_label / removed_label |
A site label is added or cleared by wp captaincore site-label |
Domains
| Action | Trigger |
|---|---|
locked / unlocked |
Registrar transfer lock toggled |
enabled / disabled |
WHOIS privacy toggled |
updated |
WHOIS contacts updated, or nameservers changed. The nameserver row carries the new list in context |
executed |
The transfer auth code was retrieved |
deleted |
Domain deleted. context carries removed and warnings |
DNS records
| Action | Trigger |
|---|---|
created |
Record added. context carries type and name |
updated |
Record modified. Same context |
deleted |
Record removed. Same context |
These are logged from the bulk DNS handler as well as the single-record routes, so a zone import produces one row per record.
Email forwarding
| Action | Trigger |
|---|---|
created |
Email forwarding activated for a domain, entity type email_forward |
Environments
| Action | Trigger |
|---|---|
deployed |
An environment was pushed onto another, staging deployed to production, or production deployed to staging |
session_anomaly |
The session snapshot ingest detected anomalies. The description lists each one as [severity] detail, and context carries site_id, environment_id and the full anomalies array |
Files
| Action | Trigger |
|---|---|
viewed |
A file was read through the file browser. context carries path and environment |
deleted |
A file was deleted through the file browser. Same context |
Accounts
| Action | Trigger |
|---|---|
invited |
A user was invited to an account, whether the invite was sent or access was granted to an existing user |
Notes on accuracy
A few things are easy to get wrong when reading this table.
The session_anomaly row is written during ingest, but no email is sent at that moment. High and critical alerts are delivered as an hourly digest by wp captaincore session-alerts, which reads unalerted rows through the alerted_at column on captaincore_session_snapshots. That keeps SMTP off the ingest request path.
monitor_on and monitor_off are built by string concatenation from the resolved state, so they will not appear in a grep for a quoted literal.
Rows written from WP-CLI, notably the site-label commands, carry a real user_id only when a user context exists. From cron they are attributed to System.
There is no transferred action and no toggled action in the current source, despite older documentation listing them. Environment toggles are recorded as monitor_on / monitor_off on the site, and a domain transfer records executed when the auth code is retrieved.