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

183 lines
5.8 KiB
Markdown

---
title: "Local API — Server Functions"
aliases: [server-functions, server-actions-payload, payload-server-actions]
tags: [payloadcms, local-api, server-functions, next-js, authentication]
sources: [raw/local-api__server-functions.md]
created: 2026-05-15
updated: 2026-05-15
---
## Overview
**Server functions** (formerly "server actions") are Next.js-native async functions marked with `'use server'` that run exclusively on the server. They bridge the gap between client components and Payload's [[wiki/payloadcms/local-api|Local API]], which cannot be called directly from client-side code.
## Why Use Server Functions
- **Local API is server-only** — client components can't call `payload.*` directly; server functions act as the secure bridge
- **No extra endpoints needed** — avoids creating REST/GraphQL routes just for simple CRUD operations
- **Security** — restricts exposed operations to only what the function explicitly allows
- **Performance** — Next.js optimizes caching, DB queries, and network overhead automatically
## Basic Pattern
```ts
// server/actions.ts
'use server'
import { getPayload } from 'payload'
import config from '@payload-config'
export async function createPost(data) {
const payload = await getPayload({ config })
try {
return await payload.create({ collection: 'posts', data })
} catch (error) {
throw new Error(`Error creating post: ${error.message}`)
}
}
```
```ts
// components/PostForm.tsx
'use client'
import { createPost } from '../server/actions'
// call like any async function from onClick/onSubmit
const newPost = await createPost({ title: 'Hello' })
```
## Common Operations
### Create Document
```ts
'use server'
export async function createPost(data) {
const payload = await getPayload({ config })
return await payload.create({ collection: 'posts', data })
}
```
### Update Document
```ts
'use server'
export async function updatePost(id, data) {
const payload = await getPayload({ config })
return await payload.update({ collection: 'posts', id, data })
}
```
### Authenticate User (check session)
```ts
'use server'
import { headers as getHeaders } from 'next/headers'
export const authenticateUser = async () => {
const payload = await getPayload({ config })
const headers = await getHeaders()
const { user } = await payload.auth({ headers })
return user ? { hello: user.email } : { hello: 'Not authenticated' }
}
```
### File Upload
Pass the file as a second argument and merge it into the data:
```ts
'use server'
export async function createPostWithUpload(data, upload) {
const payload = await getPayload({ config })
return await payload.create({
collection: 'posts',
data: { ...data, media: upload },
})
}
```
## Built-in Auth Server Functions
`@payloadcms/next/auth` exports ready-made server functions for auth operations that manage cookies automatically — no need to handle tokens manually.
| Function | Import | Purpose |
|----------|--------|---------|
| `login` | `@payloadcms/next/auth` | Verify credentials, set auth cookie |
| `logout` | `@payloadcms/next/auth` | Clear auth cookie + sessions |
| `refresh` | `@payloadcms/next/auth` | Refresh token + session |
**Pattern** — wrap each in a thin `'use server'` helper (config can't be imported in client components):
```ts
'use server'
import { login } from '@payloadcms/next/auth'
import config from '@payload-config'
export async function loginAction({ email, password }: { email: string; password: string }) {
return await login({ collection: 'users', config, email, password })
}
```
```ts
'use server'
import { logout } from '@payloadcms/next/auth'
import config from '@payload-config'
export async function logoutAction() {
return await logout({ allSessions: true, config })
}
```
## Error Handling
- Always wrap Local API calls in `try/catch`
- Return structured error objects — don't expose raw errors to the frontend
- Log server-side for debugging
```ts
export async function createPost(data) {
try {
const payload = await getPayload({ config })
return await payload.create({ collection: 'posts', data })
} catch (error) {
console.error('Error creating post:', error)
return { error: 'Failed to create post' }
}
}
```
## Security
- **Access control**: Check `user.role` before sensitive operations
- **Avoid leaking data**: Never return passwords, tokens, or sensitive fields
- Use Payload's `UnauthorizedError` for clean rejection:
```ts
import { UnauthorizedError } from 'payload'
export async function deletePost(postId, user) {
if (!user || user.role !== 'admin') throw new UnauthorizedError()
const payload = await getPayload({ config })
return await payload.delete({ collection: 'posts', id: postId })
}
```
## Key Takeaways
- `'use server'` at the top of the file (or function) marks it as a server function
- `getPayload({ config })` is the entry point — import `config` from `@payload-config`
- All [[wiki/payloadcms/local-api|Local API]] operations (`create`, `update`, `delete`, `find`, `auth`) work inside server functions
- Built-in auth helpers (`login`/`logout`/`refresh`) from `@payloadcms/next/auth` handle cookie complexity
- Server functions replace the need for custom [[wiki/payloadcms/rest-api|REST API]] endpoints for internal operations
- Always return the result — don't just run the operation and discard
- Use `overrideAccess: false` + pass `user` if you want [[wiki/payloadcms/local-api-access-control|access control]] enforced
- For file uploads, pass the `File` object as a separate argument and merge into data
## Related
- [[wiki/payloadcms/local-api|Local API — Overview]]
- [[wiki/payloadcms/local-api-access-control|Local API — Access Control]]
- [[wiki/payloadcms/local-api-outside-nextjs|Local API — Outside Next.js]]
- [[wiki/payloadcms/authentication-operations|Authentication — Operations]]
- [[wiki/payloadcms/rest-api|REST API]]