Form Wire
Core Concepts

Server Actions

Type-safe server actions with createAction

Form Wire provides createAction for Next.js server actions that validate against your Zod schema and return typed results.

createAction

import { createAction } from "@form-wire/core";
import { z } from "zod";

const schema = z.object({
  email: z.email(),
  password: z.string().min(8),
});

export const login = createAction(schema, async (data) => {
  // data is typed as { email: string; password: string }
  const user = await authenticate(data);
  return { data: { userId: user.id } };
});

Return Types

createAction returns an action compatible with React's useActionState. It returns ActionState<TResult>:

type ActionState<TResult> =
  | { success: true; data?: TResult; message?: string }
  | { success: false; fieldErrors: Record<string, string[]>; formErrors: string[] };

Success

return { data: { id: 1 } };          // ActionSuccess with data
return { message: "Saved!" };         // ActionSuccess with message only
return {};                            // ActionSuccess, no data/message

Failure

return { formErrors: ["Something went wrong"] };
return { fieldErrors: { email: ["Already taken"] } };

formError Helper

For quick error responses:

import { formError } from "@form-wire/core";

export const register = createAction(schema, async (data) => {
  if (await emailExists(data.email)) {
    return formError({
      fieldErrors: {
        email: ["Email already registered"],
      },
    });
  }
  // ...
});

Throwing Errors

If your action throws a FormWireServerError, it's caught and converted to a failure state:

import { FormWireServerError } from "@form-wire/core";

throw new FormWireServerError({
  fieldErrors: { email: ["Invalid"] },
  formErrors: ["Please fix the errors above"],
});

With Next.js

// app/login/action.ts
"use server";
import { createAction } from "@form-wire/core";

export const login = createAction(schema, async (data) => {
  // ...
});

// app/login/page.tsx
import { FormWireProvider, htmlMapper, createFormWire } from "@form-wire/react";
import { login } from "./action";

const Login = createFormWire(schema, { /* config */ });

export default function LoginPage() {
  return (
    <FormWireProvider mapper={htmlMapper}>
      <Login.Form action={login} />
    </FormWireProvider>
  );
}

The form automatically displays field errors and form errors from the action result.

On this page