strapi
$
npx mdskill add vm0-ai/vm0-skills/strapiQuery Strapi CMS content types and manage headless data.
- Retrieves specific content entries from custom CMS schemas.
- Requires Strapi API access and authentication tokens.
- Discovers available content types before executing operations.
- Returns structured JSON data matching requested content types.
SKILL.md
.github/skills/strapiView on GitHub ↗
---
name: strapi
description: Strapi CMS REST API for headless content management. Use when user mentions
"Strapi", "CMS", "content types", "entries", or managing headless CMS content.
---
## Troubleshooting
If requests fail, run `zero doctor check-connector --env-name STRAPI_TOKEN` or `zero doctor check-connector --url https://docs.strapi.io/cms/api/rest --method GET`
## IMPORTANT: Discovery Workflow
**Before performing any content operations, always discover the user's content types first.** Every Strapi instance has different content types, fields, and relationships. Never assume what types exist.
### Step 1: Discover Content Types
```bash
curl -s "$STRAPI_BASE_URL/api/content-type-builder/content-types" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq '[.data[] | select(.schema.visible != false) | {uid: .uid, name: .schema.displayName, kind: .schema.kind, attributes: [.schema.attributes | to_entries[] | {name: .key, type: .value.type, required: .value.required}]}]'
```
This returns all content types with their fields. Key properties:
- `uid`: The unique identifier (e.g., `api::article.article`)
- `kind`: Either `collectionType` (multiple entries) or `singleType` (one entry)
- `attributes`: Fields with their types (`string`, `richtext`, `media`, `relation`, `enumeration`, etc.)
### Step 2: Identify the API ID
The API endpoint path is derived from the content type's plural name. For `api::article.article`, the endpoint is `/api/articles`. Common pattern:
- Collection type `article` → `GET /api/articles`
- Single type `homepage` → `GET /api/homepage`
If unsure, check the content type info from Step 1 — the `uid` follows the pattern `api::{singularName}.{singularName}`.
### Step 3: Proceed with Operations
Once you know the content types and their fields, proceed with the appropriate CRUD operations below.
## Collection Types (Multiple Entries)
### List Entries
```bash
curl -s "$STRAPI_BASE_URL/api/PLURAL_API_ID" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq .
```
### List with Pagination, Sort, and Filter
```bash
curl -s "$STRAPI_BASE_URL/api/PLURAL_API_ID?pagination[page]=1&pagination[pageSize]=25&sort=createdAt:desc&filters[fieldName][\$eq]=value" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq .
```
### Get Single Entry
```bash
curl -s "$STRAPI_BASE_URL/api/PLURAL_API_ID/DOCUMENT_ID" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq .
```
### Get Entry with Relations Populated
Use `populate` to include related data:
```bash
# Populate all first-level relations
curl -s "$STRAPI_BASE_URL/api/PLURAL_API_ID/DOCUMENT_ID?populate=*" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq .
# Populate specific relations
curl -s "$STRAPI_BASE_URL/api/PLURAL_API_ID/DOCUMENT_ID?populate[0]=category&populate[1]=author" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq .
# Deep populate nested relations
curl -s "$STRAPI_BASE_URL/api/PLURAL_API_ID/DOCUMENT_ID?populate[author][populate]=avatar" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq .
```
### Create Entry
Write to `/tmp/strapi_request.json`:
```json
{
"data": {
"title": "My New Entry",
"description": "Entry content here",
"category": 1
}
}
```
Then run:
```bash
curl -s -X POST "$STRAPI_BASE_URL/api/PLURAL_API_ID" \
--header "Authorization: Bearer $STRAPI_TOKEN" \
--header "Content-Type: application/json" \
-d @/tmp/strapi_request.json | jq .
```
### Update Entry
Write to `/tmp/strapi_request.json`:
```json
{
"data": {
"title": "Updated Title"
}
}
```
Then run:
```bash
curl -s -X PUT "$STRAPI_BASE_URL/api/PLURAL_API_ID/DOCUMENT_ID" \
--header "Authorization: Bearer $STRAPI_TOKEN" \
--header "Content-Type: application/json" \
-d @/tmp/strapi_request.json | jq .
```
### Delete Entry
```bash
curl -s -X DELETE "$STRAPI_BASE_URL/api/PLURAL_API_ID/DOCUMENT_ID" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq .
```
## Single Types (One Entry)
### Get Single Type
```bash
curl -s "$STRAPI_BASE_URL/api/SINGULAR_API_ID" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq .
```
### Update Single Type
Write to `/tmp/strapi_request.json`:
```json
{
"data": {
"title": "Updated Homepage Title",
"seo": {
"metaTitle": "Home",
"metaDescription": "Welcome"
}
}
}
```
Then run:
```bash
curl -s -X PUT "$STRAPI_BASE_URL/api/SINGULAR_API_ID" \
--header "Authorization: Bearer $STRAPI_TOKEN" \
--header "Content-Type: application/json" \
-d @/tmp/strapi_request.json | jq .
```
## Media Upload
### Upload a File
```bash
curl -s -X POST "$STRAPI_BASE_URL/api/upload" \
--header "Authorization: Bearer $STRAPI_TOKEN" \
-F "files=@/path/to/file.jpg" | jq '.[0] | {id, name, url, mime}'
```
### Upload and Attach to Entry
```bash
curl -s -X POST "$STRAPI_BASE_URL/api/upload" \
--header "Authorization: Bearer $STRAPI_TOKEN" \
-F "files=@/path/to/image.jpg" \
-F "ref=api::article.article" \
-F "refId=DOCUMENT_ID" \
-F "field=cover" | jq .
```
## Filtering Reference
Strapi uses operator-based filtering:
| Operator | Description | Example |
|----------|-------------|---------|
| `$eq` | Equal | `filters[title][$eq]=Hello` |
| `$ne` | Not equal | `filters[title][$ne]=Hello` |
| `$lt` | Less than | `filters[price][$lt]=100` |
| `$lte` | Less than or equal | `filters[price][$lte]=100` |
| `$gt` | Greater than | `filters[price][$gt]=50` |
| `$gte` | Greater than or equal | `filters[price][$gte]=50` |
| `$in` | In array | `filters[id][$in][0]=1&filters[id][$in][1]=2` |
| `$contains` | Contains (case-sensitive) | `filters[title][$contains]=word` |
| `$containsi` | Contains (case-insensitive) | `filters[title][$containsi]=word` |
| `$startsWith` | Starts with | `filters[title][$startsWith]=He` |
| `$null` | Is null | `filters[image][$null]=true` |
| `$notNull` | Is not null | `filters[image][$notNull]=true` |
### Combining Filters
```bash
# AND: multiple filters on different fields
curl -s "$STRAPI_BASE_URL/api/articles?filters[status][\$eq]=published&filters[category][\$eq]=news" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq .
# OR: use $or operator
curl -s "$STRAPI_BASE_URL/api/articles?filters[\$or][0][status][\$eq]=published&filters[\$or][1][status][\$eq]=draft" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq .
```
## Pagination
```bash
# Page-based (default)
curl -s "$STRAPI_BASE_URL/api/articles?pagination[page]=1&pagination[pageSize]=25" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq '.meta.pagination'
# Offset-based
curl -s "$STRAPI_BASE_URL/api/articles?pagination[start]=0&pagination[limit]=25" \
--header "Authorization: Bearer $STRAPI_TOKEN" | jq .
```
Response includes pagination metadata:
```json
{
"meta": {
"pagination": {
"page": 1,
"pageSize": 25,
"pageCount": 4,
"total": 100
}
}
}
```
## Guidelines
1. **Always discover first**: Run the content type discovery endpoint before performing operations — never guess content type names or field names
2. **Wrap data in `data` key**: All create and update payloads must wrap the fields in a `data` object
3. **Use `populate` for relations**: By default, relations are not included in responses. Use `populate=*` for all first-level relations, or specify specific fields
4. **Check response format**: Successful responses return `{ data: { id, attributes, ... }, meta: { ... } }`
5. **Error format**: Errors return `{ error: { status, name, message, details } }`
6. **API token scope**: The operations available depend on the token type (Full Access, Read-Only, or Custom permissions per content type)
7. **Document IDs**: Strapi v5 uses document IDs (not numeric IDs) for CRUD operations
8. **Sort syntax**: Use `sort=field:asc` or `sort=field:desc`. Multiple sorts: `sort[0]=field1:asc&sort[1]=field2:desc`
## API Reference
- REST API: `https://docs.strapi.io/cms/api/rest`
- Filtering: `https://docs.strapi.io/cms/api/rest/filters-locale-publication`
- Population: `https://docs.strapi.io/cms/api/rest/populate-select`
- API Tokens: `https://docs.strapi.io/cms/features/api-tokens`