Skip to content
tRPC

Context

What every call gets as ctx, where it comes from, and how middleware adds to it.

On this page

ctx is what a call knows about where it came from: the user, a database, the platform. Its type is the root ctx you give initTrpc:

import { function initTrpc<TConfig extends RootConfig = {}>(opts?: RootOptions<TConfig["meta"] extends object ? TConfig["meta"] : {}>): TrpcRoot<TConfig["ctx"] extends object ? TConfig["ctx"] : {}, TConfig["meta"] extends object ? TConfig["meta"] : {}>initTrpc } from '@trpc12/trpc/server';

interface User {
  User.id: stringid: string;
}

const 
const t: TrpcRoot<{
    user: User | null;
}, {}, RootDef<{
    user: User | null;
}, {}>>
t
=
initTrpc<{
    ctx: {
        user: User | null;
    };
}>(opts?: RootOptions<{}>): TrpcRoot<{
    user: User | null;
}, {}, RootDef<{
    user: User | null;
}, {}>>
initTrpc
<{
ctx: {
    user: User | null;
}
ctx
: { user: User | nulluser: User | null } }>();

From the request

The handler's createContext makes it for each call, from the request (and, on runtimes like Workers, the platform argument):

import { function createFetchHandler<TRouter extends AnyRouter, TPlatform = undefined>(opts: FetchHandlerOptions<TRouter, TPlatform>): FetchHandler<TPlatform>createFetchHandler } from '@trpc12/trpc/server';

const const handler: FetchHandler<undefined>handler = 
createFetchHandler<{
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}, undefined>(opts: FetchHandlerOptions<...>): FetchHandler<...>
createFetchHandler
({
BaseHandlerOptions<{ post: { byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>, "query", { output: Post; errors: TrpcError<...>; services: never; unmapped: never; }>; list: BuildProcedure<...>; create: BuildProcedure<...>; }; }>.router: {
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}
router
:
const appRouter: {
    post: {
        byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
            id: ZodString;
        }, $strip>>, "query", {
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }>;
            services: never;
            unmapped: never;
        }>;
        list: BuildProcedure<RootDef<Context, {}>, "query", {
            output: Post[];
            errors: never;
            services: never;
            unmapped: never;
        }>;
        create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
            ctx: Context;
            ctxAdded: {
                ...;
            };
            inputOut: Unset;
            errors: TrpcError<...>;
            shorts: never;
        }>, ZodObject<...>>, "mutation", {
            ...;
        }>;
    };
}
appRouter
,
createContext: (opts: CreateContextOpts<undefined>) => MaybePromise<{
    user: User | null;
}>
createContext
: (opts: CreateContextOpts<undefined>opts) => ({
user: User | nulluser: opts: CreateContextOpts<undefined>opts.CreateContextOpts<undefined>.request: HttpServerRequestrequest.HttpIncomingMessage<HttpServerError>.headers: Headersheaders['x-user'] ? { User.id: stringid: opts: CreateContextOpts<undefined>opts.CreateContextOpts<undefined>.request: HttpServerRequestrequest.HttpIncomingMessage<HttpServerError>.headers: Headersheaders['x-user'], User.name: stringname: 'Ada' } : null, }), });

request is Effect's HttpServerRequest: headers is a record of lowercase names, and on fetch runtimes request.source is the web Request. On Node the request is never converted to a web Request (Node). The platform argument is described in Fetch runtimes.

WebSocket calls get their upgrade request, and MessagePort calls the request given to handler.messagePort().

From middleware

Middleware adds to the ctx with next({ ctx }), and the procedures after it see the narrowed type (Middleware). A middleware published by a package states the ctx it needs; on a procedure that lacks a key, the key becomes a requirement that createContext must supply.

In-process calls

In-process calls have no HTTP request: you pass the ctx (or a function that makes it) yourself, and the handler's createContext doesn't run. To share request-reading code with the handler, keep it in a function of the request and build one: ctx: () => contextFor(HttpServerRequest.fromWeb(new Request(url, { headers }))). See Server-side calls.

Services instead of ctx

Things built once for the whole server (a connection pool, a mailer) can be Effect services in the handler's layer instead of ctx keys: the layer is built once, and resolvers that return an Effect ask for them (Effect).