A fintech company publishes updated terms of service. Two weeks later, a regulator asks: "Who approved this version, when did they approve it, and what exactly changed?" Without structured audit data, answering that question means digging through raw database timestamps, correlating deploy logs, and hoping someone kept notes in a spreadsheet.
A proper compliance management system answers four questions for every content change: who did it, when they did it, what the content looked like before and after, and who approved the change.
Strapi 5 covers these requirements through built-in Enterprise features and customization hooks that fill the gaps. This guide walks through the full pipeline: audit logs, Role-Based Access Control (RBAC), review workflows, Content History, custom Document Service middleware, and webhooks.
In Brief:
Important up front: Audit Logs and Review Workflows are Enterprise features, while Content History is available on Growth and Enterprise plans. RBAC with custom roles is available in Community Edition (free since v4.8). Budget for the Enterprise plan from day one if you're building for a regulated industry.
Before configuring audit trails, you need a running Strapi 5 Enterprise instance with a production-grade database.
Audit Logs, Review Workflows, and several other features in this guide require an Enterprise license and specific runtime dependencies. Confirm the following are in place before proceeding, since missing any of them will block parts of the compliance pipeline:
config/admin fileWith the prerequisites in place, scaffold a new project using the Strapi CLI. The command below creates a project directory, installs dependencies, and walks you through database selection interactively:
1npx create-strapi@latest compliance-cmsFor CI/CD pipelines or automated environments, pass the --non-interactive flag to skip all prompts. Configure your database connection separately in config/database.ts after scaffolding.
Once the project is running, configure audit log retention in config/admin.ts:
1export default ({ env }) => ({
2 auditLogs: { // only accessible with an Enterprise plan
3 enabled: env.bool('AUDIT_LOGS_ENABLED', true),
4 retentionDays: 120,
5 },
6 auth: {
7 secret: env('ADMIN_JWT_SECRET', 'someSecretKey'),
8 },
9});For Strapi Cloud customers, the retentionDays value from your license takes precedence unless the config value is smaller. You cannot exceed your plan's licensed retention period through config alone.
Strapi's built-in audit log system captures admin-level actions and stores them with full user attribution and payload data, giving compliance teams a searchable record of every change made through the Admin Panel.
Strapi's audit log system automatically records every action performed through the Admin Panel, from content edits and publish events to login attempts and role changes. Each entry ties the action to a specific user and timestamp, which means compliance teams can reconstruct a complete timeline of who did what without relying on external tooling. The system organizes events into six categories:
| Event Category | Tracked Actions |
|---|---|
| Content Type | create, update, delete |
| Entry (draft/publish) | create, update, delete, publish, unpublish |
| Media | create, update, delete |
| Login / Logout | success, fail |
| Role / Permission | create, update, delete |
| User | create, update, delete |
Each log entry records the action type, timestamp, the user who performed it, and a detailed payload viewable as JSON in the Admin Panel.
Open the audit log interface at Settings → Administration Panel → Audit Logs. Logs display in reverse chronological order by default, with three filter dimensions available: action type, user, and date range.
Clicking the detail icon on any log entry opens a modal with a Payload section. This section uses an interactive JSON component where you can expand and collapse nested data to inspect what changed.
For compliance purposes, this is your first-party record of every admin-level change. When an auditor asks "who modified this Content-Type on March 15, and what did they change?", filter by user and date range, find the entry, and open the payload.
The admin panel config reference covers the full auditLogs config block, including the retentionDays parameter and the enabled boolean toggle.
Role-Based Access Control in Strapi enforces who can create, edit, publish, and view content. For compliance workflows, RBAC becomes the mechanism that ensures separation of duties between content creators, reviewers, and approvers.
Strapi ships with three default administrator roles: Super Admin (can access all features and settings), Editor (can create, manage, and publish any content), and Author (can create and manage their own content only). These are available in both Community and Enterprise editions.
For a compliance management system, you need purpose-built roles that enforce separation of duties. Create these at Settings → Administration Panel → Roles by clicking "Add new role":
For each role, the permissions table is split into four categories: Collection types, Single types, Plugins, and Settings. Per Content-Type, you configure five actions: Create, Read, Update, Delete, and Publish (the Publish option only appears when Draft & Publish is enabled on the Content-Type).
To restrict entry-level access, apply built-in conditions to each permission. For example, Strapi ships with a "Has created the entry" condition: when applied to the Update action, Content Creators can only edit entries they authored, not entries created by other users.
To set this, click the gear icon next to the action, select the condition from the dropdown, and click Apply. A dot appears next to the permission name to confirm a condition is active. This is how you enforce author-level isolation without writing custom code.
Some fields should be locked down by role. For example, a compliance-status field on a Content-Type should be writable only by the Compliance Officer role, while Content Creators can see it but not change it.
Configure this in the RBAC role editor:
Content Creators see the compliance status on every entry but cannot modify it. Changes are tracked in audit logs, and admin access is controlled through roles and permissions.
Review Workflows define the approval stages content must pass through before publication, giving compliance teams a formal gate between authoring and going live.
Review Workflows (Enterprise only) let you define approval stages that content must pass through before publication. Configure them at Settings → Global Settings → Review Workflows.
The default workflow ships with four stages: To do, In progress, Ready to review, and Reviewed. For a compliance system, replace these with stages that map to your actual approval process:
For each stage, configure two permission fields:
Both permissions must be satisfied for a stage transition to appear in the dropdown. This enforces separation of duties: a Content Creator can push an entry from Draft to Legal Review, but the dropdown won't show Compliance Approval or Ready to Publish as options because they lack the required role permissions for those stages.
From the Content Manager edit view of any entry, locate the Information box on the right side. Click the Assignee drop-down list to assign a specific admin user as the reviewer. This assignment saves automatically.
To change the review stage, click the Review stage drop-down list in the same box and select the new stage. The change saves automatically too.
A dedicated webhook event (review-workflows.updateEntryStage) fires when an entry moves between stages, which you can use to notify external systems. One gap to note: the webhook payload for stage transitions does not include a user attribution field. For compliance scenarios where you need to know who moved the entry between stages, rely on the Enterprise Audit Logs or implement a custom Document Service middleware.
Content History complements audit logs by answering a different question. Audit logs tell you who did what. Content History shows you what the content looked like at each point. Together, they cover the full compliance picture.
Access it from the Content Manager: open any entry's edit view, click the menu icon (top right), and select Content History. The sidebar lists every saved version with a timestamp and a status label (Draft, Modified, or Published). Selecting a version shows the field-by-field content for that snapshot.
If a regulator asks "what did this page say on March 15?", select the closest version and read the fields directly. The view also handles schema evolution: fields that have been deleted or renamed since that version are clearly labeled. This prevents schema changes from misrepresenting historical content.
The restore capability does not bypass your approval pipeline. Restoring a previous version overwrites the current draft and sets the document to Modified status. It does not auto-publish. Editors must publish after restoring before the content goes live, which preserves the approval chain required for compliance.
Built-in audit logs cover Admin Panel actions, but some compliance scenarios require tracking at the API level: REST or GraphQL mutations from a frontend, automated imports, or third-party integrations hitting the Content API.
Document Service middlewares run before and after every Document Service operation, making them the right hook for custom audit logic. These middlewares are the Strapi 5 recommended approach for many use cases that previously relied on v4 lifecycle hook files. If you're migrating from v4, use the Document Service API rather than the deprecated Entity Service.
First, define an audit-trail Content-Type (via Content-Type Builder or JSON schema) with these fields: action (enumeration), contentType (string), documentId (string), userId (string), payload (JSON), and timestamp (datetime).
Then register the middleware in src/index.ts:
1// src/index.ts
2export default {
3 register({ strapi }) {
4 strapi.documents.use(async (context, next) => {
5 const result = await next();
6
7 const writeActions = ['create', 'update', 'delete', 'publish', 'unpublish'];
8 if (writeActions.includes(context.action)) {
9 await strapi.db.query('api::audit-trail.audit-trail').create({
10 data: {
11 action: context.action,
12 contentType: context.uid,
13 documentId: context.params?.documentId || result?.documentId || null,
14 userId: context.params?.data?.updatedBy || null,
15 timestamp: new Date().toISOString(),
16 },
17 });
18 }
19
20 return result;
21 });
22 },
23};A few key details about this middleware:
uid check at the top before running the audit logic:1const applyTo = ['api::article.article'];
2
3strapi.documents.use(async (context, next) => {
4 if (!applyTo.includes(context.uid)) {
5 return next();
6 }
7 // ... audit logic
8});next(). This means you capture the final state, and the result object contains the documentId for newly created entries. next(). Failing to do so breaks the Strapi application. This is a critical rule from the middleware docs. documentId, not numeric id. In Strapi 5, documentId is a persistent 24-character alphanumeric string that replaces the numeric ID from v4 in Content API calls. It's the stable identifier used across locales and draft/published versions.You can also expand the tracked actions to include discardDraft for full lifecycle coverage.
With the audit-trail Collection Type exposed via the REST API, compliance teams can pull reports programmatically. Filter by content type and date range using Strapi's filter operators:
1GET /api/audit-trails?filters[contentType][$eq]=api::policy.policy&filters[timestamp][$gte]=2025-01-01&sort=timestamp:descThis returns all audit entries for the policy Content-Type since January 2025, sorted by most recent. The filters API supports all the standard operators: $eq, $gte, $lte, $in, $contains, and more.
Webhooks in Strapi push content lifecycle events to external endpoints in real time, enabling integration with SIEM tools, Slack channels, and compliance platforms without polling the API.
Organizations that need audit data in a Security Information and Event Management (SIEM) tool, Slack channel, or external compliance platform can use Strapi's webhook system to push content lifecycle events in real time.
Create a webhook in Strapi: set the target URL, select the events to trigger on (such as entry.create, entry.update, and entry.delete), and add an authorization header for security.
To set default headers across all webhooks globally, add this to config/server.ts:
1// config/server.ts
2export default {
3 webhooks: {
4 defaultHeaders: {
5 'Custom-Header': 'my-custom-header',
6 },
7 },
8};Each webhook delivery includes an X-Strapi-Event HTTP header with the event name, and private fields are excluded from the payload by design.
One important limitation: webhooks do not fire for User Content-Type changes. Strapi excludes these by design to protect user privacy. If you need user-change audit events sent to external systems, use Document Service middleware in src/index.ts or user-model lifecycle hooks under the users-permissions extension as a workaround.
A practical architecture uses both mechanisms together: the Document Service middleware logs to the internal audit-trail collection (for querying inside Strapi and giving compliance officers an in-app interface), while webhooks simultaneously push the same events to an external system for long-term retention. The external system's retention can outlast Strapi's configured retentionDays, giving you both immediate access and archival coverage.
For Review Workflows specifically, the review-workflows.updateEntryStage webhook event (Enterprise only) fires when entries move between stages, so your external system captures the approval pipeline flow alongside content changes.
The compliance management system works as five coordinated layers:
audit-trail Collection Type. This covers REST/GraphQL requests from frontends and integrations. Built-in audit logs cover who did what. Content History covers what the content looked like. The custom middleware fills gaps for API-level tracking and before/after field states. Webhooks ensure nothing stays locked inside Strapi alone.
Three concrete next steps worth considering:
The Strapi Marketplace has plugins and providers that extend these capabilities further.
This guide built a compliance management system that answers the four questions regulators care about: who changed it, when, what it looked like, and who approved it. Strapi 5 enabled this approach through:
Ready to build this yourself? Start with the Strapi 5 documentation and create your first Content-Type, or explore Cloud hosting for a managed Enterprise deployment.
npx create-strapi-app@latest in your terminal and follow our Quick Start Guide to build your first Strapi project.