Until recently, the TypeScript validation ecosystem was plagued by severe architectural fragmentation. Every popular library enforced its own proprietary, closed API:
.safeParse(data) methods and internal _input / _output properties.v.safeParse(schema, data) paired with TypeScript utilities InferInput / InferOutput.type(data) inspected via instanceof type.errors.Ajv or TypeCompiler.For tooling and framework maintainers (React Hook Form, TanStack Form, tRPC, Hono, Astro, Next.js Server Actions), this sparked an exponential combinatorial explosion of adapter maintenance ($M \times N$ combinations). Form library authors were forced to maintain dozens of separate adapter packages: @hookform/resolvers/zod, @hookform/resolvers/valibot, @hookform/resolvers/yup, @trpc/server/adapters/arktype. Any minor issue or error structure change from one vendor shattered downstream integration pipelines.
In 2024, the creators of key validation engines — Colin McDonnell (Zod), Fabian Cook (Valibot), and David Blass (ArkType), backed by active collaboration from the TanStack and tRPC core teams — joined forces to develop the vendor-neutral, open standard: Standard Schema (@standard-schema/spec).
Standard Schema is a lightweight, vendor-agnostic specification interface widely adopted across the 2025–2026 TypeScript ecosystem. The specification defines:
~standard property.~standard.validate(value).Thanks to Standard Schema, any consuming framework or library can ingest a validation schema from any vendor without installing intermediate resolvers or custom glue code (Zero-Adapter Architecture).
Instead of wiring custom adapters for every library vendor, the downstream framework connects directly to the universal ~standard specification bus:
+-------------------------------------------------------------------------+
| ECOSYSTEM FRAGMENTATION (Past: M x N Adapters) |
+-------------------------------------------------------------------------+
| React Hook Form ===> [@hookform/resolvers/zod] ===> Zod |
| TanStack Form ===> [@tanstack/valibot-adapter] ===> Valibot |
| tRPC Server ===> [@trpc/arktype-resolver] ===> ArkType |
| * Dozens of brittle adapters, version drift, breaking signature changes|
+-------------------------------------------------------------------------+
| |
| STANDARD SCHEMA ARCHITECTURE (Present: 1 Unified Bus) |
+-------------------------------------------------------------------------+
| React Hook Form ---\ |
| TanStack Form -----> [ StandardSchemaV1: ~standard.validate() ] |
| tRPC / Hono / Actions -/ | |
| +---> Zod v4 (~standard) |
| +---> Valibot (~standard) |
| +---> ArkType (~standard) |
| * Full out-of-the-box interoperability across any compliant validator! |
+-------------------------------------------------------------------------+Every conforming schema encapsulates a reserved metadata property named ~standard. The method ~standard.validate() accepts raw input data and returns a deterministic discriminated result: either { value: Output } on success, or { issues: [...] } upon validation failure.
<FormInput name="email" schema={EmailSchema} />) capable of standalone validation without binding to a specific npm validation dependency.The consuming library verifies the presence of the '~standard' property on the candidate object and confirms compatibility ('version: 1').
Invokes the vendor-supplied standardized validation routine. Discriminates between synchronous execution and asynchronous Promise resolution.
When validation fails, the returned issues array exposes normalized, standardized descriptors: message strings and deterministic key path segments.
On success, returns the validated and transformed value property, guaranteed to conform strictly to the schema's output type signature.
@standard-schema/spec Contract Definition#Below is the strict TypeScript contract definition for StandardSchemaV1, crafted without relying on fallback any types:
// Path segment of an issue: object property key or array index
export type StandardPathSegment = string | number | symbol;
export interface StandardPath {
readonly key: StandardPathSegment;
}
// Standardized validation issue descriptor
export interface StandardIssue {
readonly message: string;
readonly path?: ReadonlyArray<StandardPathSegment | StandardPath>;
}
// Successful validation result descriptor
export interface StandardSuccessResult<TOutput> {
readonly value: TOutput;
readonly issues?: undefined;
}
// Failed validation result descriptor
export interface StandardFailureResult {
readonly issues: ReadonlyArray<StandardIssue>;
readonly value?: undefined;
}
export type StandardResult<TOutput> =
| StandardSuccessResult<TOutput>
| StandardFailureResult;
// Core interface of the universal specification
export interface StandardSchemaV1<TInput = unknown, TOutput = TInput> {
readonly '~standard': {
readonly version: 1;
readonly vendor: string;
readonly validate: (
value: unknown
) => StandardResult<TOutput> | Promise<StandardResult<TOutput>>;
readonly types?: {
readonly input: TInput;
readonly output: TOutput;
};
};
}
// Utility types for static type extraction
export type InferStandardInput<TSchema extends StandardSchemaV1> =
NonNullable<TSchema['~standard']['types']>['input'];
export type InferStandardOutput<TSchema extends StandardSchemaV1> =
NonNullable<TSchema['~standard']['types']>['output'];Here is a resilient executor function capable of validating arbitrary input data against any schema adhering to the Standard Schema contract:
import {
StandardSchemaV1,
StandardResult,
InferStandardOutput,
} from './standard-schema-spec';
// Guard ensuring the candidate object conforms to the Standard Schema specification
export function isStandardSchema(value: unknown): value is StandardSchemaV1 {
return (
typeof value === 'object' &&
value !== null &&
'~standard' in value &&
typeof (value as Record<string, unknown>)['~standard'] === 'object' &&
(value as Record<string, unknown>)['~standard'] !== null &&
(value as { '~standard': { version: unknown } })['~standard'].version === 1
);
}
// Universal validation executor supporting both synchronous and asynchronous schemas
export async function validateUniversal<TSchema extends StandardSchemaV1>(
schema: TSchema,
input: unknown
): Promise<InferStandardOutput<TSchema>> {
if (!isStandardSchema(schema)) {
throw new Error('Provided schema object does not conform to the Standard Schema v1 specification');
}
// Invoke the unified vendor validation method
let result = schema['~standard'].validate(input);
// Await resolution if the schema executes asynchronously
if (result instanceof Promise) {
result = await result;
}
if (result.issues && result.issues.length > 0) {
const formattedErrors = result.issues
.map((issue) => {
const path = issue.path
?.map((p) => (typeof p === 'object' ? String(p.key) : String(p)))
.join('.');
return path ? `${path}: ${issue.message}` : issue.message;
})
.join('; ');
throw new Error(`Data validation failed: ${formattedErrors}`);
}
return result.value as InferStandardOutput<TSchema>;
}Because these libraries conform to Standard Schema, all three work seamlessly through the same execution pipeline:
import { z } from 'zod';
import * as v from 'valibot';
import { type } from 'arktype';
import { validateUniversal } from './universal-validator';
// 1. Zod v4 schema (native ~standard support)
const ZodPayment = z.object({
amountCents: z.number().positive(),
currency: z.enum(['USD', 'EUR', 'GBP']),
});
// 2. Valibot schema (native ~standard support)
const ValibotPayment = v.object({
amountCents: v.pipe(v.number(), v.minValue(1)),
currency: v.picklist(['USD', 'EUR', 'GBP']),
});
// 3. ArkType 2.0 schema (native ~standard support)
const ArkTypePayment = type({
amountCents: 'number > 0',
currency: '"USD" | "EUR" | "GBP"',
});
// Universal business logic for checkout processing
async function processCheckout() {
const rawPayload = { amountCents: 5000, currency: 'EUR' };
// All three calls invoke the identical validateUniversal executor!
const res1 = await validateUniversal(ZodPayment, rawPayload);
const res2 = await validateUniversal(ValibotPayment, rawPayload);
const res3 = await validateUniversal(ArkTypePayment, rawPayload);
console.log('Successfully validated by all vendors:', res1.currency, res2.amountCents, res3.amountCents);
}Below is an enterprise-grade factory for building type-safe React 19 / Next.js Server Actions accepting arbitrary Standard Schema validators:
import {
StandardSchemaV1,
InferStandardOutput,
InferStandardInput,
} from './standard-schema-spec';
export interface ActionResponse<TData> {
isSuccess: boolean;
data?: TData;
fieldErrors?: Record<string, string[]>;
serverError?: string;
}
export function createUniversalAction<
TSchema extends StandardSchemaV1,
TResult
>(
schema: TSchema,
actionHandler: (data: InferStandardOutput<TSchema>) => Promise<TResult>
) {
return async (
rawInput: InferStandardInput<TSchema>
): Promise<ActionResponse<TResult>> => {
// 1. Contract specification verification
const standard = schema['~standard'];
if (!standard || standard.version !== 1) {
return {
isSuccess: false,
serverError: 'Incompatible validation specification version',
};
}
// 2. Perform input validation
let validation = standard.validate(rawInput);
if (validation instanceof Promise) {
validation = await validation;
}
// 3. Normalized error handling
if (validation.issues && validation.issues.length > 0) {
const fieldErrors: Record<string, string[]> = {};
for (const issue of validation.issues) {
const pathKey = issue.path
?.map((segment) => (typeof segment === 'object' ? String(segment.key) : String(segment)))
.join('.') || '_root';
if (!fieldErrors[pathKey]) {
fieldErrors[pathKey] = [];
}
fieldErrors[pathKey].push(issue.message);
}
return {
isSuccess: false,
fieldErrors,
};
}
// 4. Execute validated server logic
try {
const result = await actionHandler(validation.value as InferStandardOutput<TSchema>);
return {
isSuccess: true,
data: result,
};
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : 'Internal server failure';
return {
isSuccess: false,
serverError: msg,
};
}
};
}Developers frequently construct a purported "generic" handler but invoke schema.parse(data) or v.safeParse(schema, data) internally. This immediately couples the utility to a specific engine and throws TypeError: schema.parse is not a function as soon as a consumer passes a Valibot or ArkType schema. Generic utilities must exclusively interact with schema['~standard'].validate().
The validate() function signature permits returning either a synchronous StandardResult or a Promise<StandardResult>. Omitting an instanceof Promise check or await will cause asynchronous schemas to fail silently or throw errors when trying to read the issues property directly from an unresolved Promise instance.
// ❌ BROKEN: Assumes synchronous execution only
const result = schema['~standard'].validate(input);
if (result.issues) { /* result.issues is undefined if result is a Promise! */ }
// ✅ CORRECT: Normalizes both synchronous and asynchronous execution paths
let result = schema['~standard'].validate(input);
if (result instanceof Promise) {
result = await result;
}When schemas apply coercion or transformations (such as z.coerce.number() or v.trim()), the raw input type diverges from the sanitized output type. Extracting InferStandardInput instead of InferStandardOutput for server handlers risks passing uncoerced or raw strings into database queries.
The specification allows path segments to be primitive values (string | number) or structural wrapper objects ({ key: string | number }). Invoking naive operations like issue.path.join('.') can yield corrupted strings such as [object Object].[object Object]. Path segments must be normalized appropriately.
The engineer creates a schema using Zod, Valibot, or ArkType. The engine injects the ~standard metadata contract.
The schema is supplied to a generic consumer such as a form helper, tRPC route, or Server Action builder.
The consumer calls the unified validation method without requiring knowledge of the underlying validator engine.
The consumer awaits Promise resolution when necessary and inspects the discriminated issues payload.
If validation passes without issues, sanitized and typed value data is dispatched to business logic handlers.