Skip to main content
.use(...) accepts native oRPC middleware, generator-based Effect middleware, and Effect-returning middleware. Use Effect middleware when the middleware needs Effect services, Effect errors, or Effect control flow.

Gate middleware

A gate runs checks and lets the pipeline continue automatically.
gate.ts
const authedProcedure = effectProcedure.use(function* () {
  const user = yield* CurrentUser;
  yield* requireActiveUser(user);
});

Wrap middleware

A wrap calls next(...) explicitly and returns its result.
wrap.ts
const authedProcedure = effectProcedure.use(function* ({ next }) {
  const user = yield* CurrentUser;
  yield* requireActiveUser(user);

  return yield* next({
    context: { userId: user.id },
  });
});

Effect-returning middleware

Use Effect.fn(...) or a function returning Effect.gen(...) when you already have an Effect-returning callback shape. Guard-only Effect middleware can return void; the pipeline continues automatically.
effect-returning.ts
const authedProcedure = effectProcedure.use(
  Effect.fn("middleware.auth")(function* ({ next }) {
    const user = yield* CurrentUser;
    return yield* next({ context: { userId: user.id } });
  }),
);

const auditedProcedure = effectProcedure.use(({ next }) =>
  Effect.gen(function* () {
    yield* Effect.logDebug("before handler");
    return yield* next();
  }),
);

Transform output

output-transform.ts
const wrapped = effectProcedure.use(function* ({ next }, _input, output) {
  const result = yield* next();
  return yield* output({
    ...result.output,
    wrapped: true,
  });
});

Reusable Effect middleware

Use .middleware(...) to define Effect middleware once and pass it to .use(...).
reusable.ts
const requireAdmin = effectProcedure.middleware(function* ({ next }) {
  const user = yield* CurrentUser;
  yield* requireAdminUser(user);
  return yield* next();
});

const adminProcedure = effectProcedure.use(requireAdmin);

Native oRPC middleware still works

native.ts
const withRequestLabel = effectProcedure.use(({ context, next }) =>
  next({
    context: {
      requestLabel: `${context.role}:${context.requestId}`,
    },
  }),
);

const checked = effectProcedure.use(({ context }) => {
  if (!context.requestId) throw new Error("missing request id");
  // Returning void is a native guard: the pipeline continues automatically.
});
Native middleware can split contiguous Effect runtime boundaries. If you need request-local Effect context continuity across those boundaries in Node, see Node context bridge.

Next step

Use contracts with Contract-first APIs.
Last modified on June 15, 2026