Expert guide for writing Effect-TS code, including project setup, core principles, data modeling with Schema, error handling, and the Context.Tag service pattern. Use when writing, refactoring, or analyzing TypeScript code using the Effect library.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Expert guide for writing Effect-TS code, including project setup, core principles, data modeling with Schema, error handling, and the Context.Tag service pattern. Use when writing, refactoring, or analyzing TypeScript code using the Effect library.
allowed-tools
Read, Grep, Glob
Effect-TS Developer Guide
Guidelines, patterns, and best practices for Effect-TS in this project.
Reference Documents
Read the relevant reference before writing code. references/core-patterns.md is the master index.
Reference
Topics
references/foundations.md
Setup, imports, TypeScript config
references/construction-and-style.md
Effect.gen, pipe, Effect.fn, Effect.fnUntraced
references/schema-errors-config.md
Schema modeling, errors, config, retry
references/pattern-matching.md
Match.type, Match.value, Match.tag, Match.exhaustive — mandatory for tagged unions
Always use Match — Match.exhaustive catches missing cases at compile time. See references/pattern-matching.md.
// Match.type — reusable matcher functionconst handle = Match.type<Status>().pipe(
Match.tag("Pending", (s) =>`Pending since ${s.requestedAt}`),
Match.tag("Approved", (s) =>`Approved by ${s.approvedBy}`),
Match.exhaustive// Compile error if any variant is missing
);
// Match.valueTags — shorthand for immediate matchingMatch.valueTags(status, {
Pending: (s) =>`Pending since ${s.requestedAt}`,
Approved: (s) =>`Approved by ${s.approvedBy}`,
});
Service Pattern (Context.Tag)
See references/class-patterns.md for full pattern with factory methods and layers.
// NEVER: try-catch in Effect.gen — use Effect.exit insteadEffect.gen(function* () {
try { yield* someEffect } catch (e) { } // WRONG — will never catch
});
// NEVER: Type assertionsconst value = something asany; // FORBIDDENconst value = something asnever; // FORBIDDEN// NEVER: Missing return on terminal yieldEffect.gen(function* () {
if (bad) { yield* Effect.fail("err") } // Missing return!
});
// NEVER: switch/if-else on _tag — use Match insteadswitch (status._tag) { /* no exhaustiveness checking! */ }
// NEVER: Effect.runSync inside EffectsEffect.gen(function* () { Effect.runSync(sideEffect) }); // Loses error tracking// NEVER: Native JS where Effect data types existconstx: string | null = null; // Use Option<string>const delay = 5000; // Use Duration.seconds(5)
now = ();
price = + ;
secret = ;
it.(, .(* () {
(result).(value)
}));
.(.({ url }))
Validation Checklist
Imports use import * as Module from "effect/Module"
Effect.gen for complex logic, pipe for linear, Effect.fn for public API
Match for all _tag branching with Match.exhaustive
Branded types for domain primitives (IDs, Emails)
Errors: Data.TaggedError (discrimination) or Schema.TaggedError (serializable)
No any/unknown in error channels, no type assertions
No try-catch in Effect.gen — use Effect.exit
return yield* for terminal effects (Effect.fail, Effect.interrupt)
Services: Context.Tag with static factory methods and Effect.fn tracing
Layers: Layer.merge/Layer.provide, parameterized layers in constants
Resources: Effect.acquireRelease or Effect.scoped
Option for nullable values, Either for sync success/failure
Duration for time values, DateTime for dates (not Date)
BigDecimal for financial/precise math, Redacted for secrets
Data.struct/Data.Class for structural equality, for sets
Reference Implementation
See /packages/looper/src/data/api-client/api-client.ts for Context.Tag service pattern.
Effect Solutions CLI
pnpm exec effect-solutions list # List all topics
pnpm exec effect-solutions show <slug...> # Read topics
pnpm exec effect-solutions search <term> # Search by keyword
const
new
Date
// Use DateTime.now or DateTime.unsafeNow()
const
0.1
0.2
// Use BigDecimal for precision
const
"sk-1234"
// Use Redacted.make("sk-1234")
// NEVER: expect with it.effect
effect
"test"
() =>
Effect
gen
function
expect
toBe
// WRONG — use assert.strictEqual
// NEVER: Inline layers (breaks memoization)
Layer
provide
Postgres
layer
// Store in constant instead
HashSet
Clock.currentTimeMillis instead of Date.now()
Tests: assert from @effect/vitest (not expect) with it.effect