1. Architecture Overview
Strapi is an open-source, self-hosted headless CMS built on Node.js and TypeScript. It provides a visual admin panel (React SPA), auto-generated REST and GraphQL APIs, a plugin system, and support for multiple databases. The codebase is organized as a monorepo using Yarn workspaces and Nx for build orchestration.
System Architecture
┌─────────────────────────┐
│ Admin Panel (SPA) │
│ React + Design System │
└────────────┬────────────┘
│ HTTP
┌────────────▼────────────┐
│ Strapi Server │
│ Koa.js HTTP Server │
├──────────────────────────┤
│ Middleware Pipeline │
│ ├── CORS │
│ ├── Body Parser │
│ ├── Session │
│ ├── Security │
│ └── Custom Middlewares │
├──────────────────────────┤
│ Core Services │
│ ├── Document Service │
│ ├── Entity Service │
│ ├── Entity Validator │
│ ├── Cron Service │
│ ├── Webhook Runner │
│ └── Session Manager │
├──────────────────────────┤
│ Plugin System │
│ ├── Content Manager │
│ ├── Content-Type Builder │
│ ├── Upload (Media) │
│ ├── Users & Permissions │
│ ├── i18n │
│ ├── GraphQL │
│ ├── Documentation │
│ ├── Email │
│ ├── Review Workflows (EE) │
│ ├── Content Releases (EE) │
│ └── Sentry │
├──────────────────────────┤
│ Database Layer │
│ Knex.js Query Builder │
│ ├── SQLite │
│ ├── PostgreSQL │
│ └── MySQL │
└──────────────────────────┘
Component Inventory
| Component | Package | Path | Role |
|---|---|---|---|
| Core Runtime | @strapi/core |
packages/core/core/ |
Container, Strapi class, service registry, lifecycle |
| Strapi CLI | @strapi/strapi |
packages/core/strapi/ |
CLI commands (develop, build, start, report), Node.js build tooling |
| Admin Panel | @strapi/admin |
packages/core/admin/ |
React SPA (client), Koa server-side routes (auth, roles, tokens, AI) |
| Database | @strapi/database |
packages/core/database/ |
Knex-based ORM, schema management, migrations |
| Content Manager | @strapi/content-manager |
packages/core/content-manager/ |
CRUD operations, history, preview, homepage |
| Content-Type Builder | @strapi/content-type-builder |
packages/core/content-type-builder/ |
Content type schema creation and modification |
| Upload (Media Library) | @strapi/upload |
packages/core/upload/ |
File upload, image optimization, AI metadata, provider system |
| Permissions | @strapi/permissions |
packages/core/permissions/ |
Permission engine, condition evaluation |
| Data Transfer | @strapi/data-transfer |
packages/core/data-transfer/ |
Import/export, remote push/pull via WebSocket |
| OpenAPI | @strapi/openapi |
packages/core/openapi/ |
Auto-generated OpenAPI/Swagger specification |
| Types | @strapi/types |
packages/core/types/ |
TypeScript type definitions for the entire platform |
| Utils | @strapi/utils |
packages/core/utils/ |
Shared utilities (env helper, query param conversion, errors, validation) |
| Content Releases | @strapi/content-releases |
packages/core/content-releases/ |
Batch publish/unpublish scheduling (EE) |
| Review Workflows | @strapi/review-workflows |
packages/core/review-workflows/ |
Stage-based review workflows (EE) |
Plugin Inventory
| Plugin | Package | Description |
|---|---|---|
| Users & Permissions | @strapi/plugin-users-permissions |
End-user auth (local + OAuth providers), roles, JWT |
| GraphQL | @strapi/plugin-graphql |
Auto-generated GraphQL API with playground |
| i18n | @strapi/plugin-i18n |
Internationalization, locale management, AI-powered translations |
| Documentation | @strapi/plugin-documentation |
Auto-generated API documentation (OpenAPI/Swagger) |
@strapi/email |
Email sending abstraction with multiple providers | |
| Sentry | @strapi/plugin-sentry |
Error tracking via Sentry integration |
| Color Picker | @strapi/plugin-color-picker |
Custom field type for color values |
| Cloud | @strapi/plugin-cloud |
Strapi Cloud integration in the admin panel |
Provider Inventory
| Category | Provider | Package |
|---|---|---|
| Upload | Local filesystem | @strapi/provider-upload-local |
| Upload | AWS S3 | @strapi/provider-upload-aws-s3 |
| Upload | Cloudinary | @strapi/provider-upload-cloudinary |
| Sendmail | @strapi/provider-email-sendmail |
|
| Sendgrid | @strapi/provider-email-sendgrid |
|
| Amazon SES | @strapi/provider-email-amazon-ses |
|
| Mailgun | @strapi/provider-email-mailgun |
|
| Nodemailer | @strapi/provider-email-nodemailer |
Technology Stack
| Layer | Technology |
|---|---|
| Runtime | Node.js >=20, <=24 |
| Language | TypeScript 5.4, JavaScript (ES2020+) |
| HTTP Server | Koa.js |
| Admin Frontend | React 18, @strapi/design-system |
| Frontend Bundler | Vite or Webpack (configurable) |
| Database ORM | Knex.js |
| Build Orchestration | Nx 20.4, Yarn 4.12 workspaces |
| Testing | Jest 29, Vitest, Playwright 1.56 |
| Linting | ESLint 8, Prettier 3 |
| Package Publishing | Rollup 4, SWC |
Note
Strapi follows a dual-license model: core code under MIT, and Enterprise Edition features (code under
ee/directories) under the Strapi Enterprise License. EE features require either a license key (STRAPI_LICENSEenv var) or a Strapi Cloud subscription.
Core Data Flow
HTTP Request
→ Koa Middleware Pipeline (CORS, body parsing, auth)
→ Router (Content API or Admin API)
→ Controller (validates input, calls service)
→ Service (business logic, calls Document/Entity Service)
→ Document Service (document-level CRUD, draft/publish)
→ Entity Service (entity-level CRUD)
→ Database Layer (Knex query builder)
→ Database (SQLite/PostgreSQL/MySQL)
← HTTP Response (JSON)
Content API routes are auto-generated from content type schemas and follow the pattern /api/{pluralApiId}. Admin API routes serve the admin panel and use /admin/ prefix with JWT-based authentication.
2. System Requirements
| Requirement | Minimum | Recommended |
|---|---|---|
| Node.js | 20.0.0 | Latest LTS (20.x or 22.x) |
| Node.js max | 24.x.x | — |
| npm | 6.0.0 | Latest |
| RAM | 2 GB | 4 GB+ |
| Disk | 1 GB | 10 GB+ (with media uploads) |
| Database | SQLite 3 | PostgreSQL >=11 |
| OS | Linux, macOS, Windows | Ubuntu 22.04+ LTS |
Database options:
| Database | Version | Best For |
|---|---|---|
| SQLite | 3.x | Development, small single-instance deployments |
| PostgreSQL | >=11 | Production, multi-instance deployments |
| MySQL | >=8.0 | Production (alternative to PostgreSQL) |
3. Configuration Reference
Configuration Files
| File | Format | Location | Purpose |
|---|---|---|---|
config/server.js |
JS/TS | Project root | Server settings (host, port, cron, webhooks, logging) |
config/database.js |
JS/TS | Project root | Database connection settings |
config/admin.js |
JS/TS | Project root | Admin panel settings (auth, API tokens) |
config/middlewares.js |
JS/TS | Project root | Middleware pipeline configuration |
config/plugins.js |
JS/TS | Project root | Plugin configuration (email, upload, graphql, etc.) |
config/api.js |
JS/TS | Project root | Content API settings (responses, REST params) |
.env |
dotenv | Project root | Environment variables (secrets, host, port) |
src/index.js |
JS/TS | Project root | App lifecycle hooks (register, bootstrap, destroy) |
src/admin/app.tsx |
TS/JSX | Project root | Admin panel customization |
Environment-specific overrides: Place config files under config/env/{environment}/ (e.g., config/env/production/database.js). Strapi loads the matching directory based on NODE_ENV.
Environment Variables — Complete Reference
Required Secrets
| Variable | Type | Description | Required |
|---|---|---|---|
APP_KEYS |
Comma-separated strings | Session signing keys (minimum 2) | Yes |
API_TOKEN_SALT |
String | Salt for hashing API tokens | Yes |
ADMIN_JWT_SECRET |
String | Secret for admin panel JWT tokens | Yes |
TRANSFER_TOKEN_SALT |
String | Salt for data transfer tokens | Yes |
JWT_SECRET |
String | Secret for Users & Permissions JWT | Yes |
ENCRYPTION_KEY |
String | Key for field-level encryption | Yes |
Important
All six secrets must be unique, random strings in production. Failing to set them leaves the application vulnerable to token forgery and data exposure. Generate with
openssl rand -base64 32.
Server Configuration
| Variable | Type | Default | Description |
|---|---|---|---|
HOST |
String | 0.0.0.0 |
Server bind address |
PORT |
Integer | 1337 |
Server port |
NODE_ENV |
String | development |
Runtime environment (development, production) |
STRAPI_TELEMETRY_DISABLED |
Boolean | false |
Disable anonymous telemetry |
STRAPI_ADMIN_BACKEND_URL |
String | — | Custom backend URL for admin panel (for reverse proxies) |
WEBHOOKS_POPULATE_RELATIONS |
Boolean | true |
Include populated relations in webhook payloads |
Database Configuration
| Variable | Type | Default | Description |
|---|---|---|---|
DATABASE_CLIENT |
String | sqlite |
Database client (sqlite, postgres, mysql) |
DATABASE_HOST |
String | — | Database host |
DATABASE_PORT |
Integer | — | Database port |
DATABASE_NAME |
String | — | Database name |
DATABASE_USERNAME |
String | — | Database username |
DATABASE_PASSWORD |
String | — | Database password |
DATABASE_SSL |
Boolean | false |
Enable SSL for database connections |
DATABASE_FILENAME |
String | .tmp/data.db |
SQLite file path |
Enterprise & AI
| Variable | Type | Default | Description |
|---|---|---|---|
STRAPI_LICENSE |
String | — | Enterprise license key |
STRAPI_DISABLE_EE |
Boolean | — | Force-disable Enterprise features |
STRAPI_AI_URL |
String | — | AI service endpoint URL |
Miscellaneous
| Variable | Type | Default | Description |
|---|---|---|---|
STRAPI_HOSTING |
String | — | Hosting platform identifier (analytics) |
STRAPI_ANALYTICS_URL |
String | — | Custom analytics endpoint |
STRAPI_UUID_PREFIX |
String | — | UUID generation prefix |
STRAPI_GRAPHQL_V4_COMPATIBILITY_MODE |
Boolean | — | Enable v4 GraphQL compatibility |
USE_EXPERIMENTAL_DEPENDENCIES |
Boolean | — | Enable experimental dependency resolution |
DOCKER |
Boolean | — | Set when running inside Docker |
Warning
Setting
STRAPI_DISABLE_EE=truedisables all Enterprise Edition features regardless of license status. This is useful for CE-only deployments but will break workflows and features that depend on EE functionality.
Server Configuration — Full Schema
// config/server.js
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
// Session/cookie signing keys (minimum 2)
app: {
keys: env.array('APP_KEYS'),
},
// Webhook settings
webhooks: {
populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', true),
},
// HTTP proxy for outbound requests
globalProxy: env('http_proxy'),
// Node.js http.Server options
http: {
serverOptions: {
requestTimeout: 600000, // milliseconds
},
},
// Data transfer settings
transfer: {
remote: {
enabled: true,
},
},
// Logging configuration
logger: {
config: {
level: 'info', // silly, debug, info, warn, error
},
updates: {
enabled: true,
},
startup: {
enabled: true,
},
},
// Cron job scheduling
cron: {
enabled: false,
tasks: {
// '*/5 * * * *': async ({ strapi }) => { ... }
},
},
});
Database Configuration — Full Schema
// config/database.js — PostgreSQL
module.exports = ({ env }) => ({
connection: {
client: 'postgres',
connection: {
host: env('DATABASE_HOST', '127.0.0.1'),
port: env.int('DATABASE_PORT', 5432),
database: env('DATABASE_NAME', 'strapi'),
user: env('DATABASE_USERNAME', 'strapi'),
password: env('DATABASE_PASSWORD', ''),
ssl: env.bool('DATABASE_SSL', false) && {
rejectUnauthorized: env.bool('DATABASE_SSL_REJECT_UNAUTHORIZED', true),
},
},
pool: {
min: env.int('DATABASE_POOL_MIN', 2),
max: env.int('DATABASE_POOL_MAX', 10),
},
},
});
// config/database.js — MySQL
module.exports = ({ env }) => ({
connection: {
client: 'mysql',
connection: {
host: env('DATABASE_HOST', '127.0.0.1'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME', 'strapi'),
user: env('DATABASE_USERNAME', 'strapi'),
password: env('DATABASE_PASSWORD', ''),
ssl: env.bool('DATABASE_SSL', false),
},
},
});
Caution
The
DATABASE_CLIENTvalue must be one ofsqlite,postgres, ormysql. Any other value producesError: Unsupported database client. The MySQL driver requiresmysql2as a peer dependency.
Plugin Configuration Examples
// config/plugins.js
module.exports = ({ env }) => ({
// GraphQL plugin
graphql: {
config: {
endpoint: '/graphql',
shadowCRUD: true,
playgroundAlways: false,
depthLimit: 10,
amountLimit: 100,
},
},
// Email plugin
email: {
config: {
provider: 'sendgrid',
providerOptions: {
apiKey: env('SENDGRID_API_KEY'),
},
settings: {
defaultFrom: '[email removed]',
defaultReplyTo: '[email removed]',
},
},
},
// Upload plugin
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
accessKeyId: env('AWS_ACCESS_KEY_ID'),
secretAccessKey: env('AWS_SECRET_ACCESS_KEY'),
region: env('AWS_REGION'),
params: {
Bucket: env('AWS_BUCKET'),
},
},
},
},
// i18n plugin
i18n: {
enabled: true,
},
// Sentry plugin
sentry: {
enabled: true,
config: {
dsn: env('SENTRY_DSN'),
},
},
// Documentation plugin
documentation: {
enabled: true,
},
});
4. Deployment & Scaling
Production Checklist
- [x] Set
NODE_ENV=production - [x] Generate and set all 6 required secrets
- [x] Configure a production database (PostgreSQL recommended)
- [x] Build the admin panel:
NODE_ENV=production strapi build - [x] Configure upload provider (S3/Cloudinary for multi-instance)
- [x] Set up reverse proxy (Nginx/Traefik) with SSL
- [ ] Configure monitoring and log aggregation
- [ ] Set up database backups
- [ ] Configure rate limiting
- [ ] Review CORS and security middleware settings
Production Start
NODE_ENV=production npm run start
Important
Always run
strapi buildbeforestrapi startin production. Thestartcommand does not auto-reload or rebuild the admin panel. Changes to content types require a rebuild and restart.
Docker Deployment
Create a Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --production
COPY . .
RUN NODE_ENV=production yarn build
EXPOSE 1337
CMD ["yarn", "start"]
Docker Compose with PostgreSQL:
version: '3'
services:
strapi:
build: .
ports:
- '1337:1337'
environment:
HOST: 0.0.0.0
PORT: 1337
DATABASE_CLIENT: postgres
DATABASE_HOST: db
DATABASE_PORT: 5432
DATABASE_NAME: strapi
DATABASE_USERNAME: strapi
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
APP_KEYS: ${APP_KEYS}
API_TOKEN_SALT: ${API_TOKEN_SALT}
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
TRANSFER_TOKEN_SALT: ${TRANSFER_TOKEN_SALT}
JWT_SECRET: ${JWT_SECRET}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: strapi
POSTGRES_USER: strapi
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Reverse Proxy (Nginx)
server {
listen 80;
server_name api.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/ssl/certs/cert.pem;
ssl_certificate_key /etc/ssl/private/key.pem;
client_max_body_size 256M;
location / {
proxy_pass http://127.0.0.1:1337;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
Tip
When using a reverse proxy, set
STRAPI_ADMIN_BACKEND_URLto your public URL so the admin panel correctly resolves API endpoints. For WebSocket-based features (data transfer), ensure the proxy supports WebSocket upgrade.
Strapi Cloud
Deploy directly to Strapi's managed hosting:
strapi deploy
The Cloud CLI handles authentication, project creation, environment configuration, and deployment. Projects link to your Strapi Cloud account via browser-based OAuth.
5. Operations
Lifecycle Hooks
The src/index.js (or src/index.ts) file exposes three lifecycle hooks:
module.exports = {
// Runs before the app is initialized — extend registries
register({ strapi }) {
// Register custom content API parameters, custom fields, etc.
},
// Runs before the app starts — seed data, set up integrations
async bootstrap({ strapi }) {
// Seed initial data, configure services
},
// Runs before the app shuts down — clean up
destroy({ strapi }) {
// Close connections, flush buffers
},
};
Note
The
registerhook runs synchronously before any plugin is loaded. Use it for extending the Strapi registry (custom fields, query params). Thebootstraphook runs after all plugins are loaded and can be async.
Cron Jobs
Configure scheduled tasks in config/server.js:
module.exports = ({ env }) => ({
cron: {
enabled: true,
tasks: {
// Standard cron expression
'0 */6 * * *': async ({ strapi }) => {
// Runs every 6 hours
const entries = await strapi.documents('api::article.article').findMany({
filters: { status: 'draft' },
});
strapi.log.info(`Found ${entries.length} draft articles`);
},
},
},
});
Cron jobs can also be defined in separate files and imported. Removing a cron job by name requires calling it with a non-empty string name — Error: You must provide a name to remove a cron job.
Webhooks
Strapi supports outbound webhooks that fire on content lifecycle events:
- Manage via admin panel: Settings > Webhooks
- Events: create, update, delete, publish, unpublish
- Payload includes the full entry with optionally populated relations
The webhook runner (WebhookRunner class) dispatches events asynchronously.
Tip
Set
webhooks.populateRelations: falseinconfig/server.js(orWEBHOOKS_POPULATE_RELATIONS=false) to reduce webhook payload size in high-volume environments.
Data Transfer
Strapi's data transfer system supports three modes:
- Export — export data to a local file
- Import — import data from a file
- Remote Transfer — push/pull data between Strapi instances via WebSocket
Remote transfer architecture:
Source Strapi Instance Target Strapi Instance
┌─────────────┐ ┌─────────────┐
│ Push Handler │──── WebSocket (wss) ────→ │ Pull Handler │
│ │ │ │
│ Stages: │ │ Stages: │
│ - schemas │ │ - schemas │
│ - entities │ │ - entities │
│ - assets │ │ - assets │
│ - links │ │ - links │
│ - config │ │ - config │
└─────────────┘ └─────────────┘
Transfer proceeds in stages: schemas → entities → assets → links → configuration. Each stage uses streaming with checksums for asset integrity.
Caution
Remote transfer must be explicitly enabled in
config/server.js(transfer.remote.enabled: true). The transfer token salt (TRANSFER_TOKEN_SALT) secures the connection. Unauthorized access attempts returnProviderTransferError: Bad transfer ID provided.
Monitoring & Health
Monitor Strapi with logging and service inspection:
Log levels: silly, debug, info, warn, error (configured in config/server.js under logger.config.level).
Service listing: The CLI commands strapi services:list and strapi controllers:list enumerate registered services and controllers.
Startup logging can be controlled via logger.startup.enabled and logger.updates.enabled.
Admin User Management
API Token management:
| Token Type | Capabilities |
|---|---|
| Read-only | Read access to Content API |
| Full access | Full CRUD access to Content API |
| Custom | Per-content-type permissions |
Token lifecycle: tokens can have unlimited or time-limited lifespans. The lifespan must be a positive number or null (unlimited).
Admin role management:
- The Super Admin role cannot be deleted:
ApplicationError: You cannot delete the super admin role - At least one Super Admin user must exist:
ValidationError: You must have at least one user with super admin role - Roles with assigned users cannot be deleted:
ApplicationError: Some roles are still assigned to some users - Admin users cannot delete themselves:
ApplicationError: You cannot delete your own user
Warning
Enterprise Edition enforces license seat limits. Attempting to create users beyond the limit returns
ForbiddenError: License seat limit reached. You cannot create a new user. Similarly, re-activating blocked users may fail withForbiddenError: License seat limit reached. You cannot active this user.
6. Performance
Build Optimization
The admin panel build supports two bundlers:
| Bundler | Flag | Characteristics |
|---|---|---|
| Vite | --bundler vite |
Faster builds, ES module output |
| Webpack | --bundler webpack |
Legacy support, broader plugin compatibility |
Build flags:
# Production build with minification and no sourcemaps
strapi build --minify --silent
# Development build with sourcemaps and stats
strapi build --sourcemap --stats
Database Performance
- Connection pooling: Configure
pool.minandpool.maxinconfig/database.jsto match your concurrency needs - SQLite limitations: SQLite uses file locking and is not suitable for multi-instance or high-concurrency production deployments
- PostgreSQL recommended: For production, use PostgreSQL with connection pooling (e.g., PgBouncer) for best performance
- Query optimization: Use
fieldsandpopulateparameters to limit data retrieval to only what's needed
File Polling
For network-mounted directories (NFS, Docker volumes on some platforms), enable file polling:
strapi develop --polling
This uses filesystem polling instead of native file watchers, which is slower but more reliable on network filesystems.
7. Security
Authentication Architecture
Strapi has two separate authentication systems:
-
Admin Authentication — JWT-based, managed by
@strapi/admin- Login via
/admin/login - Registration via one-time registration tokens
- Passwords hashed with bcrypt
- API tokens for programmatic access (3 types: read-only, full-access, custom)
- Enterprise: SSO support via configurable providers
- Login via
-
Users & Permissions Authentication — JWT-based, managed by
@strapi/plugin-users-permissions- Local auth (email/password) and OAuth providers
- Email confirmation flow
- Password reset with time-limited codes
- Rate limiting configurable per-provider
Warning
Admin registration tokens are single-use. Using an invalid token returns
ValidationError: Invalid registrationToken. New super admin accounts cannot be created through the registration flow:ApplicationError: You cannot register a new super admin.
API Token Security
API tokens are hashed before storage using the API_TOKEN_SALT. The raw token is shown only once at creation time.
Token validation errors:
| Error | Cause |
|---|---|
Non-custom tokens should not reference permissions |
Attempting to set permissions on a read-only or full-access token |
Missing permissions attribute for custom token |
Custom token created without specifying permissions |
Unknown permissions provided |
Referencing permissions that don't exist |
lifespan must be a positive number or null |
Invalid token lifetime value |
Encryption
Strapi includes a versioned encryption service for field-level encryption:
- Uses the
ENCRYPTION_KEYenvironment variable - Versioned format for future algorithm upgrades
- Errors:
Unsupported encryption version(future-proofing),Invalid encrypted value format(corruption/tampering)
Content API Security
- CORS: Configured via
config/middlewares.js(strapi::corsmiddleware) - Request validation: Content API parameters are validated with Zod schemas when
api.rest.strictParamsis enabled - File upload security:
- URL validation: only
http/httpsprotocols allowed (ApplicationError: Invalid URL protocol) - SSRF protection: resolved addresses checked against blocklist (
ApplicationError: URL resolves to a blocked address) - DNS resolution validation: hostnames must resolve (
ApplicationError: Could not resolve hostname) - Request timeouts: upload URL fetches have configurable timeouts (
ApplicationError: Request timed out while fetching URL) - File name validation: invalid characters rejected (
ApplicationError: File name contains invalid characters)
- URL validation: only
Caution
The upload provider validation prevents SSRF attacks by blocking requests to private IP ranges and unresolvable hostnames. Do not disable these checks in production. The AWS S3 provider logs detailed errors prefixed with
Upload AWS S3 provider:for debugging upload failures.
SSO (Enterprise)
Enterprise Edition supports Single Sign-On via configurable providers:
- Provider validation:
ValidationError: Invalid provider supplied - SSO lock: prevents local login when SSO is enforced (
Missing user objectcheck)
8. Developer Internals
Repository Structure
strapi/strapi (monorepo)
├── packages/
│ ├── core/
│ │ ├── core/ # Strapi class, Container, service registry
│ │ ├── strapi/ # CLI, Node.js build tooling
│ │ ├── admin/ # Admin panel (React SPA + Koa server)
│ │ ├── database/ # Knex-based database layer
│ │ ├── content-manager/ # CRUD, history, preview, homepage
│ │ ├── content-type-builder/ # Schema builder
│ │ ├── content-releases/ # Batch publish scheduling (EE)
│ │ ├── review-workflows/ # Review stages (EE)
│ │ ├── upload/ # Media library + AI metadata
│ │ ├── email/ # Email abstraction
│ │ ├── data-transfer/ # Import/export/remote transfer
│ │ ├── openapi/ # OpenAPI spec generation
│ │ ├── permissions/ # Permission engine
│ │ ├── types/ # TypeScript type definitions
│ │ └── utils/ # Shared utilities
│ ├── cli/
│ │ ├── create-strapi/ # `create-strapi` package (thin wrapper)
│ │ ├── create-strapi-app/ # Project scaffolding CLI
│ │ └── cloud/ # Strapi Cloud CLI
│ ├── plugins/
│ │ ├── users-permissions/ # End-user auth
│ │ ├── graphql/ # GraphQL API
│ │ ├── i18n/ # Internationalization
│ │ ├── documentation/ # API documentation
│ │ ├── sentry/ # Error tracking
│ │ ├── color-picker/ # Color field type
│ │ └── cloud/ # Cloud admin plugin
│ ├── providers/
│ │ ├── email-*/ # Email providers (5 providers)
│ │ └── upload-*/ # Upload providers (3 providers)
│ ├── generators/ # Plop-based code generators
│ └── utils/
│ ├── logger/ # Logging utilities
│ ├── typescript/ # TS codegen helpers
│ └── upgrade/ # Upgrade CLI + codemods
├── examples/
│ ├── getstarted/ # Basic starter project
│ ├── complex/ # Complex schemas example
│ ├── kitchensink/ # All field types example
│ ├── experimental-dev/ # Experimental features
│ ├── empty/ # Minimal skeleton
│ └── plugins/ # Example plugins (todo, workspace)
├── tests/
│ ├── api/ # API integration tests
│ ├── e2e/ # End-to-end tests (Playwright)
│ ├── app-template/ # Test app template
│ └── utils/ # Test utilities
├── scripts/ # Build, release, and utility scripts
├── docs/ # Contributor documentation (Docusaurus)
└── templates/
└── website/ # Website starter template
Core Architecture Deep Dive
The Strapi Class
The main Strapi class (packages/core/core/src/Strapi.ts) extends Container and is the central service registry. It provides:
- Service registration and resolution via the Container pattern
- Lifecycle management (register → bootstrap → start → destroy)
- Plugin loading and initialization
- Content type registration
- Provider factory pattern for extensible subsystems
The Container class (packages/core/core/src/container.ts) implements a simple IoC container:
class Container {
register(name, service) { /* throws if already registered */ }
resolve(name) { /* throws if not found */ }
}
Service Layer
Key core services in packages/core/core/src/services/:
| Service | File | Purpose |
|---|---|---|
| Document Service | document-service/ |
Document-level CRUD with draft/publish, locale support |
| Entity Validator | entity-validator/ |
Schema validation for content entries |
| Cron | cron.ts |
Scheduled task execution |
| Webhook Store | webhook-store.ts |
Webhook registration and event management |
| Webhook Runner | webhook-runner.ts |
Async webhook dispatch |
| Session Manager | session-manager.ts |
Database session/transaction management |
The SessionManager uses three layers:
DatabaseSessionProvider— wraps Knex transactionsOriginSessionManager— tracks session origin for auditSessionManager— top-level session coordination
Database Layer
The Database class (packages/core/database/src/index.ts) wraps Knex.js:
- Schema management with
schema/module - Connection factory supporting SQLite, PostgreSQL, MySQL
- Model registration:
Error: Model ${uid} not foundon invalid UIDs - Type mapping:
Error: Unknown type ${attribute.type}for unsupported attribute types
Content-Type Builder
The server-side Content-Type Builder (packages/core/content-type-builder/server/src/) handles:
- Schema creation and modification via admin API
- API handler with rollback support:
Error: Cannot rollback api that was not backed up - Attribute validation:
ApplicationError: component.notFoundfor invalid component references - Content type scaffolding (controllers, routes, services directories)
OpenAPI Generation
The OpenAPI module (packages/core/openapi/) auto-generates Swagger specifications using:
RouteCollector— gathers all registered routesRouteMatcher— matches routes to content types- Context factories:
DocumentContextFactory,OperationContextFactory,PathItemContextFactory,PathContextFactory - Route providers:
AdminRoutesProvider,ApiRoutesProvider,PluginRoutesProvider
Data Transfer System
The data transfer module (packages/core/data-transfer/src/) implements:
Push handler (strapi/remote/handlers/push.ts):
- Stage-based flow: init → schemas → entities → assets → links → config
- Streaming with checksum validation:
ProviderTransferError: Missing checksum for asset - Transfer state machine with atomic transitions
- WebSocket protocol for real-time transfer
Pull handler (strapi/remote/handlers/pull.ts):
- Mirror of push handler for data retrieval
- Stream creation for each transfer stage
- Asset streaming with read stream management
Error hierarchy:
DataTransferError (base)
├── ProviderError
│ ├── ProviderInitializationError
│ ├── ProviderValidationError
│ └── ProviderTransferError
Error Classes
All Strapi errors extend from base classes in packages/core/utils/src/errors.ts:
| Error Class | HTTP Status | Usage |
|---|---|---|
ApplicationError |
400 | General application errors |
ValidationError |
400 | Input validation failures |
NotFoundError |
404 | Resource not found |
ForbiddenError |
403 | Insufficient permissions |
UnauthorizedError |
401 | Authentication required |
RateLimitError |
429 | Rate limit exceeded |
PayloadTooLargeError |
413 | Request body too large |
PolicyError |
403 | Policy check failures |
NotImplementedError |
501 | Feature not implemented |
PaginationError |
400 | Invalid pagination parameters |
YupValidationError |
400 | Yup schema validation failures |
Query Parameter Conversion
The packages/core/utils/src/convert-query-params.ts module handles REST API query parameter parsing with strict validation:
| Error | Cause |
|---|---|
Invalid sort query |
Malformed sort parameter |
Field cannot be empty |
Empty field in sort/filter |
convertStartQueryParams expected a positive integer |
Negative or non-integer start |
convertLimitQueryParams expected a positive integer |
Negative or non-integer limit |
Using fragments is not permitted to populate |
Fragment syntax in populate |
Invalid nested populate |
Malformed nested populate object |
Invalid fields parameter |
Non-string/non-array fields |
The filters parameter must be an object or an array |
Invalid filter format |
Plugin Architecture
Each plugin follows a standard structure:
plugin-name/
├── admin/
│ └── src/
│ └── index.ts # Admin panel registration
├── server/
│ └── src/
│ ├── index.ts # Server-side entry (register, bootstrap, destroy)
│ ├── config/ # Plugin default configuration
│ ├── controllers/ # Route handlers
│ ├── routes/ # Route definitions
│ ├── services/ # Business logic
│ ├── models/ # Data models
│ └── utils/ # Shared utilities
└── package.json # With strapi.kind: "plugin" metadata
Plugin metadata in package.json:
{
"strapi": {
"name": "plugin-name",
"displayName": "Plugin Display Name",
"description": "Plugin description",
"kind": "plugin"
}
}
Plugin exports use the standard exports map:
{
"exports": {
"./strapi-admin": { "source": "./admin/src/index.ts" },
"./strapi-server": { "source": "./server/src/index.ts" },
"./package.json": "./package.json"
}
}
Admin Panel Architecture
The admin panel (packages/core/admin/admin/src/) is a React SPA using:
StrapiAppclass — main application bootstrap, plugin registration, routing- React Router v6 — client-side routing
- Redux Toolkit — global state management with
adminApi(RTK Query) @strapi/design-system— component library- React DnD — drag-and-drop for content ordering
- react-query — server state management
Key features:
- RBAC with condition-based permissions (
rulesEngine.ts) - Guided tour system
- Injection zones for plugin UI integration
- Custom field registration
- Theme support (light/dark)
Note
The admin panel mounts at
/adminby default. If the root element is not found, it throwsError: [@strapi/admin]: Could not find the root element to mount the admin app. Ensure your hosting serves the admin HTML shell.
Code Generators
Strapi uses Plop.js (packages/generators/generators/) for scaffolding:
- Content type generators
- Plugin generators
- API generators
Generator utilities validate config: Error: Invalid config: singularName and type are required.
TypeScript Support
The packages/utils/typescript/ module generates TypeScript definitions:
- Model type generation from content type schemas
- Attribute type mapping
- Common model interfaces
The type generator handles: Error: typeof min/max values mismatch (type validation) and Error: Cannot convert to object literal. Unknown type (unsupported attribute types).
Testing Strategy
| Test Type | Tool | Command | Scope |
|---|---|---|---|
| Unit tests | Jest 29, Vitest | yarn test:unit |
Individual modules |
| Frontend tests | Jest + Testing Library | yarn test:front |
React components |
| API integration | Jest + Supertest | yarn test:api |
REST API endpoints |
| E2E tests | Playwright 1.56 | yarn test:e2e |
Full browser workflows |
| CLI tests | Custom runner | yarn test:cli |
CLI commands |
| TypeScript | tsc --noEmit | yarn test:ts |
Type checking |
Edition-specific testing:
# Test Community Edition only
yarn test:e2e:ce
# Test Enterprise Edition
yarn test:e2e:ee
Tip
The test app template (
tests/app-template/) provides a pre-configured Strapi project with complex content type schemas (articles, authors, categories, shops, teams, countries, conditions, etc.) for comprehensive integration testing.
Contributing
Setup the monorepo:
git clone https://github.com/strapi/strapi.git
cd strapi
yarn setup # install, clean, build
Development workflow:
# Watch all packages for changes
yarn watch
# Run a specific example
cd examples/getstarted
yarn develop
# Run tests
yarn test:unit
yarn test:front
Commit conventions: Strapi uses Commitlint with conventional commits (@commitlint/config-conventional). Use yarn commit for interactive commit message creation.
Code style:
- ESLint with Airbnb + TypeScript configs
- Prettier for formatting
- Lint-staged with Husky pre-commit hooks
- File naming conventions enforced via
eslint-plugin-check-file
9. Integration Points
Upload Providers
Custom upload providers must implement the provider interface:
module.exports = {
init(providerOptions) {
return {
async upload(file) { /* upload file, set file.url */ },
async uploadStream(file) { /* stream upload variant */ },
async delete(file) { /* delete file */ },
checkFileSize(file, sizeLimit) { /* validate file size */ },
// Optional:
async isPrivate() { /* return boolean */ },
async getSignedUrl(file) { /* return signed URL for private files */ },
};
},
};
The upload system validates that providers expose the required methods: Error: The upload provider must implement the following methods: upload, uploadStream, delete.
Email Providers
Custom email providers implement:
module.exports = {
init(providerOptions, settings) {
return {
async send(options) {
// options: { to, from, subject, text, html }
},
// Optional:
async verifyConnection() {
// Throws if connection fails
},
};
},
};
Connection verification: ApplicationError: This email provider does not support connection verification if verifyConnection is not implemented.
GraphQL Integration
The GraphQL plugin (@strapi/plugin-graphql) auto-generates:
- Queries for all content types (find, findOne)
- Mutations (create, update, delete)
- Type definitions from content type schemas
- Relay-style pagination
- Filter input types
Configuration:
// config/plugins.js
module.exports = {
graphql: {
config: {
endpoint: '/graphql',
shadowCRUD: true,
playgroundAlways: false,
depthLimit: 10,
amountLimit: 100,
},
},
};
Content API Custom Parameters
Register custom query and body parameters for the Content API:
// src/index.js
module.exports = {
register({ strapi }) {
// Custom query parameter with Zod validation
strapi.contentAPI.addQueryParams({
customParam: {
schema: (z) => z.string().max(200).optional(),
matchRoute: (route) => route.path.includes('articles'),
},
});
// Custom body parameter
strapi.contentAPI.addInputParams({
clientMutationId: {
schema: (z) => z.string().max(100).optional(),
},
});
},
};
Webhook Events
Supported webhook events:
| Event | Trigger |
|---|---|
entry.create |
New content entry created |
entry.update |
Content entry updated |
entry.delete |
Content entry deleted |
entry.publish |
Content entry published |
entry.unpublish |
Content entry unpublished |
Custom events can be registered. Invalid events produce: ValidationError: Webhook event ${event} is not supported.
Strapi Cloud CLI
The Cloud CLI (@strapi/cloud-cli) provides:
| Command | Description |
|---|---|
strapi deploy |
Deploy project to Strapi Cloud |
strapi cloud login |
Authenticate with Strapi Cloud |
strapi cloud logout |
Clear authentication |
The CLI uses browser-based OAuth for authentication, stores tokens locally via xdg-app-paths, and communicates with the Strapi Cloud API for project management, environment configuration, and deployment.
Cloud API errors:
| Error | Cause |
|---|---|
Error fetching cloud CLI config from the server |
Network/auth issue with cloud API |
Error fetching cloud projects from the server |
Cannot list projects |
Error fetching cloud environments from the server |
Cannot list environments |
Error creating trial |
Trial creation failed |
No configuration data found to patch |
Missing local project config |