# Basic Usage

A schema describes both how to **decode** unknown input into a typed value and
how to **encode** that value back into its serialized form. Once you have a
schema you pick a *runner* — a function that turns the schema into an actual
parser tailored to how you want failures reported (throwing, `Result`, `Effect`,
and so on).

```ts
import { Schema } from "effect"

// The Encoded side stores dates as ISO strings (what arrives over the wire);
// the decoded Type works with real `Date` values.
const Event = Schema.Struct({
  title: Schema.String,
  startsAt: Schema.DateFromString // Encoded: string  ->  Type: Date
})

// `decodeUnknownSync` builds a parser for untrusted input and throws on failure.
const event = Schema.decodeUnknownSync(Event)({
  title: "Launch",
  startsAt: "2026-05-30T10:00:00.000Z"
})

console.log(event.startsAt instanceof Date) // => true

// `encodeUnknownSync` runs the schema in reverse: Type -> Encoded.
const encoded = Schema.encodeUnknownSync(Event)(event)

console.log(encoded)
// => { title: "Launch", startsAt: "2026-05-30T10:00:00.000Z" }
```

## The two type parameters

Every schema is a codec parameterized by its decoded `Type` and its `Encoded`
representation. You can read these off any schema with the `Type` and `Encoded`
type accessors:

```ts
import { Schema } from "effect"

const Event = Schema.Struct({
  title: Schema.String,
  startsAt: Schema.DateFromString
})

// The value your program works with after decoding.
type Event = typeof Event.Type
// { readonly title: string; readonly startsAt: Date }

// The serialized shape that crosses the boundary.
type EventEncoded = typeof Event.Encoded
// { readonly title: string; readonly startsAt: string }
```

For a plain `Schema.String` the two sides coincide (`string` to `string`).
Schemas like `Schema.DateFromString`, `Schema.NumberFromString`, or any custom
[transformation](https://effect.plants.sh/schema/transformations/) make the two sides differ — that gap
is exactly what decoding and encoding bridge.

## Schema, Codec, Decoder, Encoder

A schema actually carries four type parameters. The fully-parameterized base is
`Bottom`, but in everyday code you work through four progressively narrower
*views* — each is just a structural interface that exposes a subset of the
parameters:

- **`Schema<T>`** — tracks only the decoded `Type`. Use it when you accept "any
  schema that decodes to `T`" and do not care about the encoded shape.
- **`Codec<T, E, RD, RE>`** — the full picture: decoded `Type` `T`, `Encoded`
  type `E`, and the services required during decoding (`RD`) and encoding (`RE`).
- **`Decoder<T, RD>`** — a decode-only view (`Encoded` is `unknown`). Runner
  families like `decodeUnknown*` accept any `Decoder`.
- **`Encoder<E, RE>`** — an encode-only view (`Type` is `unknown`). The
  `encode*` families accept any `Encoder`.

```ts
import { Schema } from "effect"

// Accept any schema that decodes to a string, ignoring its encoded shape.
declare function print(schema: Schema.Schema<string>): void
print(Schema.String) // ok
print(Schema.NonEmptyString) // ok

// Accept any codec that decodes to T and encodes to a string.
declare function serialize<T>(codec: Schema.Codec<T, string>): string
serialize(Schema.NumberFromString) // ok — decodes number, encoded as string
```

`Top` is the existential "any schema" type — every type parameter erased to
`unknown`. Use it only as the constraint for generic utilities that must accept
*any* schema.

There is also `Optic<T, Iso>`, a `Schema<T>` whose decode and encode require no
services (both service params are `never`), enabling lens/prism operations
without an Effect runtime. Most primitives implement it automatically.

### revealCodec

Widens a schema to the full `Codec<T, E, RD, RE>` interface so TypeScript infers
all four parameters. When a schema is stored in a variable typed as `Schema<T>`,
the encoded type and services are erased; `revealCodec` recovers them at zero
runtime cost.

```ts
import { Schema } from "effect"

const schema: Schema.Schema<number> = Schema.NumberFromString

const codec = Schema.revealCodec(schema)
type Enc = typeof codec.Encoded // => string
```

### revealBottom

Widens a schema to the fully-parameterized `Bottom` interface, exposing all of
its type parameters (`ast`, `~type.make.in`, `Iso`, mutability/optionality, …)
for advanced introspection and generic schema utilities.

```ts
import { Schema } from "effect"

const bottom = Schema.revealBottom(Schema.String)
type T = typeof bottom.Type // => string
type E = typeof bottom.Encoded // => string
```

## decodeUnknown vs decode

There are two families of runners:

- **`decodeUnknown*`** accept `unknown` input. Use these at real boundaries
  where the input is untrusted (HTTP bodies, `JSON.parse` output, env values).
- **`decode*`** accept input already typed as the schema's `Encoded` type. Use
  these when an upstream layer has already established the encoded shape and you
  only need the decoding transformations to run.

The same split exists for encoding: `encodeUnknown*` accept `unknown`, `encode*`
accept the schema's decoded `Type`.

## Choosing a runner

Each runner produces a different result shape so you can match it to the
boundary you are at. They all share the same signature shape —
`decodeUnknownX(schema)(input)`.

```ts
import { Schema, Result } from "effect"

const Port = Schema.Number.check(Schema.isBetween({ minimum: 1, maximum: 65535 }))

// 1. Sync — throws an Error (with the issue in its `cause`) on failure.
const port = Schema.decodeUnknownSync(Port)(8080) // => 8080

// 2. Result — returns Result<number, SchemaIssue.Issue>, no exceptions.
const result = Schema.decodeUnknownResult(Port)(99999)
if (Result.isFailure(result)) {
  console.error(String(result.failure)) // human-readable issue
}

// 3. Option — returns Option<number>, discarding the failure details.
const maybe = Schema.decodeUnknownOption(Port)(-1) // => Option.none()
```
**Tip:** `decodeUnknownExit` returns an `Exit`, and `decodeUnknownPromise` returns a
`Promise` — useful when integrating with non-Effect code.

## Decoding inside Effect

The runner you reach for most in application code is `decodeUnknownEffect`. It
keeps failures in the typed error channel as a `SchemaError`, and — unlike the
sync variants — it can run schemas whose transformations require services or
perform asynchronous work.

```ts
import { Schema, Effect } from "effect"

const Config = Schema.Struct({
  retries: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)),
  endpoint: Schema.String
})

// `Effect.fn` names the operation for tracing and returns an Effect.
const loadConfig = Effect.fn("loadConfig")(function*(raw: unknown) {
  // Failures surface as SchemaError in the error channel.
  const config = yield* Schema.decodeUnknownEffect(Config)(raw)
  yield* Effect.log(`Loaded config for ${config.endpoint}`)
  return config
})
```

Because the failure lives in the error channel, you handle it with the usual
[error-management](https://effect.plants.sh/error-management/) combinators (`Effect.catch`,
`Effect.catchTag`, …) rather than `try`/`catch`. The `SchemaError` wraps the
structured issue tree in its `.issue` field; see
[error formatting](https://effect.plants.sh/schema/error-formatting/) to render it.
**Note:** The `Effect` and `Exit` runners exposed by the `Schema` module wrap the failure
in a `SchemaError`. The `Result`, `Option`, `Sync`, and `Promise` runners report
the raw `SchemaIssue.Issue` directly (sync/promise carry it in the thrown
`Error`'s `cause` / rejection).

## Reporting every issue

By default decoding stops at the first error. Pass `{ errors: "all" }` as a
second argument to the runner to collect every issue in one pass — ideal for
form validation where you want to show all problems at once.

```ts
import { Schema } from "effect"

const Signup = Schema.Struct({
  name: Schema.String.check(Schema.isNonEmpty()),
  age: Schema.Number.check(Schema.isGreaterThanOrEqualTo(18))
})

// Collect all failures instead of bailing on the first one.
const decode = Schema.decodeUnknownExit(Signup, { errors: "all" })

console.log(String(decode({ name: "", age: 12 })))
```

### Parse options

Every runner accepts an `AST.ParseOptions` object — either when the runner is
created (`decodeUnknownX(schema, options)`) or when it is applied
(`decodeUnknownX(schema)(input, options)`). Application options override creation
options.

| Option | Values | Default | Effect |
| --- | --- | --- | --- |
| `errors` | `"first"` \| `"all"` | `"first"` | Stop at the first error, or collect every error. |
| `onExcessProperty` | `"ignore"` \| `"error"` \| `"preserve"` | `"ignore"` | Strip unknown object keys, fail on them, or keep them. |
| `propertyOrder` | `"none"` \| `"original"` | `"none"` | Let the system choose key order, or preserve input order. |
| `disableChecks` | `boolean` | `false` | Skip validation checks while still applying defaults and transformations. |
| `concurrency` | `number` \| `"unbounded"` | `1` | Max async parse effects to run concurrently. |

```ts
import { Schema } from "effect"

const User = Schema.Struct({ id: Schema.Number, name: Schema.String })

// Fail when the input carries keys the schema does not declare.
const strict = Schema.decodeUnknownResult(User, { onExcessProperty: "error" })

// Preserve the order in which keys appeared in the input.
const ordered = Schema.decodeUnknownSync(User, { propertyOrder: "original" })
```

## Decode / encode entry points

Each direction (decode and encode) comes in two input flavors — `Unknown` (for
`unknown` input) and the typed variant — and each flavor offers six result
shapes. That is 24 runners in total, all with the same call shape
`runner(schema, options?)(input, options?)`.

The result shapes are:

- **`Sync`** — returns the value; throws an `Error` (with the issue in `cause`)
  on failure.
- **`Effect`** — returns an `Effect`; failure is a `SchemaError` in the error
  channel. Required services are preserved, and async transformations work.
- **`Exit`** — returns an `Exit`; failure is a `SchemaError` in the cause. Runs
  synchronously.
- **`Result`** — returns a `Result`; failure is a `SchemaIssue.Issue` as data.
- **`Option`** — returns an `Option`; discards failure details.
- **`Promise`** — returns a `Promise`; rejects with a `SchemaIssue.Issue`.
**Caution:** The `Sync`, `Exit`, `Result`, and `Option` adapters run synchronously. If a
schema's transformation is asynchronous, those adapters surface a defect rather
than a clean failure — reach for the `Effect` (or `Promise`) variant instead.

A schema and a sample value used by the examples below:

```ts
import { Schema } from "effect"

// Encoded: string  <->  Type: number
const Count = Schema.NumberFromString
```

### Decoding `unknown` input

#### decodeUnknownSync

Decodes `unknown` input synchronously, returning the decoded `Type` and throwing
an `Error` (with the issue in `cause`) on failure.

```ts
Schema.decodeUnknownSync(Count)("42") // => 42
// Schema.decodeUnknownSync(Count)("x") // throws Error
```

#### decodeUnknownEffect

Decodes `unknown` input into an `Effect<Type, SchemaError, DecodingServices>`,
preserving service requirements and supporting async transformations.

```ts
Schema.decodeUnknownEffect(Count)("42") // => Effect<number, SchemaError>
```

#### decodeUnknownExit

Decodes `unknown` input synchronously into `Exit<Type, SchemaError>`.

```ts
Schema.decodeUnknownExit(Count)("42") // => Exit.succeed(42)
```

#### decodeUnknownResult

Decodes `unknown` input synchronously into `Result<Type, SchemaIssue.Issue>`,
returning failures as data.

```ts
Schema.decodeUnknownResult(Count)("42") // => Result.succeed(42)
Schema.decodeUnknownResult(Count)("x") // => Result.fail(<issue>)
```

#### decodeUnknownOption

Decodes `unknown` input synchronously into `Option<Type>`, discarding failure
details.

```ts
Schema.decodeUnknownOption(Count)("42") // => Option.some(42)
Schema.decodeUnknownOption(Count)("x") // => Option.none()
```

#### decodeUnknownPromise

Decodes `unknown` input into a `Promise<Type>` that rejects with a
`SchemaIssue.Issue` on failure. Only for service-free schemas.

```ts
Schema.decodeUnknownPromise(Count)("42") // => Promise<number> resolving to 42
```

### Decoding already-typed (`Encoded`) input

These mirror the `Unknown` variants but accept input statically typed as the
schema's `Encoded` type.

#### decodeSync

```ts
Schema.decodeSync(Count)("42") // => 42  (input typed as string)
```

#### decodeEffect

```ts
Schema.decodeEffect(Count)("42") // => Effect<number, SchemaError>
```

#### decodeExit

```ts
Schema.decodeExit(Count)("42") // => Exit.succeed(42)
```

#### decodeResult

```ts
Schema.decodeResult(Count)("42") // => Result.succeed(42)
```

#### decodeOption

```ts
Schema.decodeOption(Count)("42") // => Option.some(42)
```

#### decodePromise

```ts
Schema.decodePromise(Count)("42") // => Promise<number>
```

### Encoding from `unknown` input

Encoding runs the schema in reverse (`Type -> Encoded`). The `Unknown` variants
accept `unknown` input.

#### encodeUnknownSync

```ts
Schema.encodeUnknownSync(Count)(42) // => "42"
```

#### encodeUnknownEffect

```ts
Schema.encodeUnknownEffect(Count)(42) // => Effect<string, SchemaError>
```

#### encodeUnknownExit

```ts
Schema.encodeUnknownExit(Count)(42) // => Exit.succeed("42")
```

#### encodeUnknownResult

```ts
Schema.encodeUnknownResult(Count)(42) // => Result.succeed("42")
```

#### encodeUnknownOption

```ts
Schema.encodeUnknownOption(Count)(42) // => Option.some("42")
```

#### encodeUnknownPromise

```ts
Schema.encodeUnknownPromise(Count)(42) // => Promise<string>
```

### Encoding from already-typed (`Type`) input

These accept input statically typed as the schema's decoded `Type`.

#### encodeSync

```ts
Schema.encodeSync(Count)(42) // => "42"
```

#### encodeEffect

```ts
Schema.encodeEffect(Count)(42) // => Effect<string, SchemaError>
```

#### encodeExit

```ts
Schema.encodeExit(Count)(42) // => Exit.succeed("42")
```

#### encodeResult

```ts
Schema.encodeResult(Count)(42) // => Result.succeed("42")
```

#### encodeOption

```ts
Schema.encodeOption(Count)(42) // => Option.some("42")
```

#### encodePromise

```ts
Schema.encodePromise(Count)(42) // => Promise<string>
```

## Validation without decoding

When you do not need a decoded value — only a yes/no answer — use a guard or
assertion. These run the schema's checks against the **decoded `Type` side**
without producing a value.

### is

Builds a type guard `(input) => input is T` that returns `true`/`false` without
exposing issue details, narrowing the input on success.

```ts
import { Schema } from "effect"

const isString = Schema.is(Schema.String)

isString("hello") // => true
isString(42) // => false

const value: unknown = "hello"
if (isString(value)) {
  console.log(value.toUpperCase()) // => "HELLO"  (value narrowed to string)
}
```

### asserts

A TypeScript `asserts` function: returns normally when the input satisfies the
schema (narrowing it) and throws when it does not.

```ts
import { Schema } from "effect"

const input: unknown = "hello"
Schema.asserts(Schema.String, input)
console.log(input.toUpperCase()) // => "HELLO"  (input narrowed)

// Schema.asserts(Schema.String, 123) // throws
```

### isSchema

Returns `true` if a value is any schema (a `Top`). Useful when writing utilities
that branch on whether an argument is a schema.

```ts
import { Schema } from "effect"

Schema.isSchema(Schema.String) // => true
Schema.isSchema("not a schema") // => false
```

### isSchemaError

Narrows an `unknown` value (e.g. a caught error) to a `SchemaError`, whose
`.message` renders the issue tree and whose `.issue` holds the structured tree.

```ts
import { Schema } from "effect"

try {
  Schema.decodeUnknownSync(Schema.Number)("not a number")
} catch (err) {
  if (Schema.isSchemaError(err)) {
    console.log(err._tag) // => "SchemaError"
  }
}
```

## Constructing values

Every schema exposes a *maker* family that builds a decoded `Type` value
directly — no decoding/parsing of an encoded representation involved. Makers
apply constructor defaults and then run type-side validation.

The input is the schema's `~type.make.in`, which can differ from `Type`: fields
that have a constructor default become optional in `make.in`. Makers are methods
on the schema itself.

```ts
import { Effect, Schema } from "effect"

const Person = Schema.Struct({
  name: Schema.String,
  // gives `age` a constructor default, so it is optional in make input
  age: Schema.Number.pipe(Schema.withConstructorDefault(Effect.succeed(0)))
})
```

### make

Constructs a `Type` value synchronously, throwing an `Error` (issue in `cause`)
when validation fails.

```ts
Person.make({ name: "Alice" })
// => { name: "Alice", age: 0 }   (age default applied)

// Person.make({ name: 123 } as any) // throws Error
```

### makeOption

Constructs a `Type` value synchronously, returning `Option.some` on success or
`Option.none` when validation fails.

```ts
Person.makeOption({ name: "Alice" }) // => Option.some({ name: "Alice", age: 0 })
```

### makeEffect

Constructs a `Type` value inside an `Effect`, keeping validation failures in the
error channel as a `SchemaError`.

```ts
Person.makeEffect({ name: "Alice" })
// => Effect<{ name: string; age: number }, SchemaError>
```
**Note:** Pass `{ disableChecks: true }` (a `MakeOptions`) to skip validation while still
applying defaults and transformations, or `{ parseOptions: { … } }` to forward
`ParseOptions` to the constructor.

## Next steps

- Build up shapes with [primitives](https://effect.plants.sh/schema/primitives/) and
  [structs and records](https://effect.plants.sh/schema/structs-and-records/).
- Constrain values with [filters](https://effect.plants.sh/schema/filters/).
- Pair decode/encode with [transformations](https://effect.plants.sh/schema/transformations/).
- Read and present failures in [error formatting](https://effect.plants.sh/schema/error-formatting/).