Documentation Index
Fetch the complete documentation index at: https://effect-ts-effect-smol.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
The Schema module provides powerful runtime validation and type-safe schema definitions for TypeScript applications. It enables you to define data structures once and use them for validation, parsing, serialization, and type inference.
Overview
A Schema defines both:
- The encoded representation (e.g., what comes from an API or user input)
- The type representation (e.g., your application’s domain model)
Schemas automatically provide:
- Runtime validation and parsing
- Type inference for TypeScript
- JSON Schema generation
- Serialization and deserialization
- Custom error messages
Creating Schemas
Primitive Types
import { Schema } from "effect"
// Basic primitives
const StringSchema = Schema.String
const NumberSchema = Schema.Number
const BooleanSchema = Schema.Boolean
const IntSchema = Schema.Int
// Date and Duration
const DateSchema = Schema.Date
const DurationSchema = Schema.Duration
Structs and Objects
import { Schema } from "effect"
const PersonSchema = Schema.Struct({
name: Schema.String,
age: Schema.Int,
email: Schema.String
})
// Infer the TypeScript type
type Person = Schema.Schema.Type<typeof PersonSchema>
// { name: string; age: number; email: string }
Optional and Nullable Fields
import { Schema } from "effect"
const UserSchema = Schema.Struct({
id: Schema.Number,
name: Schema.String,
// Optional field (may be undefined)
nickname: Schema.optional(Schema.String),
// Nullable field (may be null)
bio: Schema.NullOr(Schema.String),
// Optional with default value
role: Schema.optional(Schema.String).pipe(
Schema.withDefault(() => "user")
)
})
Arrays and Collections
import { Schema } from "effect"
const NumberArraySchema = Schema.Array(Schema.Number)
const TagsSchema = Schema.Array(Schema.String).pipe(
Schema.minItems(1),
Schema.maxItems(10)
)
// Non-empty arrays
const NonEmptyArraySchema = Schema.NonEmptyArray(Schema.String)
Parsing and Validation
Using makeUnsafe
import { Schema } from "effect"
const PersonSchema = Schema.Struct({
name: Schema.String,
age: Schema.Int
})
// Parse and validate (throws on error)
try {
const person = PersonSchema.makeUnsafe({
name: "Alice",
age: 30
})
console.log(person)
} catch (error) {
console.error("Validation failed:", error)
}
Using makeOption
import { Schema } from "effect"
import { Option } from "effect"
const result = PersonSchema.makeOption({ name: "Bob", age: 25 })
if (Option.isSome(result)) {
console.log("Valid:", result.value)
} else {
console.log("Invalid input")
}
With Effect Integration
import { Effect, Schema } from "effect"
const parseUser = (input: unknown) =>
Effect.gen(function*() {
const UserSchema = Schema.Struct({
name: Schema.String,
age: Schema.Int
})
// Decode returns an Effect
const user = yield* Schema.decode(UserSchema)(input)
return user
})
Custom Validations
Refinements
import { Schema } from "effect"
const PositiveInt = Schema.Int.pipe(
Schema.filter((n) => n > 0, {
message: () => "Must be positive"
})
)
const EmailSchema = Schema.String.pipe(
Schema.filter(
(s) => s.includes("@"),
{ message: () => "Invalid email format" }
)
)
import { Schema } from "effect"
// Transform from string to Date
const DateFromString = Schema.transform(
Schema.String,
Schema.Date,
{
decode: (s) => new Date(s),
encode: (d) => d.toISOString()
}
)
// Transform with validation
const TrimmedString = Schema.transform(
Schema.String,
Schema.String,
{
decode: (s) => s.trim(),
encode: (s) => s
}
)
Tagged Errors
import { Schema } from "effect"
// Define custom errors with Schema
export class ValidationError extends Schema.TaggedErrorClass<ValidationError>()("ValidationError", {
message: Schema.String,
field: Schema.optional(Schema.String)
}) {}
// Use in Effects
const validateAge = (age: number) =>
age >= 18
? Effect.succeed(age)
: Effect.fail(new ValidationError({
message: "Must be 18 or older",
field: "age"
}))
Union Types
import { Schema } from "effect"
// Simple union
const StringOrNumber = Schema.Union(
Schema.String,
Schema.Number
)
// Discriminated union
type Result = Schema.Schema.Type<typeof ResultSchema>
const ResultSchema = Schema.Union(
Schema.Struct({
_tag: Schema.Literal("success"),
value: Schema.String
}),
Schema.Struct({
_tag: Schema.Literal("error"),
error: Schema.String
})
)
Records and Dictionaries
import { Schema } from "effect"
// Record with string keys and number values
const ScoresSchema = Schema.Record({
key: Schema.String,
value: Schema.Number
})
type Scores = Schema.Schema.Type<typeof ScoresSchema>
// Record<string, number>
Annotations and Documentation
import { Schema } from "effect"
const PersonSchema = Schema.Struct({
name: Schema.String.pipe(
Schema.annotate({
title: "Full Name",
description: "The person's full legal name"
})
),
age: Schema.Int.pipe(
Schema.annotate({
title: "Age",
description: "Age in years",
minimum: 0,
maximum: 150
})
)
})
Type Inference
import { Schema } from "effect"
const UserSchema = Schema.Struct({
id: Schema.Number,
name: Schema.String,
role: Schema.Literal("admin", "user")
})
// Extract the Type (decoded form)
type User = Schema.Schema.Type<typeof UserSchema>
// { id: number; name: string; role: "admin" | "user" }
// Extract the Encoded form
type UserEncoded = Schema.Schema.Encoded<typeof UserSchema>
// { id: number; name: string; role: "admin" | "user" }
Best Practices
- Define schemas at the boundaries: Validate external data at API boundaries
- Use Schema.TaggedErrorClass: Define custom errors with schemas for better type safety
- Compose schemas: Build complex schemas from simpler ones
- Add annotations: Document your schemas for better JSON Schema generation and error messages
- Prefer makeOption over makeUnsafe: Use
makeOption when validation failure is expected
Next Steps
- Learn about Effect for error handling
- Explore Config for configuration management
- See how Stream uses schemas for validation