| tags |
topic |
sources |
created |
|
|
payloadcms |
|
2026-05-15 |
PayloadCMS — REST API
Overview
Payload auto-generates a fully functional REST API from Collection and Global configs. All routes are prefixed with /api (configurable via routes.api).
- Collection slugs must be kebab-case
- Supports: depth, locale, fallback-locale, select, populate, limit, page, sort, where, joins
- Type-safe client available via
@payloadcms/sdk (beta)
Collection Endpoints
| Method |
Path |
Operation |
GET |
/api/{slug} |
Find (paginated) |
GET |
/api/{slug}/{id} |
Find by ID |
GET |
/api/{slug}/count |
Count documents |
POST |
/api/{slug} |
Create |
PATCH |
/api/{slug} |
Update many (with where) |
PATCH |
/api/{slug}/{id} |
Update by ID |
DELETE |
/api/{slug} |
Delete many (with where) |
DELETE |
/api/{slug}/{id} |
Delete by ID |
Auth Endpoints (auth-enabled collections)
| Method |
Path |
Operation |
POST |
/api/{slug}/login |
Login — returns JWT + user |
POST |
/api/{slug}/logout |
Logout |
POST |
/api/{slug}/refresh-token |
Refresh JWT |
GET |
/api/{slug}/me |
Current user |
POST |
/api/{slug}/forgot-password |
Trigger password reset |
POST |
/api/{slug}/reset-password |
Apply new password with token |
POST |
/api/{slug}/verify/{token} |
Verify email |
POST |
/api/{slug}/unlock |
Unlock locked account |
Global Endpoints
| Method |
Path |
Operation |
GET |
/api/globals/{slug} |
Get global |
POST |
/api/globals/{slug} |
Update global |
Query Parameters
| Param |
Type |
Description |
depth |
number |
Relationship population depth (default: 2) |
locale |
string |
Return doc in a specific locale |
fallback-locale |
string |
Fallback locale |
select |
object |
Field projection (select[title]=true) |
populate |
object |
Override defaultPopulate for related docs |
limit |
number |
Docs per page (default: 10) |
page |
number |
Page number |
sort |
string |
Field name, prefix - for descending, comma-separated for multi |
where |
object |
Filter query (see [[wiki/payloadcms/queries |
joins |
object |
Custom request per join field |
Code Examples
Basic fetch
// Find published posts, sorted newest first
const res = await fetch('/api/posts?where[status][equals]=published&sort=-createdAt&limit=10')
const { docs, totalDocs, totalPages } = await res.json()
Complex query with qs-esm
import { stringify } from 'qs-esm'
const query = stringify({
where: { color: { equals: 'mint' } },
select: { title: true, slug: true },
sort: '-createdAt',
limit: 20,
}, { addQueryPrefix: true })
const res = await fetch(`/api/posts${query}`)
Auth: login and use token
const { token } = await fetch('/api/users/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'dev@example.com', password: 'secret' }),
}).then(r => r.json())
// Authenticated request
const res = await fetch('/api/posts', {
headers: { Authorization: `JWT ${token}` },
})
PayloadCMS SDK (@payloadcms/sdk)
Type-safe REST client, API similar to Local API:
import { PayloadSDK } from '@payloadcms/sdk'
import type { Config } from './payload-types'
const sdk = new PayloadSDK<Config>({ baseURL: 'https://example.com/api' })
// Operations
await sdk.find({ collection: 'posts', limit: 10, where: { _status: { equals: 'published' } } })
await sdk.findByID({ collection: 'posts', id })
await sdk.create({ collection: 'posts', data: { text: 'hello' } })
await sdk.update({ collection: 'posts', id, data: { text: 'updated' } })
await sdk.delete({ collection: 'posts', id })
await sdk.findGlobal({ slug: 'header' })
await sdk.updateGlobal({ slug: 'header', data: { text: 'new' } })
// Auth operations
await sdk.login({ collection: 'users', data: { email, password } })
await sdk.me({ collection: 'users' }, { headers: { Authorization: `JWT ${token}` } })
await sdk.refreshToken({ collection: 'users' }, { headers: { Authorization: `JWT ${token}` } })
Custom endpoint
await sdk.request({ method: 'POST', path: '/send-data', json: { id: 1 } })
Custom Endpoints
Add endpoints array to collection, global, or root config:
export const Orders: CollectionConfig = {
slug: 'orders',
endpoints: [
{
path: '/:id/tracking',
method: 'get',
handler: async (req) => {
const tracking = await getTrackingInfo(req.routeParams.id)
if (!tracking) return Response.json({ error: 'not found' }, { status: 404 })
return Response.json(tracking)
},
},
],
}
Custom endpoint helpers
import { addDataAndFileToRequest, addLocalesToRequestFromData, headersWithCors } from 'payload'
// Parse body + file
await addDataAndFileToRequest(req) // populates req.data and req.file
// Parse locale params
await addLocalesToRequestFromData(req) // populates req.locale and req.fallbackLocale
// CORS headers
return Response.json(data, { headers: headersWithCors({ headers: new Headers(), req }) })
Method Override (long GET queries)
When query strings are too long, use POST with X-Payload-HTTP-Method-Override: GET:
const res = await fetch('/api/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Payload-HTTP-Method-Override': 'GET',
},
body: qs.stringify({ depth: 1, locale: 'en' }),
})
Gotchas
- Custom endpoints are not authenticated by default — add
if (!req.user) checks manually
req.data is not auto-populated in custom endpoints — call await addDataAndFileToRequest(req) first
req.locale / req.fallbackLocale are not populated in custom endpoints — use addLocalesToRequestFromData
- Custom endpoints with
root: true bypass routes.api — only valid on top-level Payload config, not on collections/globals
- SDK is in beta — may have breaking changes in minor versions
limit: 0 automatically disables pagination
Related