Three years ago, every TypeScript codebase that needed validation pinned itself to a single library. Picking Zod meant your form library, your RPC layer, and your config loader all had to accept Zod schemas, or you wrote adapters. Picking Valibot for bundle size meant rewriting those integrations. The choice was sticky in a way that had nothing to do with the validation itself.
That stickiness is gone. As of late 2025, Standard Schema is implemented by Zod, Valibot, ArkType, Effect Schema, and a long tail of smaller validators. tRPC accepts it. Hono accepts it. TanStack Form accepts it. react-hook-form has a resolver. The schema you hand a form library is no longer coupled to the library you picked for validation.
This sounds like plumbing. It is plumbing. But the plumbing change rearranged what choosing a validator actually costs, and it exposed a few opinions that the previous decade of validation libraries had quietly baked in.
The interface everyone independently invented
Every validation library converges on roughly the same shape. You hand it a value. It either returns a typed value or returns errors. The contract is small enough that you could write it on a napkin.
The reason this had not been standardized was sociological, not technical. Zod shipped first, won, and became the de facto interface. Library authors integrating validation wrote z.ZodTypeAny into their public types, which leaked Zod into every downstream consumer. When Valibot arrived in 2023 with a tree-shakeable, function-based API and dramatically smaller bundles, it had to ship a Zod-shaped adapter just to participate in the existing ecosystem.
ArkType went further. It runs at the type level and parses string-DSL schemas into compile-time types. Its runtime is also fast, but the API does not look like Zod at all. Integrating ArkType into the original tRPC required users to write a wrapper that translated ArkType's output to Zod's parse shape.
Colin McDonnell, David Blass, and Fabian Hiller, the authors of Zod, ArkType, and Valibot respectively, did the obvious thing. They sat down and agreed on a minimum interface.
What the spec actually says
Standard Schema is one TypeScript file. The spec defines a single property name, ~standard, which any validator can attach to its schema objects. Inside that property is a validate function, the vendor name, and the spec version.
interface StandardSchemaV1<Input = unknown, Output = Input> {
readonly "~standard": {
readonly version: 1;
readonly vendor: string;
readonly validate: (value: unknown) =>
| Result<Output>
| Promise<Result<Output>>;
readonly types?: {
readonly input: Input;
readonly output: Output;
};
};
}
type Result<Output> =
| { readonly value: Output; readonly issues?: undefined }
| { readonly issues: ReadonlyArray<Issue> };
interface Issue {
readonly message: string;
readonly path?: ReadonlyArray<PropertyKey | PathSegment>;
}The tilde prefix on ~standard is intentional. It pushes the property to the bottom of IDE autocomplete lists, signaling that you are not meant to call it directly. Library authors call it. Application code calls schema.parse() or schema.safeParse() or whatever the validator's idiomatic surface is.
That is the entire spec. No transformations, no async refinements, no error formatting, no JSON Schema export. Each validator keeps its own surface for those. The ~standard property is purely the integration seam.
Who shipped, and when
Adoption happened faster than most cross-library specs. The validator side moved first, since each maintainer only had to add a small property to their existing schema objects.
| Library | Standard Schema support | Notes |
|---|---|---|
| Zod | v3.24 and v4 | Backported to the v3 line so existing apps did not need a major upgrade. |
| Valibot | v0.31 and v1.0 | Designed around the spec; v1 stabilized the integration. |
| ArkType | v2.0 | First release after the v1 rewrite to add the property. |
| Effect Schema | Effect 3.x | Schema implements ~standard alongside Effect's own Schema API. |
| @sinclair/typebox | Optional adapter | Not on schema objects directly; provided as a wrapper helper. |
The consumer side took longer, because library maintainers had to decide whether to keep their existing validator-specific overloads or replace them. Most chose to add Standard Schema as an additional accepted input, not a replacement.
tRPC v11 accepts any Standard Schema validator on procedure inputs. Hono added an sValidator middleware. TanStack Form added a Standard Schema adapter alongside its Zod and Valibot ones. The @hookform/resolvers package ships a standardSchemaResolver that works against anything implementing the spec.
None of this required coordination beyond the spec itself. Once the property landed on schema objects, library authors who wanted to accept it could, and the ones who did not bother were left with a slowly aging integration story.
Why this changes the choice between validators
Before Standard Schema, the validator you picked was a wedge into your stack. Once it had spread to your form library, your API contracts, your env loader, and a half-dozen utility shims, swapping it cost weeks. The lock-in was not the validator's API. It was the network of downstream code shaped to that API.
Now the wedge is the schemas themselves, and only those. Migrating from Zod to Valibot in 2026 means rewriting your schemas. It does not mean rewriting your tRPC routes, your forms, your middleware, or your config parsing. Those keep working because they accept anything Standard-Schema-shaped.

That shifts the comparison criteria. The questions that used to matter, like which validator your favorite framework integrates with, mostly do not. The questions that remain are about the validator itself: bundle size, runtime performance, ergonomic surface for the schemas you actually write, error message shape, and TypeScript inference behavior on complex unions.
Bundle size is where Valibot is hardest to ignore. A schema that imports object, string, and number pulls only those functions. A Zod schema imports the whole z namespace and gets tree-shaken less aggressively because of how method chaining preserves references. For a marketing site validating a single contact form, that gap can be the difference between a few kilobytes and tens of kilobytes of shipped JavaScript.
ArkType is the only one of the three that does meaningful work at the type level. Its parser turns string literals into types, which means a schema like type({ email: "string.email", age: "number > 0" }) produces both a runtime validator and a precise TypeScript type without inference helpers. The trade-off is compile-time cost. ArkType-heavy codebases sometimes notice the type checker slow down, which is the kind of problem that does not show up in a runtime benchmark.
Zod keeps the largest mindshare and the most extensions. Its v4 line dropped a lot of the old technical debt around discriminated unions and reworked the z.coerce surface to be easier to reason about than the previous transform-based approach. For codebases that already run on Zod, there is rarely a strong reason to switch. The interesting choice is for new projects.
What the spec deliberately leaves out
The spec authors made a series of decisions to keep the surface small. Each of those decisions is a constraint worth understanding before depending on Standard Schema for non-trivial integrations.
There is no shared error format. Each validator's issues array contains message and path, and that is it. Localized error messages, error codes, and structured details are validator-specific. A library that wants to render Zod's nested issue codes differently from Valibot's flat ones still has to special-case them.
There is no JSON Schema bridge. If you need to expose your validation schemas to a tool that consumes JSON Schema (OpenAPI generation, AI structured outputs, form builders), you still call validator-specific helpers like zod-to-json-schema or Valibot's toJsonSchema. This matters more now than it did two years ago, because most LLM clients want JSON Schema for tool calling.
There is no transformation contract. Standard Schema's Input and Output generic parameters acknowledge that validators may transform values, but the spec does not constrain how. A consumer cannot rely on transforms being pure, synchronous, or commutative. In practice, library code that depends on Standard Schema treats it as a parse-only contract and ignores transforms.
There is no async cancellation. Refinements and async checks can return a Promise, but there is no AbortSignal, no progress reporting, and no guarantee that the validator will respect cancellation if the consumer stops awaiting. For most form and API workloads this is fine. For background validation against slow external systems, you handle cancellation outside the validator.
How library authors actually consume it
The interesting code is on the consumer side. A library that wants to accept any Standard Schema validator looks roughly like this:
import type { StandardSchemaV1 } from "@standard-schema/spec";
async function parse<S extends StandardSchemaV1>(
schema: S,
input: unknown,
): Promise<StandardSchemaV1.InferOutput<S>> {
let result = schema["~standard"].validate(input);
if (result instanceof Promise) result = await result;
if (result.issues) {
throw new SchemaError(result.issues);
}
return result.value;
}That is the whole integration. The InferOutput helper is the one ergonomic concession the spec makes. It exists because TypeScript cannot otherwise pull the output type out of ~standard.types.output cleanly, and every consumer would otherwise re-derive it.
This shape is why adoption was fast. A library does not need to know which validator produced the schema, what its surface looks like, or how it formats errors. The validator hands back either a value or a list of issues. Everything else is the library's problem.
The other side is also small. A validator implementing the spec adds one property:
function string() {
return {
parse(input: unknown): string {
if (typeof input !== "string") throw new Error("not a string");
return input;
},
"~standard": {
version: 1 as const,
vendor: "tinyvalidator",
validate(input: unknown) {
return typeof input === "string"
? { value: input }
: { issues: [{ message: "Expected string", path: [] }] };
},
},
};
}Most validators do not write this by hand. They generate the ~standard property from their existing internal parse logic. Zod's implementation, for instance, wraps safeParse and remaps the issue codes into the spec's flat shape.
The integrations that actually matter
Two integrations changed the math more than the others.
The first is form libraries. react-hook-form, TanStack Form, and Conform all accept Standard Schema validators directly. For applications that have a Zod schema generated from a database (drizzle-zod, prisma-zod-generator), the schema flows from the database into the form without an adapter layer. Replacing the validator means replacing the schema source, not the integration code.
The second is RPC layers. tRPC v11 accepts Standard Schema on both inputs and outputs. So does Hono's RPC. The combination of typed RPC plus Standard Schema means the validator can be chosen per-procedure, not per-codebase. A team can keep their existing Zod schemas for legacy procedures and write new ones in Valibot for size-sensitive routes that ship to the client.
This last point matters more than it sounds. Most production codebases accumulate validation code over years. The cost of a wholesale migration is often higher than the benefit of switching, even when the new validator is clearly better. Procedure-level swapping turns that into a gradient: rewrite the schemas that are hot on the client first, leave the cold paths on the old validator, never run a big-bang migration. That option did not exist before.
What this implies for new code
Pick the validator whose ergonomic surface fits the schemas you actually write. If most of your validation is config objects and API payloads, Zod's chained API is hard to beat for readability. If you ship a lot of code to the browser and care about bundle size, Valibot's functional API is the obvious pick. If your schemas are heavy on string formats and constraint inference, ArkType buys you type-level precision you cannot get elsewhere.
Do not write library code that types its inputs as ZodType<T> anymore. Type them as StandardSchemaV1<unknown, T>. This is true even if your team only uses Zod today. The cost of writing the more general type is negligible. The cost of refactoring later, once the library has consumers, is not.

Do not assume that all Standard-Schema-compliant validators are interchangeable in behavior. The spec only standardizes the parse contract. Two validators given the same schema definition can disagree on how they coerce, how they handle extra properties, and what they consider a valid date. Migrations still require tests.
Treat the spec as a public contract for libraries you publish. Treat your specific validator as an implementation detail of your application. The first is the part you expose. The second is the part you can change.
The pattern this fits into
Standard Schema is part of a recent run of TypeScript-ecosystem specs that exist purely to decouple libraries from each other. WebMCP did the same thing for browser-resident MCP servers. The fetch Response and Request types did it for HTTP frameworks. JSR's module conventions did it for cross-runtime packages.
Each of these specs is small. None of them solves the actual hard problem (validating data, calling tools, parsing requests). They solve the meta-problem: making sure that picking a library to solve the hard problem does not force every downstream library to pick the same one.
The lesson, repeated, is that the most valuable interop work in a maturing ecosystem is not adding capability. It is removing the implicit coupling that early library adoption built up. A spec like Standard Schema does not introduce a new feature. It documents the smallest possible shape every existing validator already had, and asks each one to expose it under a known name.
Once that name exists, the lock-in dissolves. The next validator someone writes does not have to fight an entrenched competitor's API to be adopted. It writes ~standard, and every form library, every RPC framework, every config loader picks it up for free. The validator market is now competitive in a way it was not eighteen months ago, and the libraries built on top of it stopped caring which one wins.
