Overview
Strapi is the leading open-source headless CMS built with Node.js. It provides an auto-generated REST and GraphQL API, a customizable admin panel for content editors, and a flexible plugin system. Strapi lets you define content types through the admin UI or code, and it automatically generates CRUD endpoints for each type.
Strapi supports PostgreSQL, MySQL, MariaDB, and SQLite as database backends. It includes a role-based access control system, media library with image optimization, internationalization (i18n), and webhook integrations. Content can be delivered to any frontend framework, mobile app, or IoT device through its API.
Installation
# Create a new Strapi project
npx create-strapi-app@latest my-project
# With specific database
npx create-strapi-app@latest my-project --dbclient=postgres
# Quick start with SQLite (no prompts)
npx create-strapi-app@latest my-project --quickstart
# Start development server
cd my-project
npm run develop
# Admin panel: http://localhost:1337/admin
# API: http://localhost:1337/api
Docker
# docker-compose.yml
version: "3"
services:
strapi:
image: strapi/strapi:latest
environment:
DATABASE_CLIENT: postgres
DATABASE_HOST: db
DATABASE_PORT: 5432
DATABASE_NAME: strapi
DATABASE_USERNAME: strapi
DATABASE_PASSWORD: strapi
ports:
- "1337:1337"
volumes:
- ./app:/srv/app
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_DB: strapi
POSTGRES_USER: strapi
POSTGRES_PASSWORD: strapi
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Core Commands
| Command | Description |
|---|
npm run develop | Start in development mode with auto-reload |
npm run start | Start in production mode |
npm run build | Build the admin panel |
npm run strapi generate | Generate components, APIs, plugins |
npm run strapi routes:list | List all registered routes |
npm run strapi ts:generate-types | Generate TypeScript types |
npm run strapi configuration:dump | Export configuration |
npm run strapi configuration:restore | Import configuration |
npm run strapi transfer | Transfer data between instances |
REST API
CRUD Operations
# List entries (with pagination)
curl "http://localhost:1337/api/articles?pagination[page]=1&pagination[pageSize]=25"
# Get single entry
curl "http://localhost:1337/api/articles/1"
# Create entry
curl -X POST "http://localhost:1337/api/articles" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer JWT_TOKEN" \
-d '{"data":{"title":"New Article","content":"Article body","publishedAt":"2026-01-01T00:00:00.000Z"}}'
# Update entry
curl -X PUT "http://localhost:1337/api/articles/1" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer JWT_TOKEN" \
-d '{"data":{"title":"Updated Title"}}'
# Delete entry
curl -X DELETE "http://localhost:1337/api/articles/1" \
-H "Authorization: Bearer JWT_TOKEN"
Filtering and Population
# Filter results
curl "http://localhost:1337/api/articles?filters[title][$contains]=hello"
# Deep filtering
curl "http://localhost:1337/api/articles?filters[category][name][$eq]=tech"
# Populate relations
curl "http://localhost:1337/api/articles?populate=*"
# Nested population
curl "http://localhost:1337/api/articles?populate[author][populate]=avatar"
# Sort and pagination
curl "http://localhost:1337/api/articles?sort=createdAt:desc&pagination[page]=1&pagination[pageSize]=10"
# Select specific fields
curl "http://localhost:1337/api/articles?fields[0]=title&fields[1]=slug"
Filter Operators
| Operator | Example | Description |
|---|
$eq | filters[status][$eq]=published | Equal |
$ne | filters[status][$ne]=draft | Not equal |
$lt / $lte | filters[price][$lt]=100 | Less than |
$gt / $gte | filters[price][$gt]=10 | Greater than |
$in | filters[status][$in][0]=published | In array |
$notIn | filters[status][$notIn][0]=archived | Not in array |
$contains | filters[title][$contains]=hello | Contains substring |
$startsWith | filters[title][$startsWith]=How | Starts with |
$null | filters[deletedAt][$null]=true | Is null |
Configuration
Database Configuration
// config/database.js
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", "strapi"),
ssl: env.bool("DATABASE_SSL", false),
},
},
})
Server Configuration
// config/server.js
module.exports = ({ env }) => ({
host: env("HOST", "0.0.0.0"),
port: env.int("PORT", 1337),
url: env("PUBLIC_URL", "http://localhost:1337"),
app: {
keys: env.array("APP_KEYS"),
},
})
Plugin Configuration
// config/plugins.js
module.exports = ({ env }) => ({
upload: {
config: {
provider: "aws-s3",
providerOptions: {
s3Options: {
credentials: {
accessKeyId: env("AWS_ACCESS_KEY_ID"),
secretAccessKey: env("AWS_ACCESS_SECRET"),
},
region: env("AWS_REGION"),
params: { Bucket: env("AWS_BUCKET") },
},
},
},
},
email: {
config: {
provider: "sendgrid",
providerOptions: {
apiKey: env("SENDGRID_API_KEY"),
},
settings: {
defaultFrom: "noreply@example.com",
defaultReplyTo: "support@example.com",
},
},
},
graphql: {
config: {
defaultLimit: 25,
maxLimit: 100,
},
},
})
Advanced Usage
Custom Controllers
// src/api/article/controllers/article.js
const { createCoreController } = require("@strapi/strapi").factories
module.exports = createCoreController("api::article.article", ({ strapi }) => ({
async find(ctx) {
const { data, meta } = await super.find(ctx)
// Add custom logic
return { data, meta }
},
async customAction(ctx) {
const entries = await strapi.entityService.findMany("api::article.article", {
filters: { publishedAt: { $notNull: true } },
sort: { createdAt: "desc" },
limit: 10,
})
return entries
},
}))
Custom Routes
// src/api/article/routes/custom.js
module.exports = {
routes: [
{
method: "GET",
path: "/articles/featured",
handler: "article.customAction",
config: {
auth: false,
},
},
],
}
Lifecycle Hooks
// src/api/article/content-types/article/lifecycles.js
module.exports = {
async beforeCreate(event) {
const { data } = event.params
if (data.title) {
data.slug = data.title.toLowerCase().replace(/\s+/g, "-")
}
},
async afterCreate(event) {
const { result } = event
await strapi.service("api::notification.notification").send({
message: `New article: ${result.title}`,
})
},
}
Webhooks
// Configure in Admin > Settings > Webhooks
// Or programmatically
// Triggers: entry.create, entry.update, entry.delete, entry.publish, entry.unpublish
Troubleshooting
| Issue | Solution |
|---|
| Admin panel blank after build | Run npm run build to rebuild the admin; clear browser cache |
| Database connection refused | Verify database credentials and host; ensure DB service is running |
| API returning 403 | Check permissions in Settings > Roles; enable public access for the endpoint |
| Slow API responses | Add indexes to database; use populate selectively instead of populate=* |
| Upload fails | Check file size limits in config/plugins.js; verify storage permissions |
| TypeScript types outdated | Run npm run strapi ts:generate-types after content type changes |
| Migration errors after upgrade | Back up database first; follow Strapi migration guides for version jumps |
| GraphQL not available | Install plugin: npm run strapi install graphql |
| i18n content missing | Enable i18n on the content type; create content for each locale |