Skip to main content

serverConvex

Call Convex from a Nitro request with explicit authentication policy and request-scoped state.

serverConvex(event) creates one request-scoped caller with query, mutation, and action methods.

server/api/projects.get.ts
import { api } from '#convex/api'
import { serverConvex } from '#convex/server'

export default defineEventHandler(async (event) => {
  return await serverConvex(event, { auth: 'required' }).query(api.projects.list, {})
})

Authentication modes

ModeCookie behavior
requiredExchange the request session; throw if no valid identity exists
optionalUse identity when valid, otherwise call anonymously
noneDo not inspect or exchange auth cookies

The server default is optional when no explicit principal is supplied.

Caller lifetime

One caller lazily owns:

  • one token-resolution promise;
  • one official ConvexHttpClient;
  • one current request identity snapshot.

Do not store a caller in module scope or on a cross-request cache. Create it inside the handler.

Explicit principal

An operator-controlled server workflow may provide an already obtained Convex JWT:

ts
const caller = serverConvex(event, {
  authToken: trustedToken,
})

Or exchange an explicit Better Auth cookie/bearer credential:

ts
const caller = serverConvex(event, {
  credential: { type: 'bearer', value: sessionToken },
})

authToken and credential are mutually exclusive. Either one forces required auth. Combining an explicit principal with optional or none is a synchronous programming error.

Never accept these values from an untrusted request body and forward them blindly.

Errors

Calls reject with ConvexCallError for classifiable auth, transport, and structured Convex server failures. Invalid options throw ServerConvexValidationError before network access.

A rejected caller keeps its rejected token/client state. Create a new caller for an intentional retry.

No client hydration

A serverConvex result returned from your API route is ordinary route data. It does not seed useConvexQuery or establish a subscription.

Use a composable directly in a page for normal SSR-to-live data. Use a server route when a real server boundary is required.