Strapi REST API — CRUD Endpoints, Filtering, Sorting, and Pagination
In this tutorial, you will learn how to use Strapi's auto-generated REST API to perform CRUD operations, filter content with query parameters, sort results, and paginate through large datasets efficiently.
What You'll Learn
- The REST API endpoint structure for collection and single types
- How to create, read, update, and delete entries via API
- How to filter content using comparison operators
- How to sort results by one or multiple fields
- How pagination works with page-based and offset-based methods
- How to structure request bodies for mutations
Why It Matters
The REST API is how your frontend communicates with Strapi. Every endpoint is auto-generated from your content types, meaning your data model directly determines your API surface. Understanding the REST API thoroughly lets you build efficient frontends that fetch exactly what they need without over- or under-fetching.
Real-World Use
A React-based recipe website needs to show 10 recipes per page, sorted by publish date, filtered by cuisine type, and including the author's name. With Strapi's REST API, the frontend makes a single request with the right query parameters and receives the exact data needed. No custom backend code required.
Learning Path
flowchart LR A["Content Lifecycle"] --> B["REST API
-- You are here"]:::current B --> C["API Parameters"] C --> D["GraphQL Setup"] D --> E["API Customization"] E --> F["API Security"] classDef current fill:#4945ff,color:#fff,stroke-width:2px
Endpoint Structure
Strapi generates endpoints based on your content type names. The pattern follows REST conventions.
Collection type "Article" (singular: article, plural: articles):
GET /api/articles — List all articles
GET /api/articles/:id — Get one article
POST /api/articles — Create an article
PUT /api/articles/:id — Update an article
DELETE /api/articles/:id — Delete an article
Single type "Homepage":
GET /api/homepage — Get the homepage
PUT /api/homepage — Update the homepage
DELETE /api/homepage — Delete the homepage (not common)
The base URL for the API is http://localhost:1337/api in development. In production, this is your domain plus the API path.
Reading Content (GET)
Listing entries returns an array wrapped in a data object with pagination metadata.
// GET /api/articles
// Response:
{
"data": [
{
"id": 1,
"attributes": {
"title": "Article 1",
"content": "Content here",
"createdAt": "2026-06-28T...",
"updatedAt": "2026-06-28T..."
}
},
{
"id": 2,
"attributes": {
"title": "Article 2",
"content": "Content here",
"createdAt": "2026-06-28T...",
"updatedAt": "2026-06-28T..."
}
}
],
"meta": {
"pagination": {
"page": 1,
"pageSize": 25,
"pageCount": 1,
"total": 2
}
}
}
Getting a single entry returns the same format but with a single data object:
// GET /api/articles/1
// Response:
{
"data": {
"id": 1,
"attributes": {
"title": "Article 1",
// ... other fields
}
},
"meta": {}
}
Creating Content (POST)
Create new entries by sending a POST request with the data in the request body.
// POST /api/articles
// Headers: Content-Type: application/json
// Body:
{
"data": {
"title": "New Article",
"content": "Article content",
"author": 1 // Relation ID
}
}
// Response (201 Created):
{
"data": {
"id": 3,
"attributes": {
"title": "New Article",
"content": "Article content",
"createdAt": "2026-06-28T..."
}
},
"meta": {}
}
The data wrapper is required in the request body. Strapi validates the input against the content type schema and returns validation errors with 400 status if the data is invalid.
Updating Content (PUT)
Update existing entries by sending a PUT request. You only need to include the fields you want to change.
// PUT /api/articles/3
// Body:
{
"data": {
"title": "Updated Title"
// Other fields remain unchanged
}
}
// Response (200 OK):
{
"data": {
"id": 3,
"attributes": {
"title": "Updated Title",
"content": "Article content", // Unchanged
// ...
}
}
}
You might be wondering about the difference between PUT and PATCH. Strapi uses PUT and treats it as a partial update (PATCH behavior). You do not need to send all fields.
Deleting Content (DELETE)
// DELETE /api/articles/3
// Response (200 OK):
{
"data": {
"id": 3,
"attributes": {
"title": "Updated Title",
// ... all fields before deletion
}
},
"meta": {}
}
The delete response returns the deleted entry data. This is useful for showing a confirmation or undo action in your frontend.
Filtering Content
Filters let you query content based on field values. The filter syntax uses operators with the filters query parameter.
// Basic filter: exact match
// GET /api/articles?filters[title]=Hello
// GET /api/articles?filters[title][$eq]=Hello
// Comparison filters:
// GET /api/articles?filters[views][$gte]=1000
// GET /api/articles?filters[price][$lt]=50
// GET /api/articles?filters[createdAt][$gt]=2026-01-01
// String filters:
// GET /api/articles?filters[title][$containsi]=strapi
// GET /api/articles?filters[title][$startsWith]=How
// GET /api/articles?filters[title][$endsWith]=Guide
// Array filters:
// GET /api/articles?filters[id][$in]=1,3,5
// GET /api/articles?filters[id][$notIn]=2,4
// Null filters:
// GET /api/articles?filters[author][$null]=true
// GET /api/articles?filters[author][$notNull]=true
The i suffix on operators like $containsi means case-insensitive. The operators ending in i do case-insensitive matching.
Combining Filters
You can combine multiple filters. By default, filters are AND conditions.
// AND condition - all must match
// GET /api/articles?filters[title][$containsi]=strapi&filters[views][$gte]=100
// Returns articles with "strapi" in title AND 100+ views
// OR condition (using $or):
// GET /api/articles?filters[$or][0][title][$containsi]=strapi&filters[$or][1][title][$containsi]=cms
// Returns articles with "strapi" OR "cms" in title
Sorting
Sort results with the sort parameter. Sort by one field or multiple fields.
// Sort by one field ascending
// GET /api/articles?sort=title
// Sort by one field descending
// GET /api/articles?sort=title:desc
// Sort by multiple fields
// GET /api/articles?sort[0]=createdAt:desc&sort[1]=title:asc
// First sorts by createdAt descending, then by title ascending
Ascending is the default if no direction is specified.
Pagination
Strapi supports two pagination methods: page-based and offset-based.
// Page-based pagination (default):
// GET /api/articles?pagination[page]=1&pagination[pageSize]=10
// Response meta:
"meta": {
"pagination": {
"page": 1,
"pageSize": 10,
"pageCount": 5,
"total": 50
}
}
// Offset-based pagination:
// GET /api/articles?pagination[start]=0&pagination[limit]=10
// Response meta:
"meta": {
"pagination": {
"start": 0,
"limit": 10,
"total": 50
}
}
Page-based pagination is easier for building UI paginators. Offset-based pagination is useful for Infinite Scroll implementations. The default page size is 25.
Populating Relations
As covered in the relations lesson, related data is not included by default. Use the populate parameter.
// Single relation
// GET /api/articles?populate=author
// Multiple relations
// GET /api/articles?populate=author,tags,category
// Nested population
// GET /api/articles?populate[author][populate]=profile
// Populate all (use sparingly)
// GET /api/articles?populate=*
Selecting Specific Fields
Control which fields are returned with the fields parameter:
// Return only id and title
// GET /api/articles?fields[0]=title&fields[1]=createdAt
// Response:
{
"data": [
{
"id": 1,
"attributes": {
"title": "Article 1",
"createdAt": "2026-06-28T..."
}
}
]
}
The id is always returned regardless of the fields parameter.
Common Mistakes
Forgetting the data wrapper. POST and PUT request bodies must have a
datawrapper. Sending{ "title": "Hello" }instead of{ "data": { "title": "Hello" } }returns a 400 error.Not populating relations. Expecting related data to appear in the response without using
?populate=relationName. Relations are excluded by default.Over-filtering with complex nested conditions. Building deeply nested filter conditions makes the API request hard to debug and the database query slow. Keep filters simple.
Assuming sort works on all field types. Sorting works on most field types but may not work as expected on JSON fields or dynamic zones. Test sort behavior with your data.
Not handling pagination in the frontend. Without pagination parameters, the API returns the first 25 entries. Build pagination logic into your frontend to load all data or implement infinite scroll.
Practice Questions
What is the API endpoint to list all articles sorted by title in descending order? Answer:
GET /api/articles?sort=title:descHow do you filter articles that contain "Strapi" in the title and have more than 100 views? Answer:
GET /api/articles?filters[title][$containsi]=strapi&filters[views][$gt]=100What is the difference between page-based and offset-based pagination? Answer: Page-based uses
pageandpageSizeparameters withpageCountin the meta. Offset-based usesstartandlimitparameters without page count. Page-based is for paginators. Offset-based is for infinite scroll.Challenge: Write a complete frontend API integration that: (1) Fetches the first page of 10 articles with author and tags populated, sorted by publish date descending, (2) Filters to only articles in the "JavaScript" category, (3) Displays the article titles, author names, and tag names, (4) Includes pagination controls to navigate between pages.
FAQ
Mini Project
Your task: Build a complete REST API client for your Strapi backend.
- Using curl, Postman, or a script, perform the following operations:
- Create 5 articles with different titles, content, and publish dates
- Update the title of the third article
- Fetch all articles sorted by createdAt descending
- Fetch articles filtered by title containing a specific word
- Fetch a single article with its relations populated
- Delete the second article
- Observe the response format for each operation.
- Write a JavaScript function that performs each API call using fetch and handles errors gracefully.
What's Next
Now that you understand the REST API, proceed to API Parameters to learn advanced querying techniques including the populate parameter, field selection, locale filtering, and publication state. After that, set up GraphQL as an alternative API layer.
Related lessons:
- REST API Design — General REST best practices
- GraphQL — Alternative to REST
- Node.js — How Strapi serves APIs
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro