All articles
ProductJuly 10, 2026· 6 min read

Formify: type-safe validation without the boilerplate

CerebrixDevelopment Collective

Validation libraries are either bloated or full of magic. Formify is neither: zero dependencies, full type inference, and one schema that works on the client and the server. Here's how it actually feels to use.

Most form validation setups share the same shape: a schema library, a runtime dependency tree, and a types file that drifts from both. Formify collapses that into a single dependency-free module where the schema itself is the type.

One schema, everywhere

The same schema you use to validate a signup form on the client validates the request body on your server. No duplication, no drift — the types flow straight from the schema into your editor.

import { schema, string, email, number } from "formify";

const user = schema({
  name: string(),
  email: email(),
  age: number().min(18),
});

const result = user.validate(input);
// result.data is fully typed — no annotations needed

Reading the result

Validation returns a result, not a throw. That means you decide what happens next — render errors in the UI, return a 400 from an API, or both — with the same shape on both sides:

if (!result.success) {
  // result.errors is a readable list, ready for your form
  return res.status(400).json({ errors: result.errors });
}

// result.data is your validated, typed value
createUser(result.data);

Error messages are written for humans: "email must be a valid email address", not opaque codes. Map them straight into your form fields and you're done.

Custom rules are just functions

No plugin system, no inheritance dance. A rule is a function, so your domain logic composes naturally:

import { string } from "formify";

const username = string()
  .min(3)
  .custom((value) =>
    /^[a-z0-9_]+$/.test(value)
      ? null
      : "usernames can only contain lowercase letters, numbers, and underscores"
  );

Async rules (checking if an email is already taken) work the same way — the rule returns a promise and Formify awaits it.

Composing with any form library

Formify handles the validation layer, so it composes cleanly with whatever manages your form state. Use it to validate values before submit, or wire its errors into React Hook Form, Formik, or plain useState — the API doesn't care about your framework, and neither does the bundle.

Small by design

Formify ships with zero runtime dependencies and a footprint under 3kb. It runs in browsers, Node.js, Deno, Bun, and edge runtimes — because validation shouldn't be the reason your bundle grows.

Read the docs to define your first schema, or grab the source from the Formify page.

Have a project in mind?

We build web apps, mobile apps, and browser extensions. Let's talk about yours.

Get in touch