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

5.8 KiB

title aliases tags sources created updated
Local API — Server Functions
server-functions
server-actions-payload
payload-server-actions
payloadcms
local-api
server-functions
next-js
authentication
raw/local-api__server-functions.md
2026-05-15 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, 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

// 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}`)
  }
}
// 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

'use server'
export async function createPost(data) {
  const payload = await getPayload({ config })
  return await payload.create({ collection: 'posts', data })
}

Update Document

'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)

'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:

'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):

'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 })
}
'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
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:
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 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 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 enforced
  • For file uploads, pass the File object as a separate argument and merge into data