Skip to main content
effect-orpc keeps oRPC’s input and output schema model. Use .input(...) and .output(...) before .effect(...).
schemas.ts
import * as z from "zod";

const GetUserInput = z.object({
  id: z.string().min(1),
});

const User = z.object({
  id: z.string(),
  name: z.string(),
});

export const getUser = effectProcedure
  .input(GetUserInput)
  .output(User)
  .effect(function* ({ input }) {
    return yield* UsersRepo.findById(input.id);
  });

Inferred input

The handler sees the parsed schema output as input.
parsed-input.ts
const SearchInput = z.object({
  query: z.string().trim(),
  limit: z.number().int().positive().default(20),
});

const search = effectProcedure.input(SearchInput).effect(function* ({ input }) {
  // input.limit is a number, including the default when omitted.
  return yield* UsersRepo.search(input.query, input.limit);
});

Output validation

Use .output(...) for public response contracts.
output.ts
const PublicUser = z.object({
  id: z.string(),
  name: z.string(),
});

const me = effectProcedure.output(PublicUser).effect(function* () {
  const user = yield* CurrentUser;
  return user;
});
Output schemas validate what leaves the procedure. Keep internal fields out of public output schemas unless they are intentionally part of the API.

Next step

Provide services with Service injection.
Last modified on June 15, 2026