The Event Log is a tenant-wide ledger of noteworthy activity that has happened inside the platform. It is not an access log, not a debug trace, and not an audit transcript of every database write. It is a curated stream of events that the platform and tenant configuration have explicitly decided are worth preserving for operators, administrators, and compliance reviewers.
An event is written to the ledger whenever the platform or a tenant-configured automation calls the event log facility. Each event carries:
The Event Log surface is the operator-facing view that turns this ledger into a filterable, paginated list with a detail modal per event.
The platform exposes many surfaces where the question "what actually happened here" matters: an account is reset, a record is touched by an automation, an email is sent, a cron job runs. Without a single inspection surface, these questions scatter across separate logs, separate audits, and separate UI panels.
The Event Log answers them in one place:
Because events are filterable by user, by actor, by object, by category, by date, and by full-text search over title and content, operators can slice activity along whichever axis matters for the question at hand.
There are three concerns to keep distinct.
Keeping these three ideas separate is what lets the Event Log serve simultaneously as an operational inspection tool, a governance trail, and a lightweight compliance reference.
Every event carries the same shape. Understanding each field is what makes filtering and reading the log productive.
A short string identifier that groups events into families. Common platform-provided categories include:
Categories are the primary navigation axis for the log. Filtering by category is how an operator narrows the stream to "just the emails last month" or "just the account changes".
Both are free-form text, with one convention. If the string is prefixed with __, the log treats it as a translation key and resolves it at render time using the tenant's translation files. This is how platform-generated events display in the operator's chosen language rather than hard-coding English or Czech.
In the table view, the log displays the resolved text, and the raw key remains available as a tooltip. In the detail modal, both the resolved text and the raw key are shown side by side, making localization gaps easy to spot.
Three user references live on every event, and they answer different questions.
logEvent calls that name a user, this is that user. Often null on object-only events.The combination lets operators answer both "what was done to this user?" and "what did this user do?" and, crucially, "did an admin do this while impersonating someone?".
If an event is associated with a specific record (a typed object or a user resource), the event carries the object's type id and id. The Event Log UI resolves these to the object's display title at render time using the object's canonical title property — the same label the rest of the platform uses — so events read as domain events rather than as numeric references.
If the underlying object is later renamed, the log reflects the new name automatically, because resolution happens at render time.
An optional structured payload, stored as JSON. This is the place for event-specific detail that does not fit into title or content: the recipient of an email, the parameters of a cron job, the key/value pair that changed on an object. The detail modal renders the payload pretty-printed so that operators can scan it without leaving the log.
Every event has a precise creation time. The log orders events newest first and exposes a date range filter so operators can scope to a specific incident window.
Events enter in three ways. Operators do not create events from the Event Log view — the log is a readout, not an entry surface.
The platform logs events at significant lifecycle points. Example points include:
Account- and messaging-class events are always written. Object-lifecycle events are written only when the object's Type has the corresponding flag enabled — see the subsection below.
These are written automatically as part of the surrounding operation. The category and title are standardized so the same event category is produced by the same action regardless of where it was triggered.
The five object categories — Object Created, Object Updated, Object Accessed, Object Deleted, Object Restored — are controlled on the Logging form group of each type. Four flags govern them: Object Deleted and Object Restored share a single flag, because the two are halves of one lifecycle and it doesn't make sense to log deletions without restorations (or vice versa).
| Flag (on the type) | Fires on | Custom message field |
|---|---|---|
log_create_enabled |
successful object creation | log_create_message |
log_read_enabled |
operator-visible single-object retrieval — object detail page, REST single-resource fetch, MCP get tool call |
log_read_message |
log_update_enabled |
persisted updates with at least one actually changed property | log_update_message |
log_delete_enabled |
object deletion (captures pre-delete state) and restoration of a soft-deleted record from the Trash surface | log_delete_message |
Every flag defaults to off. Turn them on deliberately per type.
When log_delete_enabled is on, both Object Deleted and Object Restored events are emitted, and both reuse log_delete_message as their custom content. The platform's default message distinguishes "has been deleted." from "has been restored." automatically based on the event category, so leaving the message field empty yields correctly-worded defaults for both halves. When a custom message is provided, phrase it neutrally if it needs to cover both cases.
When a custom message field is empty, the platform writes a neutral default naming the type, its id, the object id, and the operation. When the field has content, it is passed through the expression layer (Expressions) so property shortcodes resolve against the object in scope — e.g. "{$this.invoice_number} archived by {$this.owner.user_name}.".
Read logging has a deliberately narrow scope. It fires on operator-visible single-object retrievals — the detail page's initial render, the REST single-resource fetch, and the MCP single-object tool call — and not on list views, snippet redraws, signal handlers, batch sub-requests, background AJAX side-tasks, or internal object resolution (permission checks, expression evaluation, form rendering, cache warm-up, etc.). This keeps the ledger meaningful: one event per deliberate "show me this record" interaction, not one per partial redraw or framework-internal lookup. Even so, enable it only when read traceability is a governance requirement (sensitive personal data, financial records under audit, regulatory records).
The Types chapter documents each flag and its recommended use in full.
The built-in Log event automation action calls the event log facility with inputs taken from the automation's scope. Tenant implementers add this action to an automation whenever they want a specific workflow step to be traceable in the operator-facing log.
Typical uses:
Automation-generated events can populate any field on the event, so implementers can tailor categorization to the tenant's domain vocabulary.
Cron work executed by the scheduler can log events using the same mechanism. This is how operators correlate "cron fired at 02:00" with "this is what it did".
The Event Log presenter gives access to the ledger through three concerns layered together: filtering, listing, and detail inspection.
A persistent filter panel on the left sits alongside the list. It narrows by:
Filters compose. Every applied filter is reflected in the URL, so a filtered view is shareable and bookmarkable. Pagination and filter state are preserved across each other.
The filter panel itself is resizable — operators working on wider screens can give filters more room; operators who want more table width can collapse filters. This preference is remembered per session.
The list is paginated (100 events per page by default), sorted newest first, and shows the signal that operators typically scan for:
A summary above the table shows page position and total event count so operators can tell immediately whether a filter has narrowed the result set usefully.
The user and object links open inside the same page using the platform's modal stack, so operators can drill into an affected record and return to the list without losing filter state.
Clicking a row opens a detail modal with every field of the event in full:
The modal is where compliance-style "I need to see everything on this event" work happens. The list is the scanning surface; the modal is the inspection surface.
Access to the operator surface is gated by a dedicated permission:
Without this permission, the navigation entry is hidden, the URL redirects back to the homepage with an access-denied flash, and the surface is not reachable.
This is deliberately a narrow permission. The event log is readable activity about the whole tenant, including user lifecycle and object access. It should be granted only to roles whose job legitimately requires cross-tenant inspection — typically tenant administrators, compliance reviewers, and a small set of power users. It should not be granted to ordinary users.
Writing events is not gated by this permission. Automations and platform lifecycle hooks write events regardless of who triggered them.
created-by user to surface every event produced while a specific admin was impersonating other users.General Activity with a free-text search on the automation's signature title. Scan the resulting feed to verify the automation fired as expected.Cron Action and a date range to review scheduled work for a given window.The Event Log complements rather than replaces other governance tooling.
The Event Log is the activity surface; the other governance chapters are the state and configuration surfaces.
__ lets operators read the log in their configured language. The raw key stays visible as a tooltip for exact grepability.data payload as the overflow field. Anything not in title, content, category, or the user/object references belongs there. Keep it JSON-serializable and modest in size.Avoid these patterns.
Suppose a tenant operator is investigating a user complaint: "I tried to reset my password yesterday and never got the email".
A workable approach:
Account Password Reset by User event.In this scenario the log is used as the diagnostic lens across two independent concerns (account lifecycle and messaging) that would otherwise require separate tools.