.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.
const authedProcedure = effectProcedure.use(function* () {
const user = yield* CurrentUser;
yield* requireActiveUser(user);
});
Wrap middleware
A wrap calls next(...) explicitly and returns its result.
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.
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();
}),
);
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(...).
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
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