--- tags: [payloadcms, tech-patterns] topic: payloadcms sources: - https://payloadcms.com/docs/rest-api/overview created: 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|Queries]]) | | `joins` | `object` | Custom request per join field | ## Code Examples ### Basic fetch ```ts // 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 ```ts 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 ```ts 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: ```ts import { PayloadSDK } from '@payloadcms/sdk' import type { Config } from './payload-types' const sdk = new PayloadSDK({ 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 ```ts await sdk.request({ method: 'POST', path: '/send-data', json: { id: 1 } }) ``` ## Custom Endpoints Add `endpoints` array to collection, global, or root config: ```ts 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 ```ts 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`: ```ts 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 - [[wiki/payloadcms/queries|Queries]] - [[wiki/payloadcms/local-api|Local API]]