---
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]]
