Skip to content

Strapi REST API — CRUD Endpoints, Filtering, Sorting, and Pagination

DodaTech Updated 2026-06-28 8 min read

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

  1. Forgetting the data wrapper. POST and PUT request bodies must have a data wrapper. Sending { "title": "Hello" } instead of { "data": { "title": "Hello" } } returns a 400 error.

  2. Not populating relations. Expecting related data to appear in the response without using ?populate=relationName. Relations are excluded by default.

  3. 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.

  4. 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.

  5. 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

  1. What is the API endpoint to list all articles sorted by title in descending order? Answer: GET /api/articles?sort=title:desc

  2. How 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]=100

  3. What is the difference between page-based and offset-based pagination? Answer: Page-based uses page and pageSize parameters with pageCount in the meta. Offset-based uses start and limit parameters without page count. Page-based is for paginators. Offset-based is for infinite scroll.

  4. 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

What is the maximum page size?

The default maximum page size is 100. This can be changed in Strapi's server configuration under api.responses.pagination.maxPageSize. Setting it too high can cause performance issues.

How do I handle errors in the REST API?

Strapi returns standard HTTP status codes: 200 (success), 201 (created), 400 (validation error), 401 (unauthorized), 403 (forbidden), 404 (not found), 500 (server error). Error responses include a data and error object with details.

Can I disable specific API endpoints?

Yes. In the Content-Type Builder, you can disable individual actions (create, find, findOne, update, delete) per content type. Disabled endpoints return 404.

Does Strapi support API versioning?

Strapi does not have built-in API versioning. You can implement it by creating separate Strapi instances for different API versions or by using a reverse proxy that rewrites URL paths.

How do I send authentication in API requests?

Include the JWT token in the Authorization header: Authorization: Bearer <jwt_token>. Tokens are obtained through the auth endpoints or created as API tokens in the admin settings.

Mini Project

Your task: Build a complete REST API client for your Strapi backend.

  1. 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
  2. Observe the response format for each operation.
  3. 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:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro