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
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
const result =
userSchema.safeParse(input);
if (!result.success) {
console.log(
result.fieldErrors
);
return;
}
console.log(result.data);Successful results have:
{
success: true,
data: value,
}Failures have:
{
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.
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
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.
import {
ValidationError,
parse,
} from "bcp/validation";
try {
const user =
parse(
userSchema,
input
);
} catch (error) {
if (
error instanceof
ValidationError
) {
console.log(
error.fieldErrors
);
}
}Available validators
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:
v.string().optional()
v.string().nullable()
v.optional(v.string())
v.nullable(v.string())String validation
v.string({
trim: true,
minLength: 2,
maxLength: 100,
email: true,
pattern: /^[A-Z]/,
})Number validation
v.number({
coerce: true,
integer: true,
min: 1,
max: 100,
})coerce: true is useful for HTML forms because FormData values are strings.
Boolean validation
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.
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:
v.object(
{
name: v.string(),
},
{
allowUnknown: true,
}
)Validation paths and errors
Each issue contains:
{
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.
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.
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.