obsidian/wiki/payloadcms/local-api.md
2026-05-15 16:14:29 +01:00

268 lines
8.3 KiB
Markdown

---
title: "PayloadCMS — Local API"
aliases: [local-api, payload-local-api, payload-server-api]
tags: [payloadcms, local-api, server-side, nextjs, typescript]
sources:
- raw/local-api__overview.md
- https://payloadcms.com/docs/local-api/overview
- https://payloadcms.com/docs/local-api/outside-nextjs
- https://payloadcms.com/docs/local-api/server-functions
- https://payloadcms.com/docs/local-api/access-control
created: 2026-05-15
updated: 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 import `getPayload`
- **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
```ts
// 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
```ts
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
```ts
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
```ts
const result = await payload.findByID({
collection: 'posts',
id: '507f1f77bcf86cd799439011',
depth: 2,
disableErrors: true, // returns null instead of throwing
})
```
### Count / FindDistinct
```ts
const { totalDocs } = await payload.count({ collection: 'posts', where: {} })
const { values } = await payload.findDistinct({
collection: 'posts',
field: 'title',
sort: 'title',
})
```
### Update (by ID or bulk)
```ts
// 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)
```ts
await payload.delete({ collection: 'posts', id: '...' })
await payload.delete({ collection: 'posts', where: { fieldName: { equals: 'v' } } })
```
## Auth Operations
```ts
// 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
```ts
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:
```ts
'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`)
```ts
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)
```ts
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 `.env` the same way Next.js does (no `dotenv` needed)
- 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
```ts
// 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:
```ts
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: true` silences `findByID` errors; use carefully in production
- `payload run` handles `.env` loading; avoid `dotenv` alongside 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 `File` object into `data` before calling `payload.create()`
- `beforeRead`/`afterRead` hooks may not receive full doc when `select` is active — use entity-level `select` config 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 set `overrideAccess: false` + `user` for user-facing logic
- **Two access patterns:** `req.payload` (in hooks/access control) vs `await getPayload({ config })` (everywhere else)
- **Thread `req` through** all operations when using Postgres or MongoDB replica sets (transactions)
- **Full TypeScript inference** — `payload.create({ collection: 'posts', data: { ... } })` returns typed `Post`
- **`pagination: false`** skips count queries — useful for internal data fetching where counts aren't needed
- **`disableErrors: true`** makes `findByID` return `null` instead of throwing — use cautiously in production
- **`context`** passes extra metadata to hooks without polluting document data (e.g. `triggerBeforeChange` flag)
- **Server functions** (`'use server'`) are the bridge for using Local API from client-triggered actions in Next.js
- **`payload run`** CLI handles `.env` automatically for standalone scripts — no need for `dotenv`
## Related
- [[wiki/payloadcms/queries|Queries]]
- [[wiki/payloadcms/rest-api|REST API]]
- [[wiki/payloadcms/hooks|Hooks]]
- [[wiki/payloadcms/authentication-operations|Authentication — Operations]]
- [[wiki/payloadcms/local-api-outside-nextjs|Local API — Outside Next.js]]
- [[wiki/payloadcms/local-api-access-control|Local API — Access Control]]
- [[wiki/payloadcms/database-transactions|Database — Transactions]]