Composable
Mount validators and nested Containers on any path. Stay flat or nest as deep as the input demands.
Composable, path-based validation for TypeScript. Mount validators on any path, run them in groups, and collect structured issues — without decorators or schema DSLs.
{
"success": false,
"issues": [
{
"type": "item",
"path": [
"email"
],
"message": "Must look like an email address",
"code": "value_invalid"
},
{
"type": "item",
"path": [
"password"
],
"message": "Must be at least 8 characters",
"code": "value_invalid"
}
]
} Each keystroke re-runs the Container and re-renders the structured Issue[] — same model that powers integrations with zod, express-validator, Routup, and Vue.
A small core (Container · Validator · Issue) — and everything else falls out from how you compose it.
Mount validators and nested Containers on any path. Stay flat or nest as deep as the input demands.
Mount via dotted paths (a.b.c), brackets (foo[0]), or globs (**.foo) — expansion is handled by pathtrace.
Run different validations for create / update / custom groups from the same container — no schema duplication.
Discriminated Issue items and groups with code, path, message, expected, received — ready for any UI.
Pure JS, ESM-only. Runs in Node, browsers, Deno, Bun, and edge runtimes — no Node-specific deps.
Container<T, C> propagates output shape and a typed user context all the way through nested mounts.
validup keeps a pure-JS core. Drop in a thin adapter to bridge your favorite validator or the framework you already ship with.
Bridge to any Standard Schema library — zod 3.24+, valibot, arktype, effect-schema, …
Vendor-specific bridge for zod 3 / 4 — surfaces expected & received fields on each issue.
Drop existing express-validator chains into a Container — no rewrite required.
Run a Container against a routup HTTP request — body, cookies, params, or query.
Vue 3 composable: vuelidate-shaped reactive form state driven by safeRun().
Three steps. No decorators, no schema DSL, no build-time codegen.
npm install validup@validup/vue
A single composable turns any Container into reactive form state. Errors gate on dirty paths, debounce on keystrokes, and abort cleanly when the user keeps typing.
// SignupForm.vue
import { Container } from 'validup';
import { useValidup } from '@validup/vue';
import { reactive } from 'vue';
const signup = new Container<{ email: string; password: string }>();
signup.mount('email', isEmail);
signup.mount('password', isStrongPassword);
const state = reactive({ email: '', password: '' });
const v = useValidup(signup, state, { debounce: 200 });
async function submit() {
const result = await v.$validate();
if (result.success) save(result.data);
}