Skip to content
Agentmart
Submit a tool

effect-schema

Effect 4 Schema for parsing and modelling data.

Skill by andabove

IndexedMIT
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/andabove-effect-schema

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Effect 4 Schema for parsing and modelling data. Use when you decode untrusted input such as a request body, JSON, a form or a database row, define a domain model or a tagged error with Schema.Class or Schema.TaggedError, write a transformation between an encoded and a decoded form, format validation errors for users, or connect Schema to zod or another Standard Schema library.

Use cases: backend, data, writing

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/effect-schema

What's inside

Skill name
effect-schema
Description
Effect 4 Schema for parsing and modelling data. Use when you decode untrusted input such as a request body, JSON, a form or a database row, define a domain model or a tagged error with Schema.Class or Schema.TaggedError, write a transformation between an encoded and a decoded form, format validation errors for users, or connect Schema to zod or another Standard Schema library.
License
MIT
Instructions
196 lines in SKILL.md · view on GitHub

Files (5)

  • SKILL.md
  • effect-schema-building-blocks.md · references/
  • effect-schema-errors.md · references/
  • effect-schema-interop.md · references/
  • effect-schema-transformations.md · references/

SKILL.md (first lines)

# Effect Schema

Checked against `effect 4.0.0`. When the project has a newer version, search `node_modules/effect/src/Schema.ts` (it is large: search for the name, read the match), `SchemaGetter.ts`, `SchemaTransformation.ts` and `SchemaIssue.ts`, and read `node_modules/effect/AGENTS.md`. The installed source wins over this skill.

Schema needs `"strict": true` in `tsconfig.json`. Turn on `"exactOptionalPropertyTypes": true` as well: without it, the type of a `Schema.optionalKey` field accepts `undefined`, which decoding still rejects.

## The model

A schema is a value that describes data in two forms: `Type`, the value your code uses, and `Encoded`, the value on the wire or on disk. Its full type is `Codec<Type, Encoded, DecodingServices, EncodingServices>`.

- Decoding turns `Encoded` (or `unknown`) into `Type` and validates it. Encoding turns `Type` back into `Encoded`.
- Read the types with `typeof S.Type` and `typeof S.Encoded`.
- A name `XFromY` decodes `Y` into `X`: `Schema.FiniteFromString` has `Type` `number` and `Encoded` `string`.
- Parse every value from outside the process with a schema at the boundary, and use `Schema` for all validation. Hand-written type guards drift from the types they guard.

## Decode untrusted input

```ts
import * as Effect from "effect/Effect"
import * as Schema from "effect/Schema"

export const CreateOrder = Schema.Struct({
  sku: Schema.NonEmptyString,
  quantity: Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 100 })),
  note: Schema.optionalKey(Schema.String)
})
export type CreateOrder = typeof CreateOrder.Type

export class InvalidOrder extends Schema.TaggedError<InvalidOrder>()("InvalidOrder", {
  message: Schema.String
}) {}

const decodeCreateOrder = Schema.decodeUnknownEffect(CreateOrder)

export const parseOrder = (body: unknown): Effect.Effect<CreateOrder, InvalidOrder> =>
  decodeCreateOrder(body, { errors: "all" }).pipe(
    Effect.mapError((error) => new InvalidOrder({ message: error.message }))
  )
```

Pick the decoder by where the code runs:

| Situation | Decoder | On invalid input |
| --- | --- | --- |
| inside Effect code | `Schema.decodeUnknownEffect(S)` | fails with `SchemaError` |
| plain code that branches on the outcome | `Schema.decodeUnknownResult(S)` | returns a `Result` failure holding a `SchemaError` |
| plain code where invalid input is a bug | `Schema.decodeUnknownSync(S)` | throws `SchemaError` |
| plain code with async transformations | `Schema.decodeUnknownPromise(S)` | rejects with `SchemaError` |
| a yes or no answer | `Schema.is(S)` | returns `false` |

- `decodeUnknown*` accepts `unknown`. `decode*` accepts a value already typed as `Encoded`. The `encode*` family mirrors both.
- Build each decoder once, at module scope. Options go to the factory or to each call: `Schema.decodeUnknownEffect(S, { errors: "all" })` or `decode(input, { errors: "all" })`.
- Unknown keys are dropped without an error. Pass `{ onExcessProperty: "error" }` to reject them. The option takes only `"ignore"` (the default) and `"error"`.
- Only the first issue is reported. Pass `{ errors: "all" }` for forms and API error bodies.
- The Sync, Result and Option decoders cannot run an asynchronous transformation. They throw a plain `Error`, not a `SchemaError`, and `decodeUnknownExit` returns a defect. Decode such schemas with the Effect or Promise decoder.
- Parse JSON text with `Schema.fromJsonString(S)`. Invalid JSON fails with `Expected a valid JSON string`.
- Catch the error with `Effect.catchTag("SchemaError", ...)`. Map it to a domain error at the boundary, as `parseOrder` does.
- `Schema.Finite` rejects `NaN` and `Infinity`; `Schema.Number` accepts them. Prefer `Finite` or `Int` for input.
- `Schema.optional(S)` accepts an absent key or `undefined`. `Schema.optionalKey(S)` accepts an absent key but rejects `undefined`.

Primitives, filters, unions, records and recursion: [references/effect-schema-building-blocks.md](references/effect-schema-building-blocks.md).

## Encode for the wire

Before you send or store a decoded value, encode it with the same schema: `Schema.encodeEffect(S)(value)` inside Effect, `Schema.encodeSync(S)(value)` outside. Encoding produces the `Encoded` form: a `Date` from `DateFromString` becomes an ISO string, a class instance becomes a plain object, an `Option` from `OptionFromNullOr` becomes `null` or the value. `JSON.stringify` on the decoded value skips this and emits the wrong shape.

## Schema.Class: a domain model

```ts
import * as Equal from "effect/Equal"
import * as Schema from "effect/Schema"

export class Customer extends Schema.Class<Customer>("myapp/Customer")({
  id: Schema.String,
  email: Schema.String.check(Schema.isPattern(/^[^@]+@[^@]+$/)),
  joinedAt: Schema.DateFromString
}) {
  get domain(): string {
    return this.email.split("@")[1] ?? ""
  }
}

const customer = Schema.decodeUnknownSync(Customer)({ id: "c1", email: "ada@example.com", joinedAt: "2026-01-01T00:00:00.000Z" })
customer.domain // "example.com"
Schema.encodeSync(Customer)(customer) // { id: "c1", email: "ada@example.com", joinedAt: "2026-01-01T00:00:00.000Z" }
Equal.equals(customer, new Customer({ ...customer })) // true
```

- Decoding returns an instance of the class. Encoding returns a plain object in the `Encoded` form.
- The string identifier names the schema in error messages and JSON Schema, and recognizes instances across hot module reloads. Make it unique.
- `new Customer({ ... })` and `Customer.make({ ... })` validate and throw an `Error` whose message is `Schema validation failed`; the issue is on `error.cause`. For input that can be invalid, decode it, or call `Customer.makeOption`, which returns an `Option`.
- The `Type` side is the class: `Schema.is(Customer)` returns `false` for a plain object with the same fields.
- Instances compare by value with `Equal.equals`.
- `Customer.extend<Vip>("myapp/Vip")({ tier: Schema.Int })` adds fields and keeps the methods. `Schema.TaggedClass<Self>()("Tag", fields)` adds a `_tag` field.
- Use `Schema.Class` when the value needs methods, `instanceof` or a nominal type. Use `Schema.Struct` for plain data such as request bodies.

## Schema.TaggedError: a typed, serializable error

```ts
import * as Effect from "effect/Effect"
import * as Schema from "effect/Schema"

export class PaymentDeclined extends Schema.TaggedError<PaymentDeclined>()("PaymentDeclined", {
  orderId: Schema.String,
  reason: Schema.Literals(["insufficient_funds", "card_expired"])
}) {}

export const charge = Effect.fn("charge")(function* (orderId: string): Effect.fn.Return<string, PaymentDeclined> {
  if (orderId.startsWith("x")) return yield* new PaymentDeclined({ orderId, reason: "card_expired" })
  return `receipt-${orderId}`
})

export const handled = charge("x1").pipe(
  Effect.catchTag("PaymentDeclined", (e) => Effect.succeed(`declined: ${e.reason}`))
)
```

- Write `return yield* new PaymentDeclined({ ... })` inside the generator. The `return` tells TypeScript the branch ends.
- An instance is an `Error` with a stack. Its `name` is the tag. Its `message` is empty unless you declare a `message` field.

From andabove/skills, MIT.

More from andabove/skills

Signals

Stars
0
Forks
0
Open issues
0
Last commit (this folder)
Oct 3, 2026 (26 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from andabove

Cite this page

effect-schema skill. Agent Mart. https://agentmart.dev/skills/andabove-effect-schema (last updated 2026-10-24)