better-effect
Integrations and adapters

Schema and validation

Decode, encode and derive typed values without coupling the core to a provider.

better-effect-schema is the provider-neutral schema package for applications that need runtime decoding alongside better-result and better-effect. The recommended application path is a real provider schema imported through one of the three preconfigured provider subpaths. The root entry point uses Standard Schema and does not import Zod, Valibot or ArkType.

Install and choose a provider

bun add better-effect-schema better-effect better-result
# Choose one provider and install its peer:
bun add zod # or: bun add valibot / arktype
ImportPurposeDependency
better-effect-schemaStandard Schema classes, operations and failuresStandard Schema, better-result, better-effect types
better-effect-schema/zodPreconfigured Zod 4 Schema; choose for its ecosystem, codecs and class derivationsZod
better-effect-schema/valibotPreconfigured Valibot Schema; choose for modular APIs and small bundlesValibot
better-effect-schema/arktypePreconfigured ArkType Schema; choose for concise definitions and inferred runtime typesArkType

The provider packages are optional peers. Importing the root does not select a provider or add framework lifecycle.

Decode and encode

Schema.Class describes a decoded class backed by the provider schema. Decode and encode operations return better-result values; expected validation failures are not thrown. This is the shape to use for HTTP, MQ and other untrusted boundaries where runtime validation and a stable application type matter.

import * as z from 'zod'
import { Result } from 'better-result'
import { Schema as CoreSchema } from 'better-effect-schema'
import { Schema } from 'better-effect-schema/zod'

const UserSchema = z.object({
  id: z.string().min(1),
  email: z.email(),
  displayName: z.string().min(1)
})

class User extends Schema.Class<User>('example/User')(UserSchema) {}

const decoded = Schema.decodeUnknown(User, {
  id: 'user-1',
  email: 'ada@example.com',
  displayName: 'Ada Lovelace'
})
if (Result.isError(decoded)) throw decoded.error
const encoded = CoreSchema.encode(User, decoded.value)
if (Result.isError(encoded)) throw encoded.error

Use Schema.decode when the encoded input type is known, Schema.decodeUnknown for data from JSON, HTTP, queues or other untrusted sources, and Schema.make for decoded constructor props. Use the corresponding Async operation for an asynchronous validator or encoder. Transformed values must provide an explicit encoder; the package never invents a JSON representation.

Provider adapters

Each provider subpath exports a ready-to-use Schema facade. Import the one that matches your native schema library; aliases let multiple providers coexist in one module:

import * as z from 'zod'
import { Schema as ZodSchema } from 'better-effect-schema/zod'
import { Schema as ValibotSchema } from 'better-effect-schema/valibot'
import { Schema as ArkTypeSchema } from 'better-effect-schema/arktype'

class Person extends ZodSchema.Class<Person>('example/Person')({
  id: z.int().positive(),
  name: z.string()
}) {}

HTTP, MQ codecs and other integrations accept Standard Schema values directly, so a native schema can be passed to Codec.standardSchema without teaching the core package about a provider. Use the root package for provider-neutral Standard Schema code; use a provider subpath when you want that provider's native class factories, normalized Result failures or derivations. The advanced Schema.with API is for authoring custom adapters or intentionally configured capability sets, not for built-in providers.

Raw Standard Schema: an adapter escape hatch

Most applications should not hand-write a StandardSchemaV1 object. Prefer a provider-backed schema and adapter so validation, issues and inferred types stay consistent. Implement raw Standard Schema only when you are authoring a schema provider/adapter or integrating a library that already exposes Standard Schema and cannot be represented by a supported provider. It is an interoperability escape hatch, not the default application example.

Tagged classes and errors

Schema.TaggedClass and Schema.TaggedError preserve literal tags and use the better-result failure protocol. They can be yielded from Result.gen and composed in an Effect.gen Program without introducing another error model.

The package is intentionally pure: it has no Runtime, Scope, framework or database lifecycle. See the package catalog for how it fits with HTTP and the other integrations.

On this page