✨ Strapi MCP is now Generally Available - let your agents manage your Strapi content ✨

Legal sends you a GDPR questionnaire. The CMS is yours, so the questionnaire is now yours too. "Where do you store personal data?", "Who can access it?", and "How do you erase it on request?" look like one-line answers until you try to build a GDPR compliance checklist for CMS data on a running Strapi project. Personal data is never in one table. Editor accounts hold names and emails. End-user records hold whatever your forms collect. The Media Library holds signed PDFs and headshots. Audit Logs and Content History keep records of admin changes, and your backups keep copies of all of it.

This guide walks through a self-hosted Strapi 5 headless CMS control by control, mapping each to its GDPR article and Strapi plan. Plans matter: Audit Logs are Enterprise only, Single Sign-On (SSO) needs Enterprise or a paid Growth add-on, and role-based access control, including field-level permissions, is free on Community.

This is engineering guidance, not legal advice: your Data Protection Officer (DPO) or counsel decides what compliance means for your organization.

In brief:

This guide covers four practical areas:

  • Where personal data lives in a CMS, and whether you are controller or processor
  • Self-hosting pins the database and uploads to an EU region, but email, CDN, webhooks, and logs can move data out
  • Audit Logs (Enterprise), SSO (Enterprise or Growth add-on), and Content History map to Articles 5, 25, and 32
  • Handling access, erasure, and retention so personal data doesn't linger in logs, versions, and backups

Together, these areas give you a working structure for reviewing your Strapi setup.

What a GDPR Compliance Checklist for CMS Needs to Cover

If your checklist only covers the content database, it misses most of the copies a regulator will ask about.

Where Personal Data Lives in a CMS

Your checklist should cover both the obvious stores and the copies that hide in plain sight:

  • Admin user accounts: These hold personal data about your editors and administrators.
  • End-user records: These come from the Users and Permissions plugin.
  • Form submissions: Your project may save these to a Collection Type.
  • Uploaded files: The Media Library can hold personal-data files.
  • Audit Logs: These store a JSON payload per event with the acting user and date. The Audit Logs docs say the details modal can show the user's IP address and the request and response bodies, so treat payloads as personal data. In Breyer (C-582/14), the Court of Justice of the EU (CJEU) held that a dynamic IP address can be personal data, so IPs in any log count.
  • Content History: Content History stores retained prior versions of documents modified through the Content Manager, and those versions show field contents, which can include fields containing personal data.
  • Exports and backups: strapi export archives include entities, links, and assets, per the data export docs, and your backups hold copies too.

Reviewing each of these stores keeps personal-data copies from slipping past your checklist.

Controller, Processor, and Where Your CMS Fits

Article 4 defines the controller as the body that "determines the purposes and means of the processing" and the processor as one that "processes personal data on behalf of the controller." When you self-host Strapi, you are the controller and the host, and every vendor touching the data (hosting, database, object storage, email) is a processor that needs an Article 28 contract covering subject-matter, duration, purpose, and the processor's duty to delete or return data when the service ends.

Managed hosting adds Strapi itself as a processor. The cloud legal page for Strapi Cloud includes a Data Processing Addendum that treats you as the controller and authorizes Strapi to engage subprocessors, and its Annex III points to a subprocessor list. Review both before you sign, and confirm with Strapi whether your procurement process needs a countersigned copy.

Data Location: Self-Hosting as Your Residency Answer

Under Article 44, Chapter V allows transfers outside the EU only when its conditions are met. Self-hosting turns GDPR data residency from a vendor question into configuration you control, provided you configure all of it.

Choosing Where Your Data Lives

Three stores need a region: the database, uploads, and backups. The database host comes from DATABASE_HOST and friends in config/database.ts, per the database configuration docs, so pick an EU-hosted instance. Uploads default to local public/uploads/. For object storage, set the region through an environment variable in the Amazon S3 provider config:

// config/plugins.ts
export default ({ env }) => ({
  upload: {
    config: {
      provider: 'aws-s3',
      providerOptions: {
        baseUrl: env('CDN_URL'),
        rootPath: env('CDN_ROOT_PATH'),
        s3Options: {
          credentials: {
            accessKeyId: env('AWS_ACCESS_KEY_ID'),
            secretAccessKey: env('AWS_ACCESS_SECRET'),
          },
          region: env('AWS_REGION'),
          params: {
            ACL: 'private',
            signedUrlExpires: 15 * 60,
            Bucket: env('AWS_BUCKET'),
          },
        },
      },
    },
  },
});

ACL: 'private' matters for personal-data files, since URLs are only signed when the ACL is private, with a 15-minute default expiry. Self-hosted backups have no Strapi-side setting, so their region is wherever your database tooling writes them.

Locking Down the Rest of the Stack

A Frankfurt database with a US email provider likely still counts as a transfer. Check every environment for these leaks:

  • Email. The provider in config/plugins processes recipient addresses and message content wherever it runs. For Nodemailer, SMTP_HOST decides that (email docs).
  • CDN. baseUrl: env('CDN_URL') only rewrites saved asset URLs; the CDN itself, and its edge caches, live outside Strapi.
  • Webhooks. Destination URLs are set per webhook in the Admin Panel, and payloads typically go out as HTTP POST requests to third parties (webhooks docs). They skip the User Content-Type, but any Collection Type holding personal data goes to whatever endpoint is configured.
  • Logs. config/logger.js exports a winston config and nothing more. Log shipping is your infrastructure's job, so check where your log pipeline stores data.
  • Third-party plugins. Audit the outbound network calls of every Marketplace plugin yourself.

This review closes the transfer gaps that database residency alone cannot cover.

What Self-Hosting Costs You

You own patching, backups, and uptime. The security overview puts self-hosted instances out of scope for Strapi's own hardening. Strapi Cloud takes over managed PostgreSQL, automatic backups, and security patching, but becomes another processor on your Article 28 map. The self-hosted vs Cloud comparison covers that tradeoff in full.

Access Control with Role-Based Permissions and SSO

Article 25 asks you to limit personal-data processing and accessibility by default, while Article 32 requires security measures to ensure that people with access process personal data only on the controller's instructions.

Mapping Roles to Least Privilege

The three default roles in role-based access control (RBAC), Super Admin, Editor, and Author, were not designed around personal data. Build custom roles at Settings > Administration Panel > Roles, where the permissions table splits into Collection Types, Single Types, Plugins, and Settings, with create, read, update, delete, and publish per Content-Type.

For each role, ask whether you need its users to access customer records; a marketing editor publishing blog posts does not need read on Order. That is data minimization under Article 5(1)(c) expressed as a permissions matrix. Strapi's content governance guide covers how roles, workflows, and audit trails fit together.

Conditions and Field-Level Permissions

Field-level permissions and conditions are free on every plan, including Community, and the RBAC docs label the feature a "Free feature". Clicking a Content-Type in the Roles screen lists its fields, and unticking a field for an action blocks that role from it. That is how you hide email or phone from a support role that only needs ticketStatus.

Each ticked permission also has a Settings button with two built-in conditions (the admin is the creator, or shares the creator's role). Custom conditions register in bootstrap via conditionProvider.register() and return a boolean or a sift query such as { amount: { $lt: 10000 } } (custom conditions guide).

Centralizing Identity with SSO

SSO requires Enterprise or the paid Growth add-on (pricing). The setup guides cover Microsoft, Keycloak, and Okta, and the pricing page lists Okta, Active Directory, and other SAML 2.0 or OAuth2 providers.

The GDPR angle is offboarding. Enable the per-role local authentication lock-out and your admins can only log in through the identity provider (IdP), with no local password to set or reset. Leave Super Admin off that list: the docs warn that locking out the Super Admin role can shut you out of the Admin Panel entirely.

Disabling the IdP account is expected to block new logins, but the docs confirm neither that nor session revocation, so test both. SSO also hands multi-factor authentication (MFA), conditional access, and device policies to the IdP, where your security team already enforces them.

Scoping API Tokens and End-User Permissions

You manage admin users and end users through separate systems. End users authenticate through Users and Permissions with two default roles, Authenticated and Public, and any request without a token gets the Public role's permissions. Give Public only find and findOne on content meant to be public and leave user-related permissions off.

API tokens come as Read-only, Full access, or Custom. Prefer Custom tokens scoped per Content-Type and rotate anything long-lived. Run Strapi 5.37.0 or later: CVE-2026-27886, a critical relational-filtering data leak, affects 5.36.1 and earlier.

Audit Logs and Content History: Proving What Happened

GDPR audit logs give you evidence for accountability and Article 32 security. Strapi has two features that look similar and serve different purposes.

What Audit Logs Capture

Audit Logs are Enterprise only. They record events for Content-Types, entries, media, logins, roles and permissions, users, tokens, webhooks, and releases. Since 5.52.0, each JSON payload carries an origin key of mcp or admin. Access is Super Admin only.

From 5.53.0, users whose role has both the read and export Audit Logs permissions can export the filtered entries as a CSV file from the Audit Logs screen. That export is the artifact you hand to an auditor. Each export is itself logged, and exports stop at 1,000,000 rows by default (auditLogs.exportMaxRows).

Content History Is Recovery, Not Accountability

"A version is only created when a document is modified through the Content Manager in the admin panel," the docs say. REST, GraphQL, Document Service calls, lifecycle hooks, cron jobs, strapi import, and strapi transfer create no version.

Content History shows what an editor changed and lets you restore it. It says nothing about what integrations or migrations wrote. Pair it with Audit Logs for admin and MCP actions. The Audit Logs docs list admin panel and MCP entry actions and say nothing about REST or Document Service writes, so if integrations touch personal data, log those writes yourself, for example with Document Service middleware.

Setting Retention to Match Storage Limitation

Set retention for both features because they hold personal data and Article 5(1)(e) requires you to keep it identifiable "no longer than is necessary":

  • Audit Logs: They keep 90 days by default and delete older entries, which cannot be recovered.
  • Content History: It keeps 14 days on Growth (the maximum) and 30 days on Enterprise, configurable up to 90.

Both settings follow the same pattern in config/admin.ts, per the admin panel configuration:

// config/admin.ts
export default ({ env }) => ({
  auditLogs: {
    retentionDays: 90,
  },
  history: {
    retentionDays: 30,
  },
});

history.retentionDays can only shorten retention: when your plan's license and the config file both set a value, the lower one applies.

The 90-day default is well short of the six months to one year that France's data protection authority (CNIL) recommends for traceability logs of authorised-user actions. If your retention policy says a year, archive outside Strapi.

Strapi's audit trails post describes pushing audit data to a Security Information and Event Management (SIEM) tool via webhooks and Document Service middlewares. Plan to build that hop yourself and document its retention.

Handling Data Subject Requests in Strapi

Article 12(3) gives you one month to answer each data subject access request, extendable by two more for complex ones.

Access and Portability Requests

Article 15 entitles the person to a copy "in a commonly used electronic form", and Article 20 adds "structured, commonly used and machine-readable" when processing rests on consent or contract. JSON from the Document Service meets both:

// src/api/privacy/services/export.ts
export async function exportOrders(email: string, locales: string[]) {
  const records = [];
  for (const status of ['draft', 'published'] as const) {
    for (const locale of locales) {
      records.push(...(await strapi.documents('api::order.order').findMany({
        filters: { customerEmail: { $eqi: email } },
        populate: '*',
        status,
        locale,
      })));
    }
  }
  return records;
}

Two defaults will bite you. findMany() returns drafts in the default locale unless you pass status and locale, and it accepts a single locale per call, hence the loop above. Nothing is populated by default either. populate: '*' pulls in related data. The equivalent over REST is GET /api/orders?filters[customerEmail][$eqi]=...&populate=*, per the REST API docs.

Erasure Requests and Leftover Copies

The right to erasure under Article 17 applies "without undue delay" where a ground applies, with exceptions for legal obligations and legal claims. When you delete the primary document, you have only completed the first step:

// src/api/privacy/services/erase.ts
export async function eraseOrder(documentId: string) {
  // locale '*' removes every locale; omitting status removes draft and published
  await strapi.documents('api::order.order').delete({ documentId, locale: '*' });
  return { documentId, erased: true };
}

The docs do not say that delete() removes linked media, so delete files through DELETE /api/upload/files/:id with the numeric id (upload REST) or the provider's delete(file). Delete end-user accounts through DELETE /api/users/:id. Then account for the copies:

Where it hidesDocumented behaviorExpires
Other localesYou leave them untouched without a locale wildcardNever, until deleted
Content HistoryNo statement on removal at delete14 days Growth, 30 to 90 Enterprise
Audit Log payloadsNo statement on scrubbing90 days default
Provider filesA separate delete call removes themNever, until deleted
BackupsNo documented purge on deleteYour schedule (Strapi Cloud: 28 days)

The UK Information Commissioner's Office (ICO) says to put backup data beyond use until it is overwritten on schedule, and to be clear with individuals about that (ICO right to erasure). Write that schedule into your response template.

Article 15 obliges you to state purposes and the envisaged storage period, and one Article 17 ground is consent withdrawn with no other legal basis. You can't answer either from a bare email field. Add consentGivenAt (datetime), consentPurpose (enumeration), and lawfulBasis (enumeration) to any Content-Type holding end-user data, and set them at the point of collection. The export above then returns the evidence alongside the data.

GDPR Compliance Checklist for CMS Before You Ship

Before launch, close the gaps a breach would expose, then tick every row of the table below.

Security and Breach Readiness

Your security baseline should cover transport, proxy handling, and secrets:

  • Transport: Strapi serves plain HTTP, so your reverse proxy terminates TLS.
  • Proxy handling: PROXY_IP_HEADER decides which client IP reaches your logs (server configuration).
  • Secrets: Inject all six secrets (APP_KEYS through ENCRYPTION_KEY) from CI, "never from a committed .env file" (deployment docs).
  • Token encryption: Set apiToken.secrets.encryptionKey so token keys stay viewable by their owner without being regenerated.

These controls give you a defined baseline before you address patching and breach response.

Patching is yours. Subscribe to GitHub security advisories for strapi/strapi, since the security policy makes GitHub the only accepted channel for vulnerability reports. Article 33 requires notifying the supervisory authority without undue delay and, where feasible, within 72 hours of becoming aware of a breach, so your runbook should already name who pulls the Audit Logs export and who files.

The Final Checklist

GDPR requirementStrapi controlPlanDone
Transfers (Chapter V)EU region for DATABASE_HOST, AWS_REGION, email, backupsCommunity☐
Processor contracts (Art. 28)DPAs with hosting, database, storage, email, CloudAll plans☐
Minimization (Art. 5(1)(c), 25)Custom roles, field-level permissions, conditionsCommunity☐
Authorised access and offboarding (Art. 32)SSO with local login lock-outGrowth add-on or Enterprise☐
Content API exposure (Art. 25)Public role find/findOne only, Custom API tokens, Strapi 5.37.0 or laterCommunity☐
Records and accountability (Art. 5(2), 32)Audit Logs, CSV exportEnterprise☐
Storage limitation: Audit Logs (Art. 5(1)(e))auditLogs.retentionDays, SIEM archiveEnterprise☐
Storage limitation: Content History (Art. 5(1)(e))history.retentionDaysGrowth, Enterprise☐
Access and portability (Art. 15, 20)Document Service API queries scoped by status and localeCommunity☐
Erasure (Art. 17)delete() plus media, locales, backup scheduleCommunity☐
Consent/lawful-basis records (Art. 7; Art. 17 erasure grounds)consentGivenAt, consentPurpose, lawfulBasis fieldsCommunity☐
Breach notification (Art. 33)Monitor Strapi security advisories/releases; maintain patch runbook and 72-hour ownerAll plans☐

Compliance Is a Practice, Not a Plugin

Which rows you need, how long you keep logs, and what you promise in an erasure response are decisions your organization makes and documents. Strapi gives you the mechanisms. You supply the policy, the DPAs, and the person who answers the questionnaire. If that answer requires Audit Logs or SSO, the Enterprise page covers what ships on that plan, or you can book a demo to walk through it against your questionnaire. Start with the free rows this week, since role and field permissions cost nothing but an afternoon.

Theodore Kelechukwu OnyejiakuDevRel and Community | Software Developer | Technical Writer

Theodore is a Technical Writer and a full-stack software developer. He loves writing technical articles, building solutions, and sharing his expertise.

Related Posts

Content Governance in a Headless CMS: Roles, Workflows and Audit Trails
Ecosystem·16 min read

Content Governance in a Headless CMS: Roles, Workflows and Audit Trails

Learn how to enforce content governance in Strapi 5 with RBAC, Review Workflows, Document Service middlewares, and audit trails. Includes working code examples.

·September 24, 2026
Multi-factor authentication for the Strapi admin panel: what your options are
EcosystemIntermediate·6 min read

Multi-factor Authentication for the Strapi Admin Panel: what your options are

Strapi ships no built-in TOTP two-factor auth for the admin panel. The three routes that work: SSO with an IdP, a plugin, and network-level controls.

·September 9, 2026
The True TCO of Self-Hosting a Headless CMS
Ecosystem·12 min read

The True TCO of Self-Hosting a Headless CMS

Discover the real cost of self-hosting Strapi 5: infrastructure, DevOps labor, security, migrations, and hidden costs—with a practical TCO checklist.

·September 24, 2026