Docs/Routing & Data/Validation

Routing & Data

Validation

Validation — Routing & Data documentation for BCP Framework.

Last updated: 2026-08-29T01:11:41

Validation

BCP Framework 0.1.19 adds a built-in validation system through bcp/validation.

The module is universal and can be used in server actions, API routes, loaders and client-side code.

Basic schema

TypeScript
import {
    v,
} from "bcp/validation";

const userSchema =
    v.object({
        name:
            v.string({
                trim: true,
                minLength: 2,
            }),
        email:
            v.string({
                trim: true,
                email: true,
            }),
        age:
            v.number({
                coerce: true,
                integer: true,
                min: 18,
            }),
        role:
            v.enum([
                "admin",
                "user",
            ]),
    });

Safe parsing

TypeScript
const result =
    userSchema.safeParse(input);

if (!result.success) {
    console.log(
        result.fieldErrors
    );
    return;
}

console.log(result.data);

Successful results have:

TypeScript
{
    success: true,
    data: value,
}

Failures have:

TypeScript
{
    success: false,
    issues: [...],
    fieldErrors: {
        email: [
            "Must be a valid email address.",
        ],
    },
    formErrors: [],
}

Nested field paths are flattened with dot notation, for example profile.email and items.0.name.

Form actions

validateFormData() converts repeated FormData keys into arrays and validates the resulting object.

TypeScript
import {
    validateFormData,
    v,
} from "bcp/validation";

const schema =
    v.object({
        email:
            v.string({
                trim: true,
                email: true,
            }),
        age:
            v.number({
                coerce: true,
                min: 18,
            }),
    });

export async function saveUser(
    formData: FormData
) {
    const result =
        validateFormData(
            schema,
            formData
        );

    if (!result.success) {
        return result;
    }

    // result.data is typed and validated.
    // await db.execute(...)

    return {
        success: true,
        user: result.data,
    };
}

The failure object is serializable and can be returned from a BCP form action directly.

API routes

TypeScript
import {
    v,
} from "bcp/validation";

const schema =
    v.object({
        email:
            v.string({
                email: true,
            }),
    });

export async function POST(
    request: Request
) {
    const input =
        await request.json();
    const result =
        schema.safeParse(
            input
        );

    if (!result.success) {
        return Response.json(
            result,
            {
                status: 422,
            }
        );
    }

    return Response.json({
        user: result.data,
    });
}

Throwing parse

Use parse() when invalid input should throw a ValidationError.

TypeScript
import {
    ValidationError,
    parse,
} from "bcp/validation";

try {
    const user =
        parse(
            userSchema,
            input
        );
} catch (error) {
    if (
        error instanceof
        ValidationError
    ) {
        console.log(
            error.fieldErrors
        );
    }
}

Available validators

TypeScript
v.string()
v.number()
v.boolean()
v.literal("active")
v.enum(["admin", "user"])
v.array(v.string())
v.object({ ... })
v.union([ ... ])

Optional and nullable values:

TypeScript
v.string().optional()
v.string().nullable()

v.optional(v.string())
v.nullable(v.string())

String validation

TypeScript
v.string({
    trim: true,
    minLength: 2,
    maxLength: 100,
    email: true,
    pattern: /^[A-Z]/,
})

Number validation

TypeScript
v.number({
    coerce: true,
    integer: true,
    min: 1,
    max: 100,
})

coerce: true is useful for HTML forms because FormData values are strings.

Boolean validation

TypeScript
v.boolean({
    coerce: true,
})

Boolean coercion recognizes common form values including true, false, 1, 0, on, off, yes and no.

Custom rules

Use refine() for application-specific rules.

TypeScript
const password =
    v.string({
        minLength: 8,
    }).refine(
        (value) =>
            /\d/.test(value),
        "Password must contain a number.",
        "password_number"
    );

Unknown object fields

Object validation strips fields that are not declared in the schema by default.

This is useful for API and form input because untrusted extra fields do not automatically pass into database writes.

To preserve unknown fields explicitly:

TypeScript
v.object(
    {
        name: v.string(),
    },
    {
        allowUnknown: true,
    }
)

Validation paths and errors

Each issue contains:

TypeScript
{
    path: [
        "profile",
        "email",
    ],
    message:
        "Must be a valid email address.",
    code:
        "invalid_email",
}

Use getFieldError() when only the first message for a field is needed.

TypeScript
import {
    getFieldError,
} from "bcp/validation";

const emailError =
    getFieldError(
        result,
        "email"
    );

Type inference

Use InferValidator when application code needs the TypeScript output type of a schema.

TypeScript
import type {
    InferValidator,
} from "bcp/validation";

type UserInput =
    InferValidator<
        typeof userSchema
    >;

External schema libraries

BCP 0.1.19 does not require Zod, Valibot or another validation dependency. The built-in API keeps framework validation dependency-free.

Adapters for external schema libraries can be added in future releases without changing the validation result model used by actions and API routes.