# Valibot > The modular and type safe schema library for validating structural data. ## Posts of 2026 ### Making Valibot easier for coding agents to use Published on August 11, 2026 by [flySewa](https://github.com/flySewa) We assume a lot of people use coding agents when they're building with Valibot, so we spent the last few weeks reworking the documentation for them. Agents already write most Valibot schemas correctly from their training data. The problems we found were around function names that no longer matched, pages that nothing linked to, and how much context a single page costs to read. A lot of our work went into those. #### We now run an MCP server It's at `https://valibot.dev/mcp`, free, needs no authentication, and gives your agent three tools: - `search_docs` searches the guides, API reference and blog and returns matching pages - `get_doc` reads a specific page and returns it as Markdown - `list_docs` returns all available documentation grouped by area and category Add it to Claude Code with this: ```bash claude mcp add --transport http valibot https://valibot.dev/mcp ``` For Cursor and other tools that use a JSON configuration file, add this entry: ```json { "mcpServers": { "valibot": { "url": "https://valibot.dev/mcp" } } } ``` Your agent makes one tool call instead of several requests. When it needs a specific page, it asks for that page and gets it, instead of pulling three unrelated ones first. #### Agents can find the skill now The skill loads the current API and the patterns we recommend at the start of the session, so the code your agent writes follows the examples in our guides. We think it's the most useful thing here for most people. It lives in `open-circle/agent-skills`, and until recently that was the only place it lived. Nothing on valibot.dev served it or linked to it, so you had to know the repo existed to point your agent at it. The site now publishes it at `/.well-known/agent-skills/valibot/SKILL.md` and lists it in a discovery index, so an agent looking for one can find it without being told. Installing is still one command. ```bash npx skills add open-circle/agent-skills --skill valibot ``` #### The Markdown is generated from the page now We were already publishing a `.md` file for every page, but it was a copy of the source file, and our source files have components in them. Where the page showed a full type signature or a list of related links, the `.md` file just had the tag that produces them, so an agent reading it got less than you would. Now the Markdown is generated the way the page is, with those components turned into content. That's worth having because of what an HTML page carries. When your agent fetches one it gets the navigation, the scripts and the styling along with it, and pays for all of that in context without using any of it. The Markdown version has the content and none of the rest, so your agent can read several pages for what one used to cost. We also added an `X-Markdown-Tokens` header to every Markdown response, so a tool can check what a page costs before loading it. API pages keep their full type signatures, and the links between pages point at Markdown, so an agent following a reference from one page to another never has to switch formats. #### Every page now points to llms.txt We'd been publishing `llms.txt` for a while, and nothing on the site pointed at it, so an agent had to already know it was there. Every Markdown page now links back to it and to its own HTML page, and the file opens with a summary saying what's in it and where the rest of the files are. We publish a few other shapes as well, since one file doesn't suit every tool. `llms-full.txt` is the entire documentation in a single file. For something narrower, `llms-guides.txt` covers the guides, `llms-api.txt` the API reference, and `llms-blog.txt` the blog. #### The API reference now matches the library The API reference is the first place an agent looks, so we checked it against the source. We corrected 28 files, covering function names that no longer matched, invalid links and outdated menu entries. We also found five pages that nothing linked to. `ltValue`, `LastTupleItem`, `Reference`, `VariantOption` and `VariantOptionAsync` were all written and published, but an agent working through the menu had no way of knowing they existed. We've listed them now. Ask an agent about `VariantOption` today and it reads the page instead of guessing. #### The whole site is static now None of this helps much if the pages are slow, so every page is generated ahead of time and served as a file. That's more than 800 pre-rendered pages, so every page an agent asks for is already built and comes back in around 80 ms from the edge cache. Thanks to the [ZanReal](https://zanreal.com/) team for making that possible. They wrote about the [migration](https://zanreal.com/case-studies/valibot-formisch-static-docs) if you want the detail. #### The API design helps too We think a lot of this works because of how Valibot is built, which is the part we didn't have to change. Valibot is made of schemas, actions and methods, and all three are plain objects. There's no class hierarchy to follow and no inherited methods to discover. Once a model understands how a schema and an action fit together, it can apply that same shape across the whole API. That's one pattern to learn instead of one per data type. The skill, the MCP server, the LLMs.txt files and the Markdown versions are all documented on the [coding agents page](/guides/coding-agents.md). [Formisch](https://formisch.dev/), our form library, has the same setup if you're using it. ### One Model, Many Views: Type-Safe Transforms with Valibot and doba Published on June 3, 2026 by [karol-broda](https://github.com/karol-broda) _This is a guest post by [Karol Broda](https://karolbroda.com). I like building tools that solve problems I keep running into, and doba came out of one of those._ Most apps have the same data in multiple shapes. A database row has a password hash and internal metadata. The frontend gets a sanitized version without any of that. The AI endpoint needs a flat struct with just the fields the model cares about. A legacy API from two years ago returns something different entirely. The typical solution is a handful of functions. `toFrontendUser()`, `toAIUser()`, `fromLegacyV1()`. Each one is fine on its own. The problem is that they don't know about each other. Someone needs legacy-to-AI, there's no function for that, so they chain two together and hope the intermediate shape doesn't change. Nobody writes tests for these because they're "just mapping." Then a schema change ships and half your transforms silently produce wrong data. I wrote [doba](https://doba.karolbroda.com) to deal with this ([source](https://github.com/karol-broda/doba)). It's a schema registry that works with any [Standard Schema](https://github.com/standard-schema/standard-schema) compatible library, but I use it with Valibot, and the two pair well for a few reasons. Valibot's modular architecture means your bundle only includes what you actually use. A registry with ten schema variants doesn't pull in validators that only two of them use. With most other schema libraries, you'd import the whole thing regardless. Valibot's type inference is also what makes doba's typed migrations work. When you define a schema with `v.object()`, the inferred type flows straight into the migration function signature. Rename a field in the Valibot schema and the migration won't compile until you fix it. That feedback loop is the core of what makes this useful. #### Schemas and migrations You register your Valibot schemas and define migrations between them. Each migration function is fully typed against the source and target schemas. ```typescript import { createRegistry } from 'dobajs'; import * as v from 'valibot'; const databaseUser = v.object({ id: v.string(), email: v.pipe(v.string(), v.email()), passwordHash: v.string(), createdAt: v.pipe(v.string(), v.isoTimestamp()), settings: v.object({ theme: v.picklist(['light', 'dark']), notifications: v.object({ email: v.boolean(), push: v.boolean(), }), }), }); const frontendUser = v.object({ id: v.string(), email: v.pipe(v.string(), v.email()), createdAt: v.pipe(v.string(), v.isoTimestamp()), settings: v.object({ theme: v.picklist(['light', 'dark']), notifications: v.object({ email: v.boolean(), push: v.boolean(), }), }), }); const aiUser = v.object({ id: v.string(), email: v.string(), theme: v.string(), hasNotifications: v.boolean(), }); const registry = createRegistry({ schemas: { database: databaseUser, frontend: frontendUser, ai: aiUser }, migrations: { 'database->frontend': (user) => ({ id: user.id, email: user.email, createdAt: user.createdAt, settings: user.settings, }), 'frontend->ai': (user) => ({ id: user.id, email: user.email, theme: user.settings.theme, hasNotifications: user.settings.notifications.email || user.settings.notifications.push, }), }, }); ``` Add a new required field to a target schema and every migration pointing at it lights up red until you handle it. That's Valibot's type inference doing the work. You don't need to write a migration for every possible pair of schemas either. If there's no direct path, doba walks the graph and chains through intermediate ones automatically: ```typescript // We only defined database->frontend and frontend->ai, // but this still works. doba routes through frontend. const result = await registry.transform(databaseData, 'database', 'ai'); ``` #### Migration context Legacy migrations are full of quiet decisions. Defaulting an ID because the old format didn't have one. Guessing an email from a name field. Mapping a boolean called `darkMode` to a theme enum. In a regular transform function, all of that disappears into the function body. You migrate 10k legacy users, three months later someone asks why half of them have `unknown@example.com` as their email, and nobody remembers what the migration assumed. doba passes a context object to every migration so you can record what you defaulted and why. ```typescript const legacyUser = v.object({ name: v.optional(v.string()), darkMode: v.optional(v.boolean()), }); // A separate registry that also includes the legacy schema const extendedRegistry = createRegistry({ schemas: { database: databaseUser, frontend: frontendUser, ai: aiUser, legacy: legacyUser, }, migrations: { // ...previous migrations 'legacy->frontend': (user, ctx) => { ctx.defaulted(['id'], 'generated new id'); ctx.defaulted(['createdAt'], 'set to current timestamp'); let email = 'unknown@example.com'; if (user.name && user.name.length > 0) { email = `${user.name.toLowerCase().replace(/\s+/g, '.')}@legacy.example.com`; ctx.warn(`converted name "${user.name}" to email`); } return { id: `legacy-${Date.now()}`, email, createdAt: new Date().toISOString(), settings: { theme: user.darkMode === true ? 'dark' : 'light', notifications: { email: false, push: false }, }, }; }, }, }); const result = await extendedRegistry.transform( { name: 'Alice Johnson', darkMode: true }, 'legacy', 'frontend' ); if (result.ok) { result.meta.defaults; // [{ path: ['id'], message: 'generated new id', ... }] result.meta.warnings; // [{ message: 'converted name "Alice Johnson" to email', ... }] } ``` `result` is a discriminated union. `ok: true` with value and metadata, `ok: false` with typed validation errors. No try/catch. Most migrations are mechanical. Renaming a field, dropping another, adding a default. doba has a `pipe` builder for that so you don't have to write the boilerplate by hand: ```typescript 'database->frontend': { pipe: (p) => p.drop('passwordHash'), }, ``` The builder tracks the shape as you chain. A `.rename('foo', 'bar')` followed by `.drop('foo')` is a type error. #### Identifying unknown data Sometimes you get data and don't know which schema it came from. doba can figure that out and transform it: ```typescript import { createRegistry, match } from 'dobajs'; const registry = createRegistry({ schemas: { database: databaseUser, frontend: frontendUser, ai: aiUser }, migrations: { // ...same as before }, identify: { database: match.field('passwordHash'), frontend: match.fields('createdAt', 'settings'), ai: match.field('hasNotifications'), }, }); const result = await registry.identifyAndTransform(unknownData, 'ai'); if (result.ok) { result.value; // transformed data result.meta.from; // which schema it detected result.meta.path; // the route it took, e.g. ['database', 'ai'] } ``` Guards run in definition order. If none match, you get a typed error, not a runtime crash. #### Where this helps The examples above are simplified, but the pattern shows up in a lot of places. API versioning is the obvious one. You ship v1, then v2 changes the shape, then v3 splits a field into two. Clients are still sending all three versions. Instead of writing v1-to-v3 and v2-to-v3 converters by hand, you define v1-to-v2 and v2-to-v3 and the registry chains them. When v4 ships, you add one migration and everything upstream still works. LLM pipelines have a similar problem. Your database has a rich, nested user object, but the model prompt needs a flat struct with five fields. That transform is easy to write once. It's less easy to keep correct when the database schema evolves or when you need three different prompt formats for different models. Legacy imports are where the migration context really pays off. If you're pulling records from an old system and half the fields are missing or renamed, every decision you make during that conversion ("defaulted email because the source didn't have one") is recorded. When someone asks about it six months later, the metadata is right there on the result. Even something like a webhook handler fits. You receive payloads from a third party that's changed their format twice. You don't control when they migrate. `identifyAndTransform` figures out which version came in and normalizes it. If any of this sounds like a problem you have, take a look. Feedback and issues welcome. Thanks to [Fabian Hiller](https://github.com/fabian-hiller) and the Valibot team for having me on the blog. - [doba docs](https://doba.karolbroda.com) - [Valibot example](https://doba.karolbroda.com/docs/examples/with-valibot) - [GitHub](https://github.com/karol-broda/doba) ### How Dependency Size Impacts Cold Starts in Edge JavaScript Runtimes Published on May 26, 2026 by [flySewa](https://github.com/flySewa) Edge runtimes have gotten pretty good at avoiding cold starts. Between isolate reuse and smarter routing, a lot of requests never hit one at all. But cold starts still happen. During deployments, traffic spikes, and scaling events, new instances spin up from scratch. And when they do, there's a step that runs before your handler can do anything: the runtime loads your bundle, parses it, and evaluates every module in it before it can handle a request. So when you're trying to understand why latency spikes during those events, the question worth asking isn't just "how often do cold starts happen?" It's "how much work is happening inside each one?" A big part of the answer lives in your dependencies. #### The Experiment To put a concrete number on this, we built a minimal Cloudflare Worker to understand the cold start cost of our own library compared to Zod, using a single User schema covering name, email, age, and role. We built it twice using Wrangler 4.81.1 and TypeScript, keeping everything identical except for the validation library being imported. This comparison uses the default zod package rather than zod/mini, and focuses on baseline installs and primary APIs rather than optimized variants. One build used Zod and the other used Valibot. Both builds were done in production mode using Wrangler's default bundling. Here's what came out of the bundler: | **Library** | Raw Bundle | Gzipped | | ----------------- | ---------- | --------- | | **Zod 4.3.6** | 141.43 KiB | 26.75 KiB | | **Valibot 1.3.1** | 11.92 KiB | 2.72 KiB | Valibot's gzipped bundle is 9.8x smaller than Zod's for an identical schema. #### What This Experiment Does (and Doesn't) Show This comparison focuses on bundle size and included code, not direct cold start timing, since measuring cold start latency reliably outside of production is hard because: - Edge platforms reuse isolates aggressively - Cold starts depend on traffic patterns and scaling behavior - Platform-level metrics are often not exposed in detail So instead of simulating that imperfectly, this experiment isolates one part of the problem: how much code the runtime has to load and execute during initialization. We're using bundle size as a proxy for cold start cost. In most JavaScript runtimes, more code means more parsing work, and more modules mean more evaluation work. This applies to edge runtimes that execute ESM modules on startup, including platforms like Vercel Edge Functions and Deno Deploy. #### Why the Bundle Sizes Are So Different This is where it gets interesting, because the gap is a consequence of how each library is designed. Zod uses a class-based, chainable API. It is easy to use and has strong TypeScript inference. But internally, many parts of the library are tightly connected. From the bundler's perspective, this looks like a coarse-grained import. Even if you use a small part of Zod, shared internals like parsing logic and error handling often get included together. Valibot is built differently. Each validator is a small standalone function. You import exactly what you need. This is a fine-grained import model. The bundler can include only the functions you use and drop the rest. For the schema in this experiment, the bundler included only the specific validators the schema used. That's most of what ended up in the bundle. Here's the same User schema in both libraries: ```ts // Zod import * as z from 'zod'; const UserSchema = z.object({ name: z.string().min(1), email: z.string().email(), age: z.number().int().min(0).max(120), role: z.enum(['admin', 'user', 'guest']), }); ``` ```ts // Valibot import * as v from 'valibot'; const UserSchema = v.object({ name: v.pipe(v.string(), v.minLength(1)), email: v.pipe(v.string(), v.email()), age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(120)), role: v.picklist(['admin', 'user', 'guest']), }); ``` > Using `import * as v from 'valibot'` here does not disable tree-shaking. With modern ESM bundlers, namespace imports like this are still statically analyzable, so only the specific `v.*` exports referenced by the schema are retained in the final bundle. The schemas are functionally identical. The difference is what the bundler sees. In the Zod version, the bundler can see individual exports, but shared internals like the parsing engine, error formatting, and type utilities are tightly coupled and get pulled in regardless of how little of the API you use. In the Valibot version, each import is a discrete, standalone function. There is very little shared infrastructure to drag in, so the bundler includes only what the schema actually needs. That's where the 9.8x comes from. #### Why This Matters for Cold Starts Up to this point, we've only looked at one dependency in isolation. In a real application, that validation library sits alongside everything else you use. Routing, authentication, database clients, logging. All of it is bundled together into a single script. When a new edge instance starts, the runtime has to process that script before it can respond to a request. It does not jump straight into your handler. It first has to load the code, read through it, and run the modules so everything is ready. As that bundle gets larger, that setup step takes more work. There is simply more code to go through, and more modules that need to run before the instance is ready. You can think of it as replaying your module graph on each new instance. The larger the graph, the more work the runtime has to do before it can serve anything. A 9.8x difference in bundle size means the runtime is doing materially less work on every cold start. This shows up most clearly during deployments and traffic spikes, exactly when you can least afford extra latency. During a traffic spike, the platform scales out and starts more instances. In quieter periods, instances may not stay warm, so the next request has to go through that setup again. In all of these cases, the first request handled by a new instance waits for that initialization to finish. A smaller bundle does not remove cold starts, but it reduces how much work happens inside each one. In edge environments, where instances are created more often, that difference is more likely to affect response time. #### Does Valibot Work With the Rest of Your Stack? A fair question before switching anything is whether you lose ecosystem coverage. The short answer is no. Valibot works with the tools most teams are already using: - [**Standard Schema**](https://standardschema.dev/) – Valibot implements the Standard Schema specification, which means it works natively with any tool that supports it without adapters or wrappers. - [**tRPC**](https://trpc.io/) – Pass a Valibot schema directly to `.input()` without any wrapper. - [**React Hook Form**](https://github.com/react-hook-form/resolvers) – drop-in via `@hookform/resolvers/valibot` - [**Hono**](https://github.com/honojs/middleware/tree/main/packages/valibot-validator) – supported through `@hono/valibot-validator` - [**Conform**](https://conform.guide/) – official support via `@conform-to/valibot` Type inference works the same way you'd expect coming from Zod: ```ts import { InferOutput } from 'valibot'; type User = InferOutput; ``` If your existing codebase is heavily Zod, migration doesn't have to be a full rewrite. Most teams move validation schema-by-schema at the edges of their system, at API boundaries, form handlers, and environment config, where the cold start impact is most direct. If you want to speed that process up, the [Zod to Valibot migration guide](https://valibot.dev/guides/migrate-from-zod/) covers the key differences and includes a codemod to handle much of the conversion automatically. #### Where Bundle Size Actually Matters None of this means Valibot is the right choice in every situation. If you're running long-lived services where instances stay warm for extended periods, a 9.8x bundle size difference has almost no visible impact. In those environments, Zod's more ergonomic chaining and broader ecosystem familiarity may genuinely matter more. But if you're building for edge environments like Cloudflare Workers, Vercel Edge Functions, and Deno Deploy, cold starts are closer to the request path, and initialization overhead is more likely to show up in your p99s. In those environments, choosing a smaller, more modular library reduces the amount of work done during initialization without sacrificing type safety or expressiveness. The broader principle holds too: validation libraries aren't the only place this happens. Any large dependency with poor tree-shaking characteristics can have a similar effect. It's worth auditing what actually ends up in your bundle, not just what you imported. If you want to run the builds yourself or adapt the setup, the full repo is [here](https://github.com/Beejay9/zod-vs-valibot). ### Valibot v1.4: String case transforms, local timestamps, and perf improvements Published on May 5, 2026 by [fabian-hiller](https://github.com/fabian-hiller) Valibot v1.4 adds four string case transformation actions for converting between common naming conventions, a new validation action for local ISO timestamps without timezone, and a round of performance and compatibility improvements. Huge thanks to [@ksaurav24](https://github.com/ksaurav24), [@heiwen](https://github.com/heiwen), [@compulim](https://github.com/compulim), [@ysknsid25](https://github.com/ysknsid25), [@alaycock-stripe](https://github.com/alaycock-stripe), [@IlyaSemenov](https://github.com/IlyaSemenov), and [@wszgrcy](https://github.com/wszgrcy) for their contributions to this release. #### String case transformations Four new transformation actions handle the most common naming convention conversions: [`toCamelCase`](/api/toCamelCase.md), [`toKebabCase`](/api/toKebabCase.md), [`toPascalCase`](/api/toPascalCase.md), and [`toSnakeCase`](/api/toSnakeCase.md). These actions split words on `_`, `-`, ASCII whitespace, and case or acronym boundaries, so they work on a wide range of inputs without preprocessing. ```ts import * as v from 'valibot'; const SlugSchema = v.pipe(v.string(), v.toKebabCase()); v.parse(SlugSchema, 'My Blog Post'); // 'my-blog-post' v.parse(SlugSchema, 'getUserByID'); // 'get-user-by-id' v.parse(SlugSchema, 'first_name'); // 'first-name' ``` This is especially useful at API boundaries: normalizing form fields before persisting, or converting incoming JSON keys before passing them to a typed service. #### Local ISO timestamps [`isoTimestamp`](/api/isoTimestamp.md) requires a timezone designator and [`isoDateTime`](/api/isoDateTime.md) only validates `hh:mm`, leaving a gap for local date-times that include seconds, such as `1995-03-31T00:00:00`. The new [`isoDateTimeSecond`](/api/isoDateTimeSecond.md) action fills that gap. ```ts import * as v from 'valibot'; const EventSchema = v.object({ startsAt: v.pipe(v.string(), v.isoDateTimeSecond()), }); v.parse(EventSchema, { startsAt: '1995-03-31T00:00:00' }); // ok v.parse(EventSchema, { startsAt: '1995-03-31 00:00:00' }); // ok ``` This is useful for local-only events, schedules, and database columns like PostgreSQL's `timestamp` (without timezone), where attaching a UTC offset would be incorrect. #### Performance improvements Hot validation paths now allocate fewer objects, and a long-standing `RangeError` that could occur when spreading very large issue arrays has been fixed. Together, this makes validating large schemas both faster and safer. TypeScript performance has also improved significantly. The internal types behind [`object`](/api/object.md) and [`record`](/api/record.md) schemas — particularly when combined with [`pipe`](/api/pipe.md) and the [`readonly`](/api/readonly.md) action — have been simplified, making type inference and autocomplete noticeably faster on large codebases. If you have ever hit a `RangeError` with a large schema, noticed allocation pressure in a validation hot path, or felt sluggish autocomplete on large object schemas, this release should resolve it. #### Compatibility fixes The build target has been changed to ES2020 so the distributed output stays compatible with environments that lack newer syntax. As part of the same effort, `Object.hasOwn` (ES2022) was replaced with `Object.prototype.hasOwnProperty.call` to keep Valibot working on runtimes that have not yet shipped the newer builtin. The [`intersect`](/api/intersect.md) schema no longer mutates input values, so frozen objects and arrays can now be merged without throwing. The [`creditCard`](/api/creditCard.md) action also rejects Mastercard numbers with invalid lengths instead of accepting them. #### What's next? We will continue to focus on performance, broader runtime compatibility, and refining the developer experience around common validation workflows. If there is a validator, transformation, or guide you would like to see next, let us know on [Discord](https://discord.gg/w5mRTETqzv) or open a discussion on [GitHub](https://github.com/open-circle/valibot/discussions). New to Valibot? Check our [quick start guide](/guides/quick-start.md). Coming from Zod? Check our [migration guide](/guides/migrate-from-zod.md). ### Why migrate to Valibot? Published on March 23, 2026 by [fabian-hiller](https://github.com/fabian-hiller) Valibot is one of the most compelling schema libraries you can use today. It combines excellent startup performance, strong type safety, a very clear mental model, and a modular architecture that scales from simple form validation to advanced schema tooling. That combination is rare. Many libraries are fast but less introspectable, flexible but heavier, or familiar but harder to extend cleanly. Valibot strikes a very practical balance, which is why it has become such a strong option for modern TypeScript applications. Even if you currently use another schema library, switching is usually not as dramatic as it might sound. In many cases, the code still looks very familiar. ```ts import * as v from 'valibot'; import * as z from 'zod'; const ZodSchema = z.string().email().endsWith('@example.com'); const ValibotSchema = v.pipe(v.string(), v.email(), v.endsWith('@example.com')); ``` #### Better startup performance When people talk about performance, they often focus only on runtime parsing speed. That is important, but it is not the whole story. For websites, web apps, and serverless runtimes, startup performance also matters a lot. This includes the time required to download the library, initialize your schemas, and get your code ready to run. This is one of Valibot's biggest strengths. On [Schema Benchmarks](https://schemabenchmarks.dev/download), Valibot currently needs only `1.91 kB` gzipped whereas other schema libraries such as Zod v4 require `16.57 kB`. On the corresponding [initialization benchmark](https://schemabenchmarks.dev/initialization), the same schema is initialized in `54 μs` and therefore 16x faster than Zod v4. It is also interesting to see that even Zod now offers Zod Mini. This is a sign that modularity and tree shaking clearly matter to developers. Valibot might not ([yet](https://x.com/FabianHiller/status/2035416102810095689)) win every benchmark. But if you care about the full package instead of a single number, Valibot offers one of the best overall tradeoffs, also for runtime performance. #### A clear mental model One of the main reasons Valibot feels easy to use is that its mental model is very clear. The API is reduced to **schemas**, **methods**, and **actions**. Schemas validate raw data types like strings, numbers, objects, or unions. Methods help you use or modify a schema. Actions are used inside a pipeline to validate, transform, or describe data in more detail. Once you understand these three building blocks, the rest of the library becomes much easier to reason about. This matters even more in larger codebases where schemas are shared across forms, API boundaries, workers, and internal utilities. #### More precise type safety Valibot does not stop at inferring the output type of a schema. It also gives you precise types for the issues a schema can produce. This becomes especially useful once you start formatting errors for users or building abstractions on top of your schemas. More generally, Valibot tries to preserve as much information as possible in a type-safe way. This includes the input and output type, metadata, defaults, and issue details. Instead of falling back to broad generic types, Valibot gives you much more precise information to work with in your editor. ```ts import * as v from 'valibot'; const EmailSchema = v.pipe(v.string(), v.email()); type EmailIssue = v.InferIssue; function formatIssue(issue: EmailIssue) { if (issue.kind === 'schema') { return `Expected ${issue.expected} but received ${issue.received}.`; } return `Invalid email address: ${issue.received}.`; } ``` I think this attention to detail leads to fewer mistakes, a better developer experience, and safe AI-generated code, especially in TypeScript-heavy projects. #### Everything in one pipeline Another major advantage of Valibot is its pipeline design. Validation, transformation, and metadata all follow the same mental model. You start with a schema and then add more behavior step by step. This makes simple things easy while still scaling well to more advanced use cases. A pipeline can trim strings, validate formats, transform values, attach metadata, or even continue with another schema after a transformation. All of this still feels like one system instead of a growing collection of special-case APIs. ```ts import * as v from 'valibot'; const PortSchema = v.pipe( v.string(), v.trim(), v.regex(/^\d+$/), v.transform(Number), v.number(), v.minValue(1), v.maxValue(65535) ); ``` Pipelines also make schemas easy to extend. You can take an existing schema and build on top of it without rewriting it from scratch, a bit like adding another LEGO brick to something you already assembled. ```ts import * as v from 'valibot'; const EmailSchema = v.pipe(v.string(), v.email()); const WorkEmailSchema = v.pipe(EmailSchema, v.endsWith('@example.com')); ``` If you want to learn more about this approach, take a look at our [pipeline guide](/guides/pipelines.md). #### More functionality without more weight Small bundle size only matters if the library is still powerful enough for real projects. Fortunately, this is exactly where Valibot's modular architecture shines. The library gives you a surprisingly wide built-in toolbox, but because everything is split into small independent functions, you only pay for what you actually use. Valibot supports much more than basic primitives. It includes schemas for objects, records, maps, sets, unions, variants, functions, and promises. It also ships many validations such as [`email`](/api/email.md), [`url`](/api/url.md), [`uuid`](/api/uuid.md), [`creditCard`](/api/creditCard.md), and [`isoTimestamp`](/api/isoTimestamp.md), plus transformations like [`trim`](/api/trim.md), [`toLowerCase`](/api/toLowerCase.md), [`toNumber`](/api/toNumber.md), [`parseJson`](/api/parseJson.md), and [`mapItems`](/api/mapItems.md). We currently provide 46 schemas and 118 actions out-of-the-box, and we are adding more with every release. This means Valibot is not small because it does less. It is small because it is modular and fully tree-shakable. You get a lot out of the box without forcing every project to carry the full weight of the whole library. If you want an overview of what is included, feel free to browse our [API reference](/api/). The ecosystem around Valibot is still growing, but because schemas and actions follow a shared interface, building your own extensions or wrappers is pretty straightforward. #### Easier to extend and reason about Valibot is also a great choice if you want to go beyond the built-in functionality. One of the nicest properties of the library is that schemas and actions are plain objects with shared interfaces. This makes the internal structure much easier to understand than in many other libraries. For simple cases, you can create reusable schema factories by composing existing building blocks. For more advanced use cases, you can also implement fully custom schemas and actions that behave like first-class citizens. That makes Valibot a very good foundation not only for applications, but also for libraries, wrappers, and domain-specific schema tooling. ```ts import * as v from 'valibot'; // A simplified version of the built-in string schema // You can build your own and combine it with other schemas and actions function string(message) { return { kind: 'schema', type: 'string', reference: string, expects: 'string', async: false, message, // ... }; } ``` If that sounds interesting, I recommend reading our [extend guide](/guides/extend-valibot.md). It shows how custom schemas and actions can be built from scratch without giving up compatibility with the rest of the ecosystem. #### Valibot works well with AI coding agents Valibot is also a great fit for modern AI-assisted development. In that world, a schema library should not only be pleasant for humans to type by hand. It should also be easy for tools to inspect, understand, and modify. This is where Valibot's structure helps a lot. A schema is made up of explicit functions with a predictable shape. Instead of relying on a long chain of methods with subtly different semantics, Valibot keeps the structure of the schema visible in the code. That makes inspection, refactoring, and code generation easier for both humans and tools. The traditional DX advantage of chaining APIs also becomes less important when an AI agent writes most of the code anyway. What matters more is whether the code is regular, composable, and easy to transform safely. Valibot is very good at that. Pipelines can even carry metadata such as [`title`](/api/title.md), [`description`](/api/description.md), and [`metadata`](/api/metadata.md). This is helpful not only for documentation, but also for AI tools that need more context about what a field represents. ```ts import * as v from 'valibot'; const UsernameSchema = v.pipe( v.string(), v.regex(/^[a-z0-9_-]{4,16}$/iu), v.title('Username'), v.description( 'A username must be between 4 and 16 characters long and can only contain letters, numbers, underscores and hyphens.' ) ); ``` We also provide dedicated [`llms.txt` routes](/guides/coding-agents.md) for older or less powerful AI models and an installable agent skill described in our [installation guide](/guides/installation.md#for-ai-agents). That makes it easier for AI tools to generate, migrate, and optimize schemas according to the current best practices. #### Final thoughts Valibot is not just a smaller alternative to other schema libraries. It is a carefully designed system with a clear mental model, strong type safety, excellent startup characteristics, and a modular architecture that scales from simple form validation to advanced schema tooling. If those benefits sound attractive to you, the next step is simple. Try the [migration guide](/guides/migrate-from-zod.md) and our Zod-to-Valibot codemod, or experiment with a few schemas in the [playground](/playground/). I think you will quickly notice that Valibot feels small without feeling limited, and powerful without feeling complicated. ### Valibot v1.3: Smarter pipelines, result caching, and new validators Published on March 17, 2026 by [fabian-hiller](https://github.com/fabian-hiller) Valibot v1.3 adds new tools for building smarter pipelines, avoiding repeated validation work, and validating more real-world string formats. With [`guard`](/api/guard.md) and [`parseBoolean`](/api/parseBoolean.md) you can refine types and parse boolish input directly in a pipeline, while [`cache`](/api/cache.md) helps you reuse schema results for repeated inputs. This release also adds [`domain`](/api/domain.md), [`jwsCompact`](/api/jwsCompact.md), and [`isrc`](/api/isrc.md), plus a few important compatibility fixes. #### TL;DR - New pipeline tools: [`guard`](/api/guard.md) for type refinement and [`parseBoolean`](/api/parseBoolean.md) for boolish input. - New caching APIs: [`cache`](/api/cache.md) and [`cacheAsync`](/api/cacheAsync.md). - New validators: [`domain`](/api/domain.md), [`jwsCompact`](/api/jwsCompact.md), and [`isrc`](/api/isrc.md). - Compatibility fixes for [`creditCard`](/api/creditCard.md), [`isoTimestamp`](/api/isoTimestamp.md), and deeply readonly defaults and fallbacks. Huge thanks to [@EskiMojo14](https://github.com/EskiMojo14), [@yslpn](https://github.com/yslpn), [@alexilyaev](https://github.com/alexilyaev), [@idleberg](https://github.com/idleberg), [@BerkliumBirb](https://github.com/BerkliumBirb), and [@frenzzy](https://github.com/frenzzy) for their contributions to this release. #### Smarter pipelines Two new additions make it easier to refine and transform data directly inside a pipeline. [`guard`](/api/guard.md) lets you narrow values using an existing type predicate (a function that checks and confirms a more specific type), and [`parseBoolean`](/api/parseBoolean.md) turns common boolish input into a real boolean. This is especially useful when your input starts as a broad type and becomes more precise step by step. ```ts import * as v from 'valibot'; type PixelString = `${number}px`; const SizeSchema = v.pipe( v.string(), v.guard((input): input is PixelString => /^\d+px$/u.test(input)) ); // Output type: `${number}px` const size = v.parse(SizeSchema, '320px'); ``` `parseBoolean` is built for the kind of values you often get from environment variables, query strings, and forms. By default, it accepts values like `"true"`, `"1"`, `"yes"`, `"on"`, `"enabled"`, `"false"`, `"0"`, `"no"`, `"off"`, and `"disabled"`. String matching is case-insensitive, and you can define your own truthy and falsy values when needed. ```ts import * as v from 'valibot'; const EnvSchema = v.object({ PROD: v.pipe(v.string(), v.parseBoolean()), LOG_MODE: v.pipe( v.string(), v.parseBoolean({ truthy: ['verbose', 'chatty'], falsy: ['silent', 'quiet'], }) ), }); ``` Together, these APIs make it easier to keep transformation, validation, and type refinement inside a single pipeline instead of splitting logic across helper functions. #### Schema result caching If you validate or transform the same input more than once, [`cache`](/api/cache.md) can save work by caching the output of a schema. This is useful when parsing is expensive, when the same value appears repeatedly, or when a pipeline is reused in hot paths. ```ts import * as v from 'valibot'; import { isUsernameAvailable } from '~/api'; const UsernameSchema = v.cacheAsync( v.pipe( v.string(), v.minLength(3), v.checkAsync(isUsernameAvailable, 'This username is already taken.') ), { maxSize: 500, maxAge: 10_000 } ); ``` For async workflows, [`cacheAsync`](/api/cacheAsync.md) can also deduplicate matching concurrent runs, which is especially useful when validation involves a network request. One important detail from the implementation: primitive values like strings and numbers are cached by value, while objects and functions are cached by their reference, meaning the exact instance in memory. That means mutating an input object and reusing the same reference can return a stale cached result. For best results, use caching with immutable inputs and avoid mutating cached output objects. #### New validators Valibot v1.3 expands its built-in string validation with three new actions: [`domain`](/api/domain.md), [`jwsCompact`](/api/jwsCompact.md), and [`isrc`](/api/isrc.md). The new [`domain`](/api/domain.md) action validates domain names without requiring a full URL. This is useful when users enter a host name such as `example.com` and you explicitly do not want to accept protocols, paths, or query strings. ```ts import * as v from 'valibot'; const DomainSchema = v.pipe( v.string(), v.nonEmpty('Please enter your domain.'), v.domain('The domain is badly formatted.') ); ``` The validation is intentionally focused on ASCII domains. Internationalized domain names and Punycode-encoded labels are not supported here, so if you need full URL validation you should continue using [`url`](/api/url.md) or add your own preprocessing step. For authentication flows, [`jwsCompact`](/api/jwsCompact.md) checks whether a string matches the three-part JWS compact serialization shape with unpadded Base64URL-like segments. This is a lightweight structural check and does not verify the token's authenticity. ```ts const AccessTokenSchema = v.pipe( v.string(), v.nonEmpty('Provide an access token.'), v.jwsCompact('The token must be a valid JWS compact string.') ); ``` And for music-related applications, [`isrc`](/api/isrc.md) validates International Standard Recording Codes in both compact and hyphenated form. ```ts const RecordingSchema = v.object({ title: v.pipe(v.string(), v.nonEmpty()), isrc: v.pipe(v.string(), v.isrc('The ISRC is badly formatted.')), }); // Valid formats: // 'USRC17607839' // 'US-RC1-76-07839' ``` #### Compatibility fixes This release also includes a few targeted fixes that improve compatibility with existing data. The [`creditCard`](/api/creditCard.md) action now accepts legacy 13-digit Visa card numbers, which were previously rejected even though they are valid card numbers. We also updated [`isoTimestamp`](/api/isoTimestamp.md) to allow an optional space before the UTC offset. This improves compatibility with PostgreSQL `timestamptz` output such as `2025-05-13 14:15:41.123904 +00:00`. Finally, the type system now handles deeply readonly default and fallback values correctly, which makes schemas with nested readonly data structures easier to use without type workarounds. #### What's next? We will continue to improve integrations, and refine the developer experience around common validation workflows. If there is a validator, transformation, or guide you would like to see next, let us know on [Discord](https://discord.gg/w5mRTETqzv) or open a discussion on [GitHub](https://github.com/open-circle/valibot/discussions). New to Valibot? Check our [quick start guide](/guides/quick-start.md). Coming from Zod? Check our [migration guide](/guides/migrate-from-zod.md). ### Introducing Open Circle Published on January 27, 2026 by [flySewa](https://github.com/flySewa) A few months ago, we moved the Valibot and Formisch repositories from Fabian's personal GitHub account into the Open Circle organization. This post explains what Open Circle is, why we made this change, and what this means for you and the projects going forward. #### TL;DR - Valibot and Formisch now live under the Open Circle GitHub organization - Nothing changes except the GitHub repository URL - Sponsorships are now handled transparently through Open Collective #### What is Open Circle? [Open Circle](https://github.com/open-circle) is a GitHub organization that serves as the shared home for Valibot, Formisch, and future projects that share our philosophy around modularity, type safety, and developer experience. Think of the organization as a container for related projects. It is not a tool or library you install. #### Why Open Circle? ##### Access control As Valibot and Formisch grew, more people wanted to help maintain them, but personal GitHub accounts only allowed full access or bottlenecks through a single maintainer. With Open Circle, we can grant different levels of control to team members without giving any single person full control. ##### Sponsorships Before, sponsorships went directly to a personal account. Now they flow through [Open Collective](https://opencollective.com/valibot), which provides complete transparency. Every donation and expense is visible to everyone. This builds trust with sponsors because they can see exactly where their money goes. It also makes it possible to fairly compensate contributors for their work. Open source maintainers deserve to be paid for the value they create, and a transparent financial structure makes that possible and sustainable long term. Fabian Hiller forwards 100% of his GitHub sponsorings to Open Collective, and going forward this money will mainly be used to fund contributors. ###### Fiscal host [Open Source Collective](https://oscollective.org/) is now our fiscal host. This means that all sponsorings go into a non-profit organization, providing legal and financial infrastructure that helps us operate transparently and sustainably. ##### Unified ecosystem We moved projects like Valibot and Formisch under Open Circle so related projects can be maintained together. Related tools, documentation, and resources are now easier to find for anyone building or maintaining systems with the same design principles. #### What's Changed? The main change is the repository URL and the new financial structure. #### What This Means for the Future - Clearer contribution guidelines for new maintainers - An expanded core team with formalized roles and shared decision-making - Better compensation and recognition for contributor work - A proper home for future projects aligned with our philosophy #### The Reality of Open Source Funding The recent Tailwind situation—revenue down 80%, 75% of engineering laid off despite record usage—is a tough reminder: AI tools reduce doc traffic and paid conversions, making it harder than ever to sustain pure open source projects. We know this firsthand. Valibot currently brings in around $400 per month, with over 50% from a single backer. To fully fund engineering, content, and community work, we estimate needing around $10,000 USD per month. If Valibot, Standard Schema, or Formisch power your business, please consider [sponsoring our work](https://opencollective.com/valibot). Even small amounts help. #### Thank you This move would not be possible without the community around Valibot and Formisch. Contributors, users, and companies running these projects in production have all shaped how these projects have evolved and what they require now structurally. Thanks as well to everyone who has sponsored the work. Your support makes it possible to invest in maintenance decisions that prioritize long term stability over convenience decisions that benefit these projects. ## Posts of 2025 ### Valibot v1.2: Type coercion, AI metadata, and ISBN validation Published on November 24, 2025 by [fabian-hiller](https://github.com/fabian-hiller), [EskiMojo14](https://github.com/EskiMojo14), [flySewa](https://github.com/flySewa) Valibot v1.2 adds powerful transformation actions for type coercion, new metadata features to improve AI tool integration, and ISBN validation. These additions make it easier to work with forms, APIs, and AI-powered applications while maintaining Valibot's modular design and minimal bundle size. > This release also includes an important security fix for a ReDoS vulnerability in the [`emoji`](/api/emoji.md) action. If you're using this action, we strongly recommend upgrading as soon as possible. Huge thanks to [@EskiMojo14](https://github.com/EskiMojo14) for implementing type coercion and examples metadata, and to [@ysknsid25](https://github.com/ysknsid25), a certified librarian, for contributing ISBN validation. #### Type coercion actions Five new transformation actions make type coercion straightforward: [`toBigint`](/api/toBigint.md), [`toBoolean`](/api/toBoolean.md), [`toDate`](/api/toDate.md), [`toNumber`](/api/toNumber.md), and [`toString`](/api/toString.md). Perfect for HTML forms and query parameters where everything arrives as strings. These actions use JavaScript's native coercion functions with added error handling. For example, `toNumber` checks for `NaN` after conversion, and `toDate` validates that the resulting date is valid. ```ts import * as v from 'valibot'; // Coerce form data to proper types const FormSchema = v.object({ quantity: v.pipe(v.string(), v.toNumber()), balance: v.pipe(v.string(), v.toBigint()), createdAt: v.pipe(v.string(), v.toDate()), }); // Input: { quantity: '25', balance: '1000', createdAt: '2025-11-23' } // Output: { quantity: 25, balance: 1000n, createdAt: Date } ``` Unlike Zod's `z.coerce`, Valibot's coercion actions can be composed anywhere in your pipeline, giving you precise control over when and how transformations occur. ```ts const QuerySchema = v.object({ page: v.pipe( v.string(), v.toNumber(), v.integer(), v.minValue(1), v.maxValue(999) ), limit: v.pipe( v.string(), v.toNumber(), v.integer(), v.minValue(1), v.maxValue(100) ), }); ``` #### Examples for AI tools and documentation As AI-powered applications become standard, providing machine-readable metadata about your schemas is increasingly important. The new [`examples()`](/api/examples.md) action lets you attach example values to any schema. ```ts import * as v from 'valibot'; const UserSchema = v.object({ id: v.pipe( v.string(), v.uuid(), v.examples(['550e8400-e29b-41d4-a716-446655440000']) ), name: v.pipe( v.string(), v.nonEmpty(), v.examples(['Alice Smith', 'Bob Johnson']) ), email: v.pipe( v.string(), v.email(), v.examples(['alice@example.com', 'bob@example.com']) ), }); // Extract email examples const emailExamples = v.getExamples(UserSchema.entries.email); ``` When multiple [`examples()`](/api/examples.md) actions appear in a pipeline, [`getExamples()`](/api/getExamples.md) automatically concatenates them using depth-first search, giving you a complete list of all examples in the schema tree. Use this for AI tool integration, generating documentation, or creating test fixtures. #### ISBN validation Thanks to [@ysknsid25](https://github.com/ysknsid25), a certified librarian, we now have built-in [`isbn()`](/api/isbn.md) validation for both ISBN-10 and ISBN-13 formats. ```ts import * as v from 'valibot'; const BookSchema = v.object({ title: v.pipe(v.string(), v.nonEmpty()), isbn: v.pipe(v.string(), v.isbn('The ISBN is badly formatted.')), author: v.pipe(v.string(), v.nonEmpty()), }); // Valid ISBN-10 formats: // '0-306-40615-2', '0306406152', '0 306 40615 2' // Valid ISBN-13 formats: // '978-0-306-40615-7', '9780306406157', '978 0 306 40615 7' ``` The action accepts hyphens and spaces as separators and validates the checksum to ensure mathematical correctness. Perfect for library management systems, bookstores, or any application handling book identifiers. #### Security fix: ReDoS vulnerability This release also includes an important security fix for a ReDoS (Regular Expression Denial of Service) vulnerability in the `EMOJI_REGEX` pattern used by the [`emoji`](/api/emoji.md) action. If you're using the [`emoji`](/api/emoji.md) action in your application, we strongly recommend upgrading to v1.2 as soon as possible. The vulnerability could allow an attacker to cause excessive CPU usage by providing specially crafted input strings. We've updated the regex pattern to eliminate this risk while maintaining the same validation functionality. Thank you to [@makenowjust](https://github.com/makenowjust) for finding and responsibly disclosing this issue. #### Faster builds with tsdown We've switched from tsup to [tsdown](https://www.npmjs.com/package/tsdown) (built on [Rolldown](https://rolldown.rs/), which uses Valibot for validation). This speeds up our build times. #### New partner announcement We're excited to welcome [LambdaTest](https://lambdatest.com/) as a new partner! LambdaTest is a leading cloud-based testing platform that helps developers test their web applications across 3000+ browsers and operating systems. If your company uses Valibot and benefits from our work, please consider supporting the project through [GitHub sponsors](https://github.com/open-circle/valibot). #### What's next? We're creating more guides to help you get the most out of Valibot. Got a specific use case or pattern you'd like us to cover? Let us know on [Discord](https://discord.gg/w5mRTETqzv) or open a discussion on [GitHub](https://github.com/open-circle/valibot/discussions). New to Valibot? [Get started](/start). Coming from Zod? [Migration guide](/migration). [Full changelog](https://github.com/open-circle/valibot/releases/tag/v1.2.0). ### JSON Schema package upgrade Published on June 1, 2025 by [fabian-hiller](https://github.com/fabian-hiller) Valibot's [JSON Schema package](https://github.com/open-circle/valibot/tree/main/packages/to-json-schema) has seen significant growth in adoption over the past few weeks, reaching almost 200,000 monthly downloads on npm. We believe it is particularly popular for documentation and code generation purposes via the OpenAPI specification, as well as for generating structured LLM outputs. As these use cases will probably become more common in future, we have listened to your feedback and decided to invest more time in developing the package to make it extremely powerful. This blog post will introduce the new features added in the last two minor versions. #### Convert input or output of schema The JSON Schema package now supports a new `typeMode` configuration option, which allows you to specify whether you want to convert the input or output of a Valibot schema. This is particularly useful when validating and defining an API endpoint with Valibot and your schemas contain transformations. This is because external developers are usually interested in the input schema of the request data but in the output schema of the response data. ```ts import * as v from 'valibot'; import { toJsonSchema } from '@valibot/to-json-schema'; const ValibotSchema = v.pipe( v.string(), v.decimal(), v.transform(Number), v.number(), v.maxValue(100) ); toJsonSchema(ValibotSchema, { typeMode: 'input' }); // { // $schema: "http://json-schema.org/draft-07/schema#", // type: "string", // pattern: "^[+-]?(?:\\d*\\.)?\\d+$" // } toJsonSchema(ValibotSchema, { typeMode: 'output' }); // { // $schema: "http://json-schema.org/draft-07/schema#", // type: "number", // maximum: 100 // } ``` #### Override default JSON Schema conversion The JSON Schema package now enables you to override the default behaviour of the JSON Schema conversion process. This can be achieved using the three new configuration options: `overrideSchema`, `overrideAction` and `overrideRef`. These let you specify a custom function that will be called for each schema, action or reference during conversion. You can either return a value to override the default behaviour, or return `null` or `undefined` to skip the override. Furthermore, all three callback functions provide the full context via the first function argument, enabling you to perform the same actions as we do internally. ```ts import * as v from 'valibot'; import { toJsonSchema } from '@valibot/to-json-schema'; const ValibotSchema = v.object({ createdAt: v.date() }); toJsonSchema(ValibotSchema, { overrideSchema(context) { if (context.valibotSchema.type === 'date') { return { type: 'string', format: 'date-time' }; } }, }); // { // $schema: "http://json-schema.org/draft-07/schema#", // type: "object", // properties: { // createdAt: { type: "string" format: "date-time" } // }, // required: ["createdAt"] // } ``` #### New global definition storage If you are reusing Valibot schemas within other Valibot schemas, you may be interested in representing these schemas as references in the JSON Schema output. To facilitate this, the JSON Schema package now includes a new global definition storage. This feature allows you to define these definitions after creating a Valibot schema, rather than when calling `toJsonSchema`. This can be particularly useful for larger projects with many schemas, as it helps to keep your code clean and organised. ```ts import * as v from 'valibot'; import { addGlobalDefs, toJsonSchema } from '@valibot/to-json-schema'; const ValibotSchema1 = v.string(); const ValibotSchema2 = v.number(); addGlobalDefs({ ValibotSchema1, ValibotSchema2 }); const ValibotSchema3 = v.tuple([ValibotSchema1, ValibotSchema2]); toJsonSchema(ValibotSchema3); // { // $schema: "http://json-schema.org/draft-07/schema#", // type: "array", // items: [ // { $ref: "#/$defs/ValibotSchema1" }, // { $ref: "#/$defs/ValibotSchema2" } // ], // minItems: 2, // $defs: { // ValibotSchema1: { type: "string" }, // ValibotSchema2: { type: "number" } // } // } ``` #### Output schema definitions only If you're working with the OpenAPI specification, you might be interested in generating only the JSON Schema definitions and overriding the reference IDs to customise them. The new `toJsonSchemaDefs` function and `overrideRef` configuration option now make this possible. ```ts import * as v from 'valibot'; import { toJsonSchemaDefs } from '@valibot/to-json-schema'; const ValibotSchema1 = v.string(); const ValibotSchema2 = v.number(); const ValibotSchema3 = v.tuple([ValibotSchema1, ValibotSchema2]); toJsonSchemaDefs( { ValibotSchema1, ValibotSchema2, ValibotSchema3 }, { overrideRef: (context) => `#/schemas/${context.referenceId}` } ); // { // ValibotSchema1: { type: "string" }, // ValibotSchema2: { type: "number" }, // ValibotSchema3: { // type: "array", // items: [ // { $ref: "#/schemas/ValibotSchema1" }, // { $ref: "#/schemas/ValibotSchema2" } // ], // minItems: 2 // } // } ``` You can also convert global definitions added via the new `addGlobalDefs` function. To do this, call `getGlobalDefs` to retrieve the definitions and pass them as the first argument to `toJsonSchemaDefs`. #### Enhanced metadata support Previously we only supported the [`title`](/api/title.md) and [`description`](/api/description.md) action directly but not the generic [`metadata`](/api/metadata.md) action. This was improved so that title, description and examples can now also be specified via the generic [`metadata`](/api/metadata.md) action, providing you with more flexibility in defining your schemas. ```ts import * as v from 'valibot'; import { toJsonSchema } from '@valibot/to-json-schema'; const ValibotSchema = v.pipe( v.string(), v.email(), v.metadata({ title: 'Email Schema', description: 'A schema that validates email addresses.', examples: ['jane@example.com'], }) ); toJsonSchema(ValibotSchema); // { // $schema: "http://json-schema.org/draft-07/schema#", // type: "string", // format: "email", // title: "Email Schema", // description: "A schema that validates email addresses.", // examples: ["jane@example.com"] // } ``` #### What's next? Firstly, I would like to thank [@Xiot](https://github.com/Xiot) for his detailed feedback on the JSON Schema package and all the new changes. His help in identifying edge cases and advising on the API design was invaluable. Next, we will probably release the Zod-to-Valibot codemod to help you migrate to Valibot if you are interested. Stay tuned for that! ### Valibot v1.1 release notes Published on May 7, 2025 by [fabian-hiller](https://github.com/fabian-hiller), [EltonLobo07](https://github.com/EltonLobo07) Valibot v1.1 is out! This version comes with many new actions and methods to simplify your code even more! For example, you can now define a custom error message with our new [`message`](/api/message.md) method for multiple schemas and actions at once, or use our new [`summarize`](/api/summarize.md) method to summarize your validation errors into a pretty-printable multi-line string. This is our first minor release since v1, and it is worth mentioning that there has been no need for a patch release since then. The work we put into our type and unit tests to reach 100% test coverage is paying off! Before we dive into the details of Valibot v1.1, I want to give a quick update on our newest partners and contributors. #### New partners and contributors We are excited to announce [Motion](https://www.usemotion.com/) as our newest partner. Motion builds an AI-powered project management software and uses Valibot to validate user input in their web application. They are a great example of how Valibot can be used at scale in a real-world application, and we are thrilled to have them on board. In addition, [DigitalOcean](https://www.digitalocean.com/) has extended their support and we will soon be able to announce another exciting partnership. We would also like to welcome [@muningis](https://github.com/muningis) and [@EskiMojo14](https://github.com/EskiMojo14) as new contributors to the project, who have had a big impact on this release. Thank you for your hard work and dedication to making Valibot better for everyone! #### Easier custom error messages Previously, it was a bit cumbersome to define the same custom error messages for multiple schemas and actions. You could use our [`config`](/api/config.md) method or just repeat the same message over and over again. With Valibot v1.1 this got a lot easier! You can now use the [`message`](/api/message.md) method to define a custom error message for multiple schemas and actions at once. ```ts const EmailSchema = v.message( v.pipe(v.string(), v.trim(), v.nonEmpty(), v.email(), v.maxLength(100)), 'The email is not in the required format.' ); ``` #### Simpler error pretty-printing Inspired by Zod v4's new `prettifyError` method and pushed by [@MOZGIII](https://github.com/MOZGIII)'s feedback on GitHub, we now provide an official way to format all your validation errors into a pretty printable multi-line string. This is especially useful for debugging and logging purposes, as it gives you a great overview of everything that went wrong. Imagine the following issues returned after the user entered invalid data into a login form: ```json [ { kind: "validation", type: "email", input: "jane@example", expected: null, received: "\"jane@example\"", message: "Invalid email: Received \"jane@example\"", requirement: /^[\w+-]+(?:\.[\w+-]+)*@[\da-z]+(?:[.-][\da-z]+)*\.[a-z]{2,}$/iu, path: [...], }, { kind: "validation", type: "min_length", input: "1234567", expected: ">=8", received: "7", message: "Invalid length: Expected >=8 but received 7", requirement: 8, path: [...], } ] ``` Using the new [`summarize`](/api/summarize.md) method, you can now format this into a human-readable string: ``` × Invalid email: Received "jane@example" → at email × Invalid length: Expected >=8 but received 7 → at password ``` We will consider using [`summarize`](/api/summarize.md) for the default error message when throwing a [`ValiError`](/api/ValiError.md) for Valibot's next major release. Please join [this issue](https://github.com/open-circle/valibot/issues/1139) on GitHub to discuss with us. #### Extract metadata with one line A probably still very underrated feature are our metadata actions. With [`title`](/api/title.md), [`description`](/api/description.md) and [`metadata`](/api/metadata.md) we provide 3 common metadata actions already out-of-the-box. But Valibot's modularity also allows you to build your own metdata actions on top! For example, this allows you to build your own ORM on top of Valibot to define the schema of your models. ```ts import * as o from 'orm-actions'; import * as v from 'valibot'; const UserTableSchema = v.pipe( v.object({ id: v.pipe(v.number(), v.integer(), o.primaryKey()), name: v.pipe(v.string(), v.nonEmpty(), o.index()), email: v.pipe(v.string(), v.email(), o.index()), // ... }), o.table('users') ); ``` With Valibot v1.1 we have expanded our out-of-the-box capabilities with 3 new methods. With [`getTitle`](/api/getTitle.md), [`getDescription`](/api/getDescription.md) and [`getMetadata`](/api/getMetadata.md) you can now extract the metadata of a schema with a single line of code. All 3 methods use special algorithms to extract the correct metadata even if multiple metadata actions are defined. For example, [`getMetadata`](/api/getMetadata.md) shallow merges multiple metadata using depth-first search and returns a correctly typed object. ```ts const Schema1 = v.pipe(v.string(), v.metadata({ key1: 'foo', key2: 'bar' })); const Schema2 = v.pipe(Schema1, v.metadata({ key2: 'baz', key3: 'qux' })); const metadata = v.getMetadata(Schema2); // { key1: 'foo', key2: 'baz', key3: 'qux' } ``` #### Parse and stringify JSON A probably long awaited feature was a native way to parse and stringify JSON in Valibot's pipelines. This is now possible with our new [`parseJson`](/api/parseJson.md) and [`stringifyJson`](/api/stringifyJson.md) transformation actions. They automatically catch errors and take care of all edge cases, and you can combine them with other schemas and actions for 100% reliable results. ```ts const ProductSchema = v.pipe( v.string(), v.parseJson(), v.object({ id: v.pipe(v.string(), v.uuid()), name: v.pipe(v.string(), v.nonEmpty()), price: v.pipe(v.number(), v.minValue(0)), tags: v.pipe(v.array(v.string()), v.maxLength(10)), }) ); ``` For a full list of new features and changes, please see [the release notes](https://github.com/open-circle/valibot/releases/tag/v1.1.0) on GitHub. #### The future is bright! [@EltonLobo07](https://github.com/EltonLobo07) has started working on an official Zod-to-Valibot codemod! We will be releasing some updates and demos soon. We also plan to focus a bit more on educating developers on the benefits of choosing Valibot. Stay tuned for more content on social media and this blog in the coming weeks and months! We have also started using [milestones on GitHub](https://github.com/open-circle/valibot/milestones) to keep track of our progress and give you a better overview of what we are currently working on. Feel free to take a look at what's coming in Valibot v1.2 and beyond and please contact us if you have any questions or suggestions. We are always happy to hear from you! ### Valibot v1 - The 1 kB schema library Published on March 19, 2025 by [fabian-hiller](https://github.com/fabian-hiller) I am excited to announce the release of Valibot v1. Valibot is a modular and fully tree-shakable schema library that helps you describe and validate your data with a type-safe and easy-to-remember API. As a 1 kB alternative to Zod, Valibot is perfect for validating forms and securing backend-frontend communication with a single source of truth. #### How everything started Some of you may remember my [introduction post](https://www.builder.io/blog/introducing-valibot) that I published on Builder.io in July 2023 with my supervisors Miško Hevery (creator of Angular and Qwik) and Ryan Carniato (creator of SolidJS). Back then, as part of my [bachelor thesis](https://valibot.dev/thesis.pdf), I was investigating how to drastically reduce the bundle size of JavaScript libraries by more then 90%. As part of my research, I analysed [Zod](https://zod.dev/), [ArkType](https://arktype.io/) and [Typia](https://typia.io/), and created with Valibot a new schema library from scratch. If all this makes you curious, you should definitely check out the talk I gave last October at the web development meetup I hosted with Miško Hevery and Rich Harris at Pace University. #### How the 1 kB thing works Similar to how types can be defined in TypeScript, Valibot allows you to define a schema with various small functions. This applies to primitive values like strings as well as to more complex data sets like objects. In addition, the library helps to perform more detailed validations and transformations with the help of pipelines. ```ts import * as v from 'valibot'; // 1.31 kB // Create login schema with email and password const LoginSchema = v.object({ email: v.pipe(v.string(), v.email()), password: v.pipe(v.string(), v.minLength(8)), }); // Infer output TypeScript type of login schema as // { email: string; password: string } type LoginData = v.InferOutput; // Throws error for email and password const output1 = v.parse(LoginSchema, { email: '', password: '' }); // Returns data as { email: string; password: string } const output2 = v.parse(LoginSchema, { email: 'jane@example.com', password: '12345678', }); ``` Instead of relying on a few large classes or functions with many methods, Valibot's API design is based on many small and independent functions. Each with just a few lines of code and a single task. This modular design has several advantages. On the one hand, it provides the flexibility to replace and extend Valibot's functions with custom code. On the other hand, it makes the source code more robust and secure, because the functionality of a single function as well as special edge cases can be tested more specifically. This makes it easy for us to achieve 100% test coverage, reducing bugs to a minimum. However, perhaps the biggest advantage is that a bundler like [Rolldown](https://rolldown.rs/) or [Rspack](https://rspack.dev/) can use the static import statements to remove any code that is not needed. Thus, only the code that is actually used ends up in your production build. This allows us to extend the functionality of the library without increasing your individual bundle size, which is between 1 and 2 kB for most users. This can make a big difference, especially for client-side validation and serverless environments, by reducing bundle size and speeding up startup time. > Curious to learn more? Check out the [publication of my bachelor thesis](https://www.builder.io/blog/valibot-bundle-size), where I explain why Valibot can be 10x smaller than Zod. #### Who is using Valibot? We are proud that Valibot is used on sites like [The Guardian](https://www.theguardian.com/) and that our work adds value to more than 50,000 dependent public GitHub repositories. Our users include many startups and open source projects such as [Rolldown](https://rolldown.rs/) and [React Router](https://reactrouter.com/). Chances are you have already run a Valibot schema on your devices without even knowing it. Valibot has grown from a research paper to a community-driven project with more than 140 contributors and various partners. I want to give a shoutout to [Elton](https://github.com/EltonLobo07), who joined the project as a co-maintainer. He helped rewrite the entire library from scratch and contributed a significant amount to the API reference in our docs. Another shoutout goes to [CodingBill](https://github.com/Bilboramix), who has become a moderator on Discord. If you have any schema-related questions, he is the guy to talk to! Below you will find our most influential contributors as well as our current and past sponsors and partners who helped make Valibot v1 possible. If your company uses Valibot and benefits from our work, please consider supporting the project through [GitHub sponsors](https://github.com/open-circle/valibot). #### What's next on our list? With Valibot v1, you can rest assured that the library is stable and ready for production. But we are not done yet, we are basically just getting started. On our list are things like a VS Code extension to improve the developer experience, a Zod-to-Valibot codemod to increase adoption, and an OpenAPI package to improve compatibility with other libraries. If you would like to lead or contribute to these efforts, please [contact us on Discord](https://discord.gg/w5mRTETqzv)! > Just in case this is the first time you hear about Valibot and you have never tried it before, feel free to take a look at our [quick start guide](/guides/quick-start.md) and experiment with the library in our [online playground](/playground/). ### Valibot v1 RC is now available Published on February 10, 2025 by [fabian-hiller](https://github.com/fabian-hiller), [EltonLobo07](https://github.com/EltonLobo07) After taking three steps back about a year ago to rewrite the entire library from scratch, Valibot has come back stronger than ever. In the past 12 months, the project has grown from 300k monthly downloads on npm to now more than 4.5 million. Many of you have been waiting for our first stable release, and with this blog post I am happy to announce that we are very very close. With this announcement post we want to look back on our work of the last months and show you the new features and functions you will enjoy when you upgrade to Valibot v1 RC. #### Better API design In June 2024 we released v0.31.0, introducing our new [`pipe`](/api/pipe.md) API and massively improving Valibot's type safety. I recommend reading [the blog post](/blog/valibot-v0.31.0-is-finally-available.md) we published at the time to learn more. Even though it was a big change, we got a lot of positive feedback. That's why we kept [the new mental model](/guides/mental-model.md) and have continued to improve the new API since then. #### Better compatibility A few months later, in September 2024, we intensified our work on [Standard Schema](https://standardschema.dev/). What had been just [an idea](https://x.com/colinhacks/status/1634284724796661761) was now starting to take shape. Together with [@colinhacks](https://github.com/colinhacks), the creator of [Zod](https://zod.dev/), we developed a first draft of the specification and investigated how it could be integrated into our libraries. Later, [@ssalbdivad](https://github.com/ssalbdivad), the creator of [ArkType](https://arktype.io/), joined us to help us get the details right. Standard Schema could be a game-changer not only for the further adoption and growth of Valibot, but also for the JavaScript ecosystem in general. If you haven't heard of it yet, you should definitely check it out. Thanks to Colin and David for this great collaboration! Another part of our ecosystem compatibility efforts has been to develop and provide an official JSON Schema solution. Together with [@gcornut](https://github.com/gcornut), we created a new package called `@valibot/to-json-schema` that can convert Valibot schemas to their JSON Schema representation. I am proud of our implementation because it is fast, highly efficient (1.67 kB gzip), and can handle complex cases such as recursion. If you are interested in this topic, please have a look at our [JSON Schema guide](/guides/json-schema.md). ```ts import { toJsonSchema } from '@valibot/to-json-schema'; import * as v from 'valibot'; const ValibotEmailSchema = v.pipe(v.string(), v.email()); const JsonEmailSchema = toJsonSchema(ValibotEmailSchema); // -> { type: 'string', format: 'email' } ``` #### Better object schema Our initial object schema implementation was good, but not perfect. It could not distinguish between missing and present but undefined properties, resulting in mismatches between validation logic and generated TypeScript types in edge cases. Over the past few weeks, we have fixed these issues and are now able to fully support TypeScript's [`exactOptionalPropertyTypes` configuration](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes). We also fixed the order of optional properties in the generated TypeScript types. Thanks to [@andersk](https://github.com/andersk) for driving this effort! #### Better tree shaking [@antfu](https://github.com/antfu) himself created [a PR](https://github.com/open-circle/valibot/pull/995) in December 2024 to further improve Valibot's tree shaking capabilities. If you use some of your schemas only for their TypeScript type but not at runtime, for example to validate unknown data, they can now be fully tree-shaken and excluded from your production build, reducing the bundle size of your application even further. #### Better documentation A few weeks ago we made some nice UI and UX improvements to Valibot's documentation and playground. For example, there is now a "play" button to open the code snippets of our guides directly in our playground. Also, you can now enable chapter navigation in Valibot's documentation to make it easier to navigate through the content of a large page. I would also like to point out that [@EltonLobo07](https://github.com/EltonLobo07) finalized the last pages of our [API reference](/api/). With more than 600 pages, this was a huge effort driven by several community members over the last year. Thanks to everyone who contributed! #### Other highlights In addition to these bigger changes, we shipped many new functions and improved the implementation of existing ones. For example, we added a [`function`](/api/function.md) and [`promise`](/api/promise.md) schema along with [`args`](/api/args.md), [`returns`](/api/returns.md), and [`awaitAsync`](/api/awaitAsync.md) actions to support validation of JavaScript functions and promises. We also shipped a [`rawCheck`](/api/rawCheck.md) and [`rawTransform`](/api/rawTransform.md) action to give you full control in edge cases. They allow you to hook into the raw implementation of [`pipe`](/api/pipe.md) to write advanced validation and transformation logic. Another cool addition to Valibot's capabilities is our new [metadata feature](/guides/pipelines.md#metadata). This is especially useful for documentation purposes and for deep integration of the schema library with other libraries. For example, an ORM could now provide its own metadata actions such as `table`, `primaryKey` and `index` built on top of Valibot instead of writing everything from scratch. ```ts import * as o from 'orm-actions'; import * as v from 'valibot'; const UserTableSchema = v.pipe( v.object({ id: v.pipe(v.number(), v.integer(), o.primaryKey()), name: v.pipe(v.string(), v.nonEmpty(), o.index()), email: v.pipe(v.string(), v.email(), o.index()), // ... }), o.table('users') ); ``` There are many more new exciting functions like [`assert`](/api/assert.md), [`rfcEmail`](/api/rfcEmail.md) and [`maxWords`](/api/maxWords.md). Please have a look at [the release on GitHub](https://github.com/open-circle/valibot/releases/tag/v1.0.0-rc.0) and our [API reference](/api/) for more details. #### What's next? Many more validation actions like `ltValue`, `gtValue`, `values`, `notValues`, `slug`, `toSnakeCase` and `btcAddress` are already implemented and will be reviewed and merged soon. This shows the great advantage of a modular architecture, as we can add more and more features without increasing the size of your individual bundle. One day, everything you need will be just a Valibot action away. 😎 We expect Valibot to reach v1 in 3 to 6 weeks. Please upgrade now to give us lots of feedback in the meantime. An official migration guide will be available soon, but most of you who are already on >=v0.31.0 will most likely be able to upgrade without touching any existing code. ### Should we change the object schema? Published on January 18, 2025 by [fabian-hiller](https://github.com/fabian-hiller), [andersk](https://github.com/andersk), [EltonLobo07](https://github.com/EltonLobo07) Great news! Valibot v1 RC is just around the corner. There are only a few issues holding us back. One of them is [issue #983](https://github.com/open-circle/valibot/issues/983), which may lead to a breaking change. That's why I want to discuss this with you before making a final decision. This blog post will explain the current situation and the reasons for the proposed changes. #### The problem Currently, Valibot does not distinguish between missing and undefined object entries. This leads to a mismatch between the input and output values of a schema and their types when TypeScript's [`exactOptionalPropertyTypes` configuration](https://www.typescriptlang.org/tsconfig/#exactOptionalPropertyTypes) is enabled. ```ts import * as v from 'valibot'; // This throws no error and types `output` as `{ key?: string }` const Schema = v.object({ key: v.optional(v.string()) }); const output = v.parse(Schema, { key: undefined }); // TypeScript thinks that `key` is a string if it is present // but this is wrong because `key` is actually `undefined` if ('key' in output) { const key = output.key; } ``` #### The solution The solution to this mismatch is to finalize [PR #1013](https://github.com/open-circle/valibot/pull/1013) and change the implementation of [`object`](/api/object.md) and [`optional`](/api/optional.md) to distinguish between missing and undefined object entries. To allow missing entries, [`optional`](/api/optional.md) must be used as the outermost schema of an object entry. To explicitly allow `undefined` as a value, another schema function called [`undefinedable`](/api/undefinedable.md) must be used. ```ts const Schema = v.object({ key1: v.string(), // key1: string key2: v.optional(v.string()), // key2?: string key3: v.undefinedable(v.string()), // key3: string | undefined key4: v.optional(v.undefinedable(v.string())), // key4?: string | undefined }); ``` #### The impact As seen in the previous code example, defining an object entry that can be both missing and undefined gets a little verbose. This gets especially ugly if you define a default value for both cases. ```ts const Schema = v.object({ key: v.optional( v.undefinedable(v.string(), 'undefinedable_default'), 'optional_default' ), }); ``` To fix this, we could add a new function that covers both. The problem is that there is a third schema function called [`nullable`](/api/nullable.md) that also allows `null` values. So adding a new function to cover every possible combination of [`optional`](/api/optional.md), [`undefinedable`](/api/undefinedable.md) and [`nullable`](/api/nullable.md) would result in 7 functions in total. I doubt this will make the API any better. #### Let's discuss What do you think? Should we distinguish between missing and undefined object entries and fully support TypeScript's `exactOptionalPropertyTypes` configuration? If so, should we remove [`nullish`](/api/nullish.md) and only provide 3 strong primitives with [`optional`](/api/optional.md), [`undefinedable`](/api/undefinedable.md) and [`nullable`](/api/nullable.md)? Or should we add 4 more functions to cover all possible combinations? If so, what names would you use? Your opinion matters to me. I encourage everyone to share their thoughts. Even quick feedback like "I like this ... and I don't like that ..." is welcome and will help to shape Valibot's future API design. Please discuss with us on [GitHub](https://github.com/open-circle/valibot/discussions/1022) or share your thoughts on social media. ## Posts of 2024 ### How Valibot has evolved this year Published on June 19, 2024 by [fabian-hiller](https://github.com/fabian-hiller) In the context of an independent study at the [Seidenberg School of Computer Science and Information Systems](https://www.pace.edu/seidenberg) at Pace University in New York I continued the maintenance, research and development for my open source project Valibot, which I started 11 months ago with Miško Hevery and Ryan Carniato as part of my [bachelor's thesis](/thesis.pdf) at the Stuttgart Media University. Especially in the last months the project has evolved a lot. In this blog post I would like to look back on our efforts and achievements. #### Statistics Last year, Valibot was downloaded 350k times via the npm registry. With 210k monthly downloads in January, the project started very successfully into the new year. I am very happy to report that we were able to increase the total downloads to over 3 million and the monthly downloads to almost 800k. This means that in less than 6 months the total downloads and the monthly downloads have increased by more than 500%. Our website, which contains a detailed documentation of the library, has been visited more than 60k times with more than 400k page views since the beginning of this year. According to our website statistics, Valibot is used all over the world. The top countries with more than 4% share of visits are the United States, Japan, Germany, France and India. On Github the project now has 5,483 stars and 102 contributors. During this year, the community created 323 new issues, pull requests and discussions, which I labeled and answered. I released 8 new versions with various improvements. I will highlight some of them in the next sections of this post. #### Website In December 2023 we started to add a detailed [API reference](/api/) to provide additional information besides the guides that mainly explain the general concept of Valibot. With more than 400 symbols this was a pretty big task and I am happy about the [support](https://github.com/open-circle/valibot/issues/287) I got from the community. Some references are still missing and will be added in the next weeks. Another highlight is the new [playground](/playground/) that I added in February with the help of [Milo](https://github.com/milomg), a SolidJS core team member and student at the University of Toronto. The playground can be used to write, test, and share schemas directly on our website. Since then, the button to execute code has been clicked more than 13k times. #### Library On February 6th, we shipped a pretty big update with [v0.28.0](https://github.com/open-circle/valibot/releases/tag/v0.28.0), which included our [i18n feature](/guides/internationalization.md). We also refactored a large part of Valibot's source code and expanded and improved the default error messages. They now provide much more helpful details than before, and I am happy to announce that the community has translated them into 21 languages besides English for our official i18n package. At the end of February, I started preparing our v1 release in my head, until [@Demivan](https://github.com/Demivan) and [@xcfox](https://github.com/xcfox) came up with two API proposals. I was skeptical at first, but I also knew that the API design had some major limitations that could harm the developer experience and the project in general in the long run. That's why I reached out to the community on [GitHub](https://github.com/open-circle/valibot/discussions/463) to get their feedback. It was great to see that more than 70 developers participated. The overall feedback was very positive. So I started working on a [first draft](/blog/first-draft-of-the-new-pipe-function.md) in March. Back then, the only change I planned to introduce was the new [`pipe`](/api/pipe.md) method, but in the process I saw many more areas where I could improve Valibot. In the end, I started to rewrite the whole library from scratch with the help of the community. The results are promising. Feel free to read my [previous blog post](/blog/valibot-v0.31.0-is-finally-available.md) for more details. #### Upcoming In the next few weeks, I plan to investigate the implementation of a `function` and `promise` schema, and take a look at [PR #655](https://github.com/open-circle/valibot/pull/655), which introduces a metadata feature. After that, I plan to focus on our documentation to expand and update our [API reference](/api/). As we get closer to our v1 release, I will be thinking about a v1 roadmap soon. At the moment I expect to release a release candidate in August and the final release in September. Stay tuned for further updates! > Recently I was a guest on Nick's show and talked about Valibot. If you missed our stream, you can watch it [here](https://www.youtube.com/live/fR2GJx_SQTE) on YouTube. ### Valibot v0.31.0 is finally available Published on June 6, 2024 by [fabian-hiller](https://github.com/fabian-hiller) After 3 months of hard work I am happy to announce that Valibot v0.31.0 is finally available. This is not a regular release as we have rewritten the whole library from scratch. Based on your feedback and all the lessons learned from the past, we were able to drastically improve the mental model, bundle size, flexibility, type safety and stability of the library. I would like to highlight some of the major improvements and changes in this post. > Because this release introduces some breaking changes, we put a lot of effort into making the migration experience as smooth as possible. I worked closely with the open source community to create a detailed [migration guide](/guides/migrate-to-v0.31.0.md) and two codemods to automatically update your schemas. #### Mental model We believe that Valibot will be easier to use because we have drastically improved the mental model. For a modular library like Valibot, this is crucial, as each functionality is imported as its own function. The mental model is now reduced to **schemas**, **methods** and **actions**. Schemas are used to validate a specific data type like a string, object or date. They are the starting point for using Valibot. Methods help you either modify or use a schema. For example, the new [`pipe`](/api/pipe.md) method extends the functionality of a schema by adding additional validation and transformation rules. When using a method, you always pass a schema as the first argument. Finally, there are actions. Actions are used exclusively in the pipeline of a schema. They can be used to further validate or transform a particular data type. For example, the following schema can be used to trim a string and check if it is a valid email address. > We recommend using Valibot with a wildcard import as this improves the developer experience. Tree shaking still works when using `v.`. We tested it with various build-systems. You can find a list of all schemas, methods and actions in our [API reference](/api/). #### Bundle Size After increasing the initial bundle size by introducing new features to the core of Valibot in the past, I am pleased to announce that this release reduces the individual bundle size of your schemas by approximately 15 to 30% without losing any functionality. This has been achieved by simplifying and unifying the internal structure and implementation. For example, the [`string`](/api/string.md) schema required 800 bytes in the previous version, while the same schema now requires only 560 bytes. This is a reduction of 30%. As the library is optimized for compression, and most of these bytes are shared across all schemas and actions, the bundle size increases only slightly when adding more schemas or actions. For example, adding the [`number`](/api/number.md) schema increases the bundle size by only 40 bytes, resulting in a total bundle size of 600 bytes. This is a huge improvement and makes Valibot even more attractive when using schemas to validate unknown data in the browser, on the edge, or in serverless environments. A smaller bundle size can greatly improve the startup performance of your application by reducing the time it takes to download and parse the JavaScript code. > I am planning an experimental library with the same external API but slightly less functionality. If it works out, it could become a drop-in replacement if you do not need the full functionality of Valibot. I expect the initial bundle size of this library to start around 200 bytes. Stay tuned! #### Flexibility Another huge improvement is the flexibility gained by our new [`pipe`](/api/pipe.md) method. Compared to the previous versions, we removed many limitation. For example, it is now possible to transform the data type inside of pipelines. This simplifies the usage and readability as it reduces function nesting. ```ts // With the previous API const BirthdaySchema = v.brand( v.transform(v.string([v.isoDate()]), (input) => new Date(input)), 'birthday' ); // With the brand new API const BirthdaySchema = v.pipe( v.string(), v.isoDate(), v.transform((input) => new Date(input)), v.brand('birthday') ); ``` Furthermore, it is now possible to extend the pipeline of an existing schema by adding additional validation and transformation rules. This makes it possible to reuse already created schemas to construct more specific ones. Similar to how you extend a class in object-oriented programming to make it more specialized. ```ts const EmailSchema = v.pipe(v.string(), v.email()); const GmailSchema = v.pipe(EmailSchema, v.endsWith('@gmail.com')); ``` #### Type safety After I started working on a first draft in early March, I spent at least two weeks thinking about the structure and interplay of everything. I also thought a lot about the type safety of the library. Previously, many parts were only typed generically. An example is the issues that a schema returns when parsing invalid data. Even though each issue contains very specific data depending on the schema or action, they were all previously typed as a generic `SchemaIssue`. This changes with this release. Wherever possible, we have tried to achieve 100% type safety. You can even infer the issue type of any schema or action used. We expect this improvement to result in a better developer experience and fewer bugs. #### Stability Once most of the decisions were made, we spent a lot of time writing unit and type tests. This explains why it took us two and a half months to release the first release candidate. The tests have allowed us to fix some previously undetected bugs and enhance the stability of the library in the long run. Before v1, I would like to further improve the test coverage to be able to fully guarantee the functionality of each function. This is an ambitious goal, but I am confident that we will reach it soon. #### Thank you! This release was a team effort! Because so many of you contributed to this release in so many different ways, I am not able to mention everyone. However, I have tried to link all of your GitHub profiles and highlight some very important contributions. Please ping me if your avatar is missing. Thanks to [@Demivan](https://github.com/Demivan) and [@xcfox](https://github.com/xcfox) for their contributions to the new API design. [@Demivan](https://github.com/Demivan) had the initial idea for the [`pipe`](/api/pipe.md) method and helped me with many decisions along the way. Thanks to [@ariskemper](https://github.com/ariskemper) for influencing the new structure of our unit tests, thanks to [@EltonLobo07](https://github.com/EltonLobo07) for porting over 25 actions to the new implementation and thanks to [@anuraghazra](https://github.com/anuraghazra) for finding a TypeScript workaround that made the new [`pipe`](/api/pipe.md) method possible in this way. I would also like to thank our partners and sponsors who provide intellectual and financial support to the project. In particular, I would like to thank the [Seidenberg School of Computer Science and Information Systems](https://www.pace.edu/seidenberg) at Pace University. Without their support over the past few months, this release would not have been possible. > Please [contact us](mailto:hello@fabianhiller.com) if your organization is interested in becoming a partner of Valibot to help us ensure the long-term development and maintenance of the project. ### First draft of the new pipe function Published on April 2, 2024 by [fabian-hiller](https://github.com/fabian-hiller), [Demivan](https://github.com/Demivan) One month after I published my [last post](/blog/should-we-change-valibots-api.md) to discuss Valibot's API, it is finally time to share a first draft with you. I have not only implemented the `pipe` functions. In the end, I basically rewrote the whole library. I improved many things while keeping performance, bundle size and developer experience in mind. > Except for the `pipe` function, the basic API remains the same. However, to make the migration as smooth as possible for you, we plan to partner with [Codemod](https://codemod.com/) to automatically migrate most of the changes with a single CLI command. #### `pipe` function First of all, thank you for your feedback! It was amazing to see how the Valibot community came together to discuss this change. In total, my post on X got more than 15,000 impressions and more than 70 developers participated on GitHub. ##### Better mental model The `pipe` function reduces the mental model of Valibot to schemas, methods and actions. Schemas validate data types like strings, numbers and objects. Methods are small utilities that help you use or modify a schema. For example, the `pipe` function is a "method" that adds a pipeline to a schema. Actions are used exclusively within a pipeline to further validate or transform a particular data type. ##### Simplified source code By moving the pipeline into its own function, we made Valibot even more modular. This has several advantages. The most obvious is that it reduces the bundle size if your schemas do not require this feature. But even if you use the pipe function, you can expect an average 15-20% reduction in bundle size with the new implementation. Another great benefit is that we were able to simplify the schema functions by removing the `pipe` argument. Schema functions are now more independent and only cover the validation of their data type. This allows us to simplify the unit tests, making the library even safer to use. Furthermore, for any primitive data type schema such as `string`, we could remove its async `stringAsync` implementation, since this was only necessary to support async validation within the `pipe` argument. This will further reduce the overall bundle size of the library. ##### More flexibility and control The `pipe` function also adds flexibility to the library and gives you more control over your schemas. Previously, it was very difficult to extend the pipeline of a schema with additional validations or transformations. This changes with the `pipe` functions because they can be nested, making your code more composable. ```ts import * as v from 'valibot'; const EmailSchema = v.pipe(v.string(), v.email()); const GmailSchema = v.pipe(EmailSchema, v.endsWith('@gmail.com')); ``` Another great benefit is that pipelines now support data type transformations. There is no longer any confusion between a transformation method and pipeline transformations. You can even add another schema function after a transformation to validate its output. ```ts import * as v from 'valibot'; const PixelSchema = v.pipe( v.string(), v.regex(/^\d+px$/), v.transform(parseInt), v.number(), v.maxValue(100) ); ``` When building forms, this also gives you more control over the input and output type of a schema. It is now possible for a field to have an input type of `string | null` but an output type of `string` without specifying a default value. ```ts import * as v from 'valibot'; // This schema const ProfileSchema = v.object({ name: v.string(), age: v.pipe(v.nullable(v.number()), v.number()), bio: v.pipe(v.nullable(v.string()), v.string()), }); // Has this input type type ProfileInput = { name: string; age: number | null; bio: string | null; }; // But this output type type ProfileOutput = { name: string; age: number; bio: string; }; ``` #### `object` schema Another major change is that I have removed the `rest` argument from the `object` schema. This reduces the bundle size by up to 150 bytes if you don't need this functionality. If you do need it, you can use the newly added `objectWithRest` schema, and to allow or not allow unknown entries, there is now a special `looseObject` and `strictObject` schema for a better developer experience. ```ts import * as v from 'valibot'; const NormalObjectSchema = v.object({ ... }); const ObjectWithRestSchema = v.objectWithRest({ ... }, v.string()); const LooseObjectSchema = v.looseObject({ ... }); const StrictObjectSchema = v.strictObject({ ... }); ``` > I also plan to remove the `rest` argument from the `tuple` schema and provide a `tupleWithRest`, `looseTuple` and `strictTuple` schema. #### Type safety I am happy to announce that I have been able to drastically improve the type safety of the library. Previously, like many other libraries out there, less important parts were only generally typed. A concrete example is the issues that Valibot returns when the data does not match your schema. Even though each issue contains very specific data depending on the schema and validation function, they were all previously typed with a generic `SchemaIssue` type. With this rewrite, each schema and validation function brings its own issues type. This way each schema knows exactly what kind of issues can occur. We also added the `isOfKind` and `isOfType` util functions to filter and handle specific objects in a type-safe way. #### Unit tests Valibot was kind of my first project that came with unit testing. When I implemented the first tests, I had very little experience with testing. Even though the current tests cover 100% of the library, I don't feel comfortable releasing v1 before rewriting them and making them perfect. Also, we have had some type issues in the past. That's why I'm also planning to add type testing to ensure the developer experience when using TypeScript. It is important to me to ensure the functionality of the library and to make sure that no unexpected bugs occur when changes are made to the code in the future. Developers should be able to fully rely on Valibot. If you are a testing expert, please have a look and review the `string` and `object` schema tests. #### Next steps I created [this draft PR](https://github.com/open-circle/valibot/pull/502) for two reasons. One is to get feedback from the community before rewriting the entire library. I have already discussed a lot of details with [@Demivan](https://github.com/Demivan) and think we are on a good path, but maybe we missed something. Feel free to investigate the source code and reach out via the comments. With the following commands you can clone, bundle and play with the new source code in advance: ```bash # Clone repository git clone git@github.com:open-circle/valibot.git # Switch branch git switch rewrite-with-pipe # Install dependencies cd ./valibot && pnpm install # Bundle library cd ./library && pnpm build # Modify `playground.ts` # Run playground pnpm play ``` On the other hand, I created [this PR](https://github.com/open-circle/valibot/pull/502) also to ask you to help me implement the missing parts. If you are passionate about open source and want to join an exponentially growing open source project, now might be the time. Starting April 8, everyone is welcome to take over the implementation of a schema or action function. I recommend starting with a simple function like `minBytes`, where you only have to copy and modify the `minLength` action, and maybe look at the previous implementation of the same function. To participate, just add a comment to [this PR](https://github.com/open-circle/valibot/pull/502) with the part you want to take over. Please only add a comment if you are able to do the implementation within 3 days, to not block the rewrite. After you get my thumbs up, you can create a PR that merges your changes into the `rewrite-with-pipe` branch. The goal is to have everything implemented this month. ⚡️ ### Should we change Valibot's API? Published on February 29, 2024 by [fabian-hiller](https://github.com/fabian-hiller), [Demivan](https://github.com/Demivan), [xcfox](https://github.com/xcfox) Hi folks, I am [Fabian](https://github.com/fabian-hiller), the creator and maintainer of Valibot. Today I am writing this message to you to discuss the further development of Valibot's API. With currently more than 80,000 weekly downloads, the library is growing into a serious open source project used by thousands of JavaScript and TypeScript developers around the world. In the last few days I received two [API proposals](https://github.com/open-circle/valibot/discussions/453) from [@Demivan](https://github.com/Demivan) and [@xcfox](https://github.com/xcfox). Both of them addressed similar pain points of Valibot's current API design. As our v1 release is getting closer and closer, it is important to me to involve the community in such a big decision. #### Current pain points Valibot's current mental model is divided into schemas, methods, validations, and transformations. Schemas validate data types like strings, numbers and objects. Methods are small utilities that help you use or modify a schema. Validations and transformations are used in the `pipe` argument of a schema. They can make changes to the input and check other details, such as the formatting of a string. ```ts // With Valibot's current API const EmailSchema = string([toTrimmed(), email(), endsWith('@example.com')]); ``` A drawback of this API design is that the current pipeline implementation is not modular. This increases the initial bundle size of simple schemas like [`string`](https://valibot.dev/api/string/) by more than 200 bytes. This is almost 30% of the total bundle size. Another pain point is that the current pipeline implementation does not allow you to transform the data type. This forced me to add a [`transform`](https://valibot.dev/api/transform/) method, resulting in two places where transformations can happen. ```ts // With Valibot's current API const NumberSchema = transform(string([toTrimmed(), decimal()]), (input) => { return parseInt(input); }); ``` Speaking of methods, it can quickly become confusing if you need to apply multiple methods to the same schema, as these functions must always be nested. ```ts // With Valibot's current API const LengthSchema = brand( transform(optional(string(), ''), (input) => input.length), 'Length' ); ``` The last pain point that comes to mind is that the current API design gives you less control over the input and output type of a schema. When working with form libraries, it can be useful to have an input type of `string | null` for the initial values, but an output type of just `string` for a required field. #### The `pipe` function After several design iterations, [@Demivan](https://github.com/Demivan) came up with the idea of a `pipe` function. Similar to the current `pipe` argument, it can be used for validations and transformations. The first argument is always a schema, followed by various actions or schemas. ```ts // With the new `pipe` function const LoginSchema = object({ email: pipe(string(), minLength(1), email()), password: pipe(string(), minLength(8)), }); // With Valibot's current API const LoginSchema = object({ email: string([minLength(1), email()]), password: string([minLength(8)]), }); ``` The big difference is that the `pipe` function also allows you to transform the data type. This would allow us to move methods like [`transform`](https://valibot.dev/api/transform/) and [`brand`](https://valibot.dev/api/brand/) into the pipeline. This prevents nesting of functions for these methods and simplifies the mental model. With the `pipe` function, the mental model is reduced to schemas, methods, and actions. Actions are always used within the `pipe` function to further validate and transform the input and type of a schema. > Alternative names for `pipe` are `flow` (idea by [@mtt-artis](https://github.com/mtt-artis)), `schema` (idea by [@genki](https://github.com/genki)), `compose` (idea by [@MohammedEsafi](https://github.com/MohammedEsafi)), and `vali` (idea by [@Hugos68](https://github.com/Hugos68)). Please [share](https://github.com/open-circle/valibot/discussions/463) your thoughts. #### The advantages The `pipe` function makes Valibot even more modular, resulting in a smaller bundle size for very simple schemas without a pipeline. For very complex schemas it reduces function nesting when using [`transform`](https://valibot.dev/api/transform/) and [`brand`](https://valibot.dev/api/brand/). ```ts // With the new `pipe` function const LengthSchema = pipe( optional(string(), ''), transform((input) => input.length), brand('Length') ); // With Valibot's current API const LengthSchema = brand( transform(optional(string(), ''), (input) => input.length), 'Length' ); ``` It also gives you more control over the input and output type, and simplifies the mental model by eliminating the confusion between the [`transform`](https://valibot.dev/api/transform/) method and the pipeline transformations of the current API. ```ts // With the new `pipe` function const NumberSchema = pipe( string(), toTrimmed(), decimal(), transform(parseInt) ); // With Valibot's current API const NumberSchema = transform( string([toTrimmed(), decimal()]), parseInt ); ``` Besides that, the `pipe` function would also allow us to easily add a [metadata feature](https://github.com/open-circle/valibot/issues/373) to Valibot. This could be interesting when working with databases to define SQL properties like `PRIMARY KEY`. ```ts // With the new `pipe` function const UserSchema = pipe( object({ id: pipe(string(), uuid(), primaryKey()), name: pipe(string(), maxLength(32), unique()), bio: pipe(string(), description('Text ...')), }), table('users') ); ``` #### The disadvantages The main disadvantage of the `pipe` function is that it requires a bit more code to write for medium sized schemas, and for more complex schemas you may end up nesting multiple pipelines. ```ts // With the new `pipe` function const NumberSchema = pipe( union([pipe(string(), decimal()), pipe(number(), integer())]), transform(Number) ); // With Valibot's current API const NumberSchema = transform( union([string([decimal()]), number([integer()])]), Number ); ``` #### Let's discuss Your opinion matters to me. I encourage everyone to share their thoughts. Even quick feedback like "I like this ... and I don't like that ..." is welcome and will help to shape Valibot's future API design. Please discuss with me on [GitHub](https://github.com/open-circle/valibot/discussions/463) or share your thoughts on [Twitter](https://x.com/FabianHiller/status/1763253086464639035).