Skip to main content
Use eoc and implementEffect(...) when you already have an oRPC contract or want contract-first enforcement.

Define the contract

contract.ts
import { eoc, ORPCTaggedError } from "effect-orpc";
import * as z from "zod";

class UserNotFoundError extends ORPCTaggedError("UserNotFoundError", {
  code: "NOT_FOUND",
  status: 404,
  schema: z.object({ id: z.string() }),
}) {}

export const contract = {
  users: {
    get: eoc
      .errors({ UserNotFoundError })
      .input(z.object({ id: z.string() }))
      .output(z.object({ id: z.string(), name: z.string() })),
  },
};

Implement with Effect

router.ts
import { implementEffect } from "effect-orpc";

const oe = implementEffect(contract, UsersRepo.Default);

export const router = oe.router({
  users: {
    get: oe.users.get.effect(function* ({ input }) {
      const user = yield* UsersRepo.findById(input.id);
      if (!user)
        return yield* new UserNotFoundError({ data: { id: input.id } });
      return user;
    }),
  },
});

What contract leaves expose

Contract leaves preserve the contract-defined input, output, and error surface. They add .effect(...) alongside implementer methods such as .handler(...) and .use(...). They do not expose contract-changing methods such as .input(...) or .output(...) because those belong in the contract definition.

When to use this path

Use contract-first when:
  • multiple teams depend on a stable API contract
  • you generate clients from contracts
  • you want API shape changes isolated from implementation code
  • you want tagged Effect errors declared at contract definition time
Use direct eos builders when you want the shortest path to implementing procedures.

Next step

Read the implementEffect reference.
Last modified on June 15, 2026