createServerFn

Create a server function that can be called from both server and client code. On the server, the handler is called directly. On the client, the Vite plugin transforms the call into an RPC request to an auto-generated API endpoint.

Import

import { createServerFn } from "catmint/server";

Signature

function createServerFn<TInput, TOutput>(
  handler: (input: TInput) => Promise<TOutput> | TOutput,
  options?: ServerFnOptions<TInput>,
): ServerFunction<TInput, TOutput>;

Parameters

ParameterTypeRequiredDescription
handler(input: TInput) => Promise<TOutput> | TOutputYesThe server-side handler function.
optionsServerFnOptions<TInput>NoConfiguration options for method and validation.

ServerFnOptions<T>

interface ServerFnOptions<T = unknown> {
  method?: "GET" | "POST" | "PUT" | "DELETE";
  validate?: StandardSchemaV1 | ((input: unknown) => T);
}
FieldTypeDefaultDescription
method'GET' | 'POST' | 'PUT' | 'DELETE''GET'HTTP method for the auto-generated RPC endpoint.
validateStandardSchemaV1 | ((input: unknown) => T)Input validation. Accepts any Standard Schema object (Zod, Valibot, ArkType, etc.) or a plain validation function that throws on invalid input.

Return Value

Returns a ServerFunction<TInput, TOutput> — an async callable function with attached metadata used by the Vite plugin for build-time transformation.

type ServerFunction<TInput, TOutput> = ((input: TInput) => Promise<TOutput>) &
  ServerFnMetadata;

interface ServerFnMetadata {
  __serverFn: true;
  __method: "GET" | "POST" | "PUT" | "DELETE";
  __hasValidation: boolean;
}

Runtime Behavior

Call siteBehavior
Server code (SSR, other server fns)Direct function invocation. No HTTP overhead.
Client code (browser)Compiled into a fetch() call to an auto-generated endpoint.

The function name is hashed at compile time to produce a stable, opaque identifier for the RPC endpoint (e.g., /__catmint/fn/data/a1b2c3d4).

Examples

import { createServerFn } from "catmint/server";

// Basic server function
export const getUser = createServerFn(async (id: string) => {
  return db.users.findById(id);
});
import { createServerFn } from "catmint/server";
import { z } from "zod";

// With Standard Schema validation (Zod)
const userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

export const createUser = createServerFn(
  async (data: z.infer<typeof userSchema>) => {
    return db.users.create(data);
  },
  {
    method: "POST",
    validate: userSchema,
  },
);
import { createServerFn } from "catmint/server";

// With a plain validation function
export const updateSettings = createServerFn(
  async (input: { theme: string }) => {
    await db.settings.update(input);
  },
  {
    method: "PUT",
    validate: (input: unknown) => {
      if (typeof input !== "object" || input === null) {
        throw new Error("Input must be an object");
      }
      return input as { theme: string };
    },
  },
);
// UserProfile.client.tsx — calling from a client component
import { getUser } from "./data.fn";

function UserProfile({ id }: { id: string }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    getUser(id).then(setUser);
  }, [id]);

  return <div>{user?.name}</div>;
}

See Also