8.3 KiB
8.3 KiB
| title | aliases | tags | sources | created | updated | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| PayloadCMS — Local API |
|
|
2026-05-15 | 2026-05-15 |
PayloadCMS — Local API
Overview
The Local API executes the same operations as REST/GraphQL but directly against the database — no HTTP layer, no latency. Ideal for React Server Components, seed scripts, hooks, and custom route handlers.
- Access via
req.payload(in hooks/access control) or importgetPayload - Access control is skipped by default (
overrideAccess: true) - Full TypeScript inference from generated types
- Works in Next.js (with HMR) and standalone scripts
Accessing Payload
// Option 1: from hook/access control args
const afterChangeHook: CollectionAfterChangeHook = async ({ req: { payload } }) => {
const posts = await payload.find({ collection: 'posts' })
}
// Option 2: import and initialize
import { getPayload } from 'payload'
import config from '@payload-config'
const payload = await getPayload({ config })
Collection Operations
Create
const post = await payload.create({
collection: 'posts',
data: { title: 'sure', description: 'maybe' },
locale: 'en',
overrideAccess: true,
filePath: path.resolve(__dirname, './image.jpg'), // for upload collections
duplicateFromID: 'existing-doc-id', // optional clone
})
Find
const result = await payload.find({
collection: 'posts',
depth: 2,
page: 1,
limit: 10,
pagination: false, // skip count queries
where: { status: { equals: 'published' } },
sort: '-createdAt',
locale: 'en',
overrideAccess: false,
user: dummyUser,
})
Find by ID
const result = await payload.findByID({
collection: 'posts',
id: '507f1f77bcf86cd799439011',
depth: 2,
disableErrors: true, // returns null instead of throwing
})
Count / FindDistinct
const { totalDocs } = await payload.count({ collection: 'posts', where: {} })
const { values } = await payload.findDistinct({
collection: 'posts',
field: 'title',
sort: 'title',
})
Update (by ID or bulk)
// By ID
await payload.update({ collection: 'posts', id: '...', data: { title: 'new' } })
// Bulk — returns { docs: [], errors: [] }
await payload.update({
collection: 'posts',
where: { fieldName: { equals: 'value' } },
data: { title: 'updated' },
overrideLock: false, // enforce document locks
})
Delete (by ID or bulk)
await payload.delete({ collection: 'posts', id: '...' })
await payload.delete({ collection: 'posts', where: { fieldName: { equals: 'v' } } })
Auth Operations
// Login
const { token, user, exp } = await payload.login({
collection: 'users',
data: { email: 'dev@payload.com', password: 'secret' },
})
// Current user from headers
const { user, permissions } = await payload.auth({ headers, canSetHeaders: false })
// Password reset flow
const token = await payload.forgotPassword({ collection: 'users', data: { email: '...' } })
const result = await payload.resetPassword({ collection: 'users', data: { password: 'new', token } })
// Verify email
await payload.verifyEmail({ collection: 'users', token: '...' })
await payload.unlock({ collection: 'users', data: { email: '...' } })
Global Operations
const header = await payload.findGlobal({ slug: 'header', depth: 2 })
await payload.updateGlobal({
slug: 'header',
data: { nav: [{ url: 'https://example.com' }] },
overrideLock: false,
})
Server Functions (Next.js)
Local API cannot be called from client components directly. Use 'use server' functions as a bridge:
'use server'
import { getPayload } from 'payload'
import config from '@payload-config'
export async function createPost(data) {
const payload = await getPayload({ config })
return await payload.create({ collection: 'posts', data })
}
Built-in auth server functions (@payloadcms/next/auth)
import { login, logout, refresh } from '@payloadcms/next/auth'
// login — sets auth cookie
await login({ collection: 'users', config, email, password })
// logout — clears auth cookie
await logout({ allSessions: true, config })
// refresh — rotates JWT
await refresh({ config })
Outside Next.js (Standalone Scripts)
import { getPayload } from 'payload'
import config from '@payload-config'
const seed = async () => {
const payload = await getPayload({ config })
await payload.create({ collection: 'users', data: { email: 'dev@example.com', password: 'pw' } })
}
await seed()
Run with: payload run src/seed.ts
- Loads
.envthe same way Next.js does (nodotenvneeded) - Flags:
--use-swc(faster, needs@swc-node/register),--disable-transpile(for Bun)
Key Options Reference
| Option | Default | Description |
|---|---|---|
overrideAccess |
true |
Skip access control checks |
user |
— | User to check against (when overrideAccess: false) |
depth |
2 |
Relationship auto-population depth |
locale / fallbackLocale |
— | Localization |
select |
— | Field projection |
populate |
— | Override defaultPopulate for related docs |
pagination |
true |
Set false to skip count queries |
showHiddenFields |
false |
Return hidden fields |
overrideLock |
true |
Ignore document locks |
disableErrors |
false |
Return null/empty instead of throwing |
disableTransaction |
false |
Skip DB transaction |
context |
— | Pass extra data to hooks via req.context |
Access Control
// Skip access control (default)
await payload.create({ collection: 'users', data: { ... } })
// Enforce access control
await payload.create({
collection: 'users',
overrideAccess: false,
user, // authenticated user document
data: { ... },
})
Transactions
Pass req through all local operations when using Postgres or MongoDB replica sets:
const post = await payload.find({ collection: 'posts', req })
Gotchas
- Access control is off by default — easy to accidentally expose privileged data in seed scripts
disableErrors: truesilencesfindByIDerrors; use carefully in productionpayload runhandles.envloading; avoiddotenvalongside it- Server functions must use
'use server'directive and can't directly import Payload config in client components - For file uploads in server functions, merge the
Fileobject intodatabefore callingpayload.create() beforeRead/afterReadhooks may not receive full doc whenselectis active — use entity-levelselectconfig to force fields
Key Takeaways
- No HTTP overhead — Local API hits the DB directly; ideal for RSC, seeds, hooks, custom route handlers
- Access control off by default (
overrideAccess: true) — always setoverrideAccess: false+userfor user-facing logic - Two access patterns:
req.payload(in hooks/access control) vsawait getPayload({ config })(everywhere else) - Thread
reqthrough all operations when using Postgres or MongoDB replica sets (transactions) - Full TypeScript inference —
payload.create({ collection: 'posts', data: { ... } })returns typedPost pagination: falseskips count queries — useful for internal data fetching where counts aren't neededdisableErrors: truemakesfindByIDreturnnullinstead of throwing — use cautiously in productioncontextpasses extra metadata to hooks without polluting document data (e.g.triggerBeforeChangeflag)- Server functions (
'use server') are the bridge for using Local API from client-triggered actions in Next.js payload runCLI handles.envautomatically for standalone scripts — no need fordotenv