Skip to content
tRPC

Partners

Authorization

On this page

The createContext function is called for each incoming request, so here you can add contextual information about the calling user from the request object.

Create context from request headers

server/context.ts
import type { 
type CreateHTTPContextOptions = {
    req: IncomingMessage;
    res: ServerResponse<IncomingMessage>;
    info: TRPCRequestInfo;
}
CreateHTTPContextOptions
} from '@trpc/server/adapters/standalone';
import {
function decodeAndVerifyJwtToken(token: string): Promise<{
    name: string;
}>
decodeAndVerifyJwtToken
} from './utils';
export async function
function createContext({ req, res }: CreateHTTPContextOptions): Promise<{
    user: {
        name: string;
    } | null;
}>
createContext
({ req: IncomingMessagereq, res: ServerResponse<IncomingMessage>res }:
type CreateHTTPContextOptions = {
    req: IncomingMessage;
    res: ServerResponse<IncomingMessage>;
    info: TRPCRequestInfo;
}
CreateHTTPContextOptions
) {
// Create your context based on the request object // Will be available as `ctx` in all your resolvers // This is just an example of something you might want to do in your ctx fn async function
function (local function) getUserFromHeader(): Promise<{
    name: string;
} | null>
getUserFromHeader
() {
if (req: IncomingMessagereq.IncomingMessage.headers: IncomingHttpHeadersheaders.IncomingHttpHeaders.authorization?: string | undefinedauthorization) { const
const user: {
    name: string;
}
user
= await
function decodeAndVerifyJwtToken(token: string): Promise<{
    name: string;
}>
decodeAndVerifyJwtToken
(
req: IncomingMessagereq.IncomingMessage.headers: IncomingHttpHeadersheaders.IncomingHttpHeaders.authorization?: stringauthorization.String.split(separator: string | RegExp, limit?: number): string[] (+1 overload)split(' ')[1], ); return
const user: {
    name: string;
}
user
;
} return null; } const
const user: {
    name: string;
} | null
user
= await
function (local function) getUserFromHeader(): Promise<{
    name: string;
} | null>
getUserFromHeader
();
return {
user: {
    name: string;
} | null
user
,
}; } export type
type Context = {
    user: {
        name: string;
    } | null;
}
Context
=
type Awaited<T> = T extends null | undefined ? T : T extends object & {
    then(onfulfilled: infer F, ...args: infer _): any;
} ? F extends (value: infer V, ...args: infer _) => any ? Awaited<V> : never : T
Awaited
<type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : anyReturnType<typeof
function createContext({ req, res }: CreateHTTPContextOptions): Promise<{
    user: {
        name: string;
    } | null;
}>
createContext
>>;

Option 1: Authorize using resolver

server/routers/_app.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC, class TRPCErrorTRPCError } from '@trpc/server';
import { import zz } from 'zod';

type 
type Context = {
    user: {
        name: string;
    } | null;
}
Context
= {
user: {
    name: string;
} | null
user
: { name: stringname: string } | null };
export const
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.context<Context>(): TRPCBuilder<Context, object>context<
type Context = {
    user: {
        name: string;
    } | null;
}
Context
>().
TRPCBuilder<Context, object>.create<RuntimeConfigOptions<Context, object>>(opts?: RuntimeConfigOptions<Context, object> | undefined): TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
const
const appRouter: BuiltRouter<{
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: string | null | undefined;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    secret: QueryProcedure<{
        input: void;
        output: {
            secret: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
    hello: QueryProcedure<{
        input: string | null | undefined;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    secret: QueryProcedure<{
        input: void;
        output: {
            secret: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    hello: QueryProcedure<{
        input: string | null | undefined;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    secret: QueryProcedure<{
        input: void;
        output: {
            secret: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}) => BuiltRouter<...>
router
({
// open for anyone
hello: QueryProcedure<{
    input: string | null | undefined;
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
hello
:
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure
.ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodOptional<z.ZodNullable<z.ZodString>>>(schema: z.ZodOptional<z.ZodNullable<z.ZodString>>): ProcedureBuilder<Context, object, object, string | null | undefined, string | null | undefined, UnsetMarker, UnsetMarker, false, DefaultErrorShape>input(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string().ZodType<any, any, $ZodStringInternals<string>>.nullish(): z.ZodOptional<z.ZodNullable<z.ZodString>>nullish()) .
ProcedureBuilder<Context, object, object, string | null | undefined, string | null | undefined, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<Context, object, object, string | null | undefined, UnsetMarker, string>): QueryProcedure<{
    input: string | null | undefined;
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
((opts: ProcedureResolverOptions<Context, object, object, string | null | undefined>opts) => `hello ${opts: ProcedureResolverOptions<Context, object, object, string | null | undefined>opts.ProcedureResolverOptions<Context, object, object, string | null | undefined>.input: string | null | undefinedinput ?? opts: ProcedureResolverOptions<Context, object, object, string | null | undefined>opts.
ProcedureResolverOptions<Context, object, object, string | null | undefined>.ctx: {
    user: {
        name: string;
    } | null;
}
ctx
.
user: {
    name: string;
} | null
user
?.name: string | undefinedname ?? 'world'}`),
// checked in resolver
secret: QueryProcedure<{
    input: void;
    output: {
        secret: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
}>
secret
:
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure.
ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
    secret: string;
}>(resolver: ProcedureResolver<Context, object, object, UnsetMarker, UnsetMarker, {
    secret: string;
}>): QueryProcedure<{
    input: void;
    output: {
        secret: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
((opts: ProcedureResolverOptions<Context, object, object, UnsetMarker>opts) => {
if (!opts: ProcedureResolverOptions<Context, object, object, UnsetMarker>opts.
ProcedureResolverOptions<Context, object, object, UnsetMarker>.ctx: {
    user: {
        name: string;
    } | null;
}
ctx
.
user: {
    name: string;
} | null
user
) {
throw new
new TRPCError(opts: {
    message?: string;
    code: TRPC_ERROR_CODE_KEY;
    cause?: unknown;
}): TRPCError
TRPCError
({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'UNAUTHORIZED' });
} return { secret: stringsecret: 'sauce', }; }), });

Option 2: Authorize using middleware

server/routers/_app.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC, class TRPCErrorTRPCError } from '@trpc/server';
import { import zz } from 'zod';

type 
type Context = {
    user: {
        name: string;
    } | null;
}
Context
= {
user: {
    name: string;
} | null
user
: { name: stringname: string } | null };
export const
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.context<Context>(): TRPCBuilder<Context, object>context<
type Context = {
    user: {
        name: string;
    } | null;
}
Context
>().
TRPCBuilder<Context, object>.create<RuntimeConfigOptions<Context, object>>(opts?: RuntimeConfigOptions<Context, object> | undefined): TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
// you can reuse this for any procedure export const
const protectedProcedure: ProcedureBuilder<Context, object, {
    user: {
        name: string;
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
protectedProcedure
=
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure.
ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
    user: {
        name: string;
    };
}>(fn: MiddlewareBuilder<{
    user: {
        name: string;
    } | null;
}, object, {
    user: {
        name: string;
    };
}, UnsetMarker> | MiddlewareFunction<Context, object, object, {
    user: {
        name: string;
    };
}, UnsetMarker>): ProcedureBuilder<Context, object, {
    user: {
        name: string;
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(
async function
function (local function) isAuthed(opts: {
    ctx: {
        user: {
            name: string;
        } | null;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}): Promise<...>
isAuthed
(
opts: {
    ctx: {
        user: {
            name: string;
        } | null;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) {
const {
const ctx: {
    user: {
        name: string;
    } | null;
}
ctx
} =
opts: {
    ctx: {
        user: {
            name: string;
        } | null;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
;
// `ctx.user` is nullable if (!
const ctx: {
    user: {
        name: string;
    } | null;
}
ctx
.user) {
user: {
    name: string;
} | null
throw new
new TRPCError(opts: {
    message?: string;
    code: TRPC_ERROR_CODE_KEY;
    cause?: unknown;
}): TRPCError
TRPCError
({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'UNAUTHORIZED' });
} return
opts: {
    ctx: {
        user: {
            name: string;
        } | null;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: <{
    user: {
        name: string;
    };
}>(opts: {
    ctx?: {
        user: {
            name: string;
        };
    } | undefined;
    input?: unknown;
}) => Promise<MiddlewareResult<{
    user: {
        name: string;
    };
}>> (+2 overloads)
next
({
ctx?: {
    user: {
        name: string;
    };
} | undefined
ctx
: {
// ✅ user value is known to be non-null now user:
const ctx: {
    user: {
        name: string;
    } | null;
}
ctx
.
user: {
    name: string;
}
user
,
user: {
    name: string;
}
}, }); }, );
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
    hello: QueryProcedure<{
        input: string | null | undefined;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    admin: BuiltRouter<{
        ctx: Context;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        secret: QueryProcedure<{
            input: void;
            output: {
                secret: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>(_: {
    hello: QueryProcedure<{
        input: string | null | undefined;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    admin: BuiltRouter<{
        ctx: Context;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        secret: QueryProcedure<{
            input: void;
            output: {
                secret: string;
            };
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}) => BuiltRouter<...>
router
({
// this is accessible for everyone
hello: QueryProcedure<{
    input: string | null | undefined;
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
hello
:
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure
.ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodOptional<z.ZodNullable<z.ZodString>>>(schema: z.ZodOptional<z.ZodNullable<z.ZodString>>): ProcedureBuilder<Context, object, object, string | null | undefined, string | null | undefined, UnsetMarker, UnsetMarker, false, DefaultErrorShape>input(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string().ZodType<any, any, $ZodStringInternals<string>>.nullish(): z.ZodOptional<z.ZodNullable<z.ZodString>>nullish()) .
ProcedureBuilder<Context, object, object, string | null | undefined, string | null | undefined, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<Context, object, object, string | null | undefined, UnsetMarker, string>): QueryProcedure<{
    input: string | null | undefined;
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
((opts: ProcedureResolverOptions<Context, object, object, string | null | undefined>opts) => `hello ${opts: ProcedureResolverOptions<Context, object, object, string | null | undefined>opts.ProcedureResolverOptions<Context, object, object, string | null | undefined>.input: string | null | undefinedinput ?? opts: ProcedureResolverOptions<Context, object, object, string | null | undefined>opts.
ProcedureResolverOptions<Context, object, object, string | null | undefined>.ctx: {
    user: {
        name: string;
    } | null;
}
ctx
.
user: {
    name: string;
} | null
user
?.name: string | undefinedname ?? 'world'}`),
admin: BuiltRouter<{
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    secret: QueryProcedure<{
        input: void;
        output: {
            secret: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
admin
:
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
    secret: QueryProcedure<{
        input: void;
        output: {
            secret: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    secret: QueryProcedure<{
        input: void;
        output: {
            secret: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}) => BuiltRouter<{
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    secret: QueryProcedure<{
        input: void;
        output: {
            secret: string;
        };
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
// this is accessible only to admins
secret: QueryProcedure<{
    input: void;
    output: {
        secret: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
}>
secret
:
const protectedProcedure: ProcedureBuilder<Context, object, {
    user: {
        name: string;
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
protectedProcedure
.
ProcedureBuilder<Context, object, { user: { name: string; }; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
    secret: string;
}>(resolver: ProcedureResolver<Context, object, {
    user: {
        name: string;
    };
}, UnsetMarker, UnsetMarker, {
    secret: string;
}>): QueryProcedure<{
    input: void;
    output: {
        secret: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
((
opts: ProcedureResolverOptions<Context, object, {
    user: {
        name: string;
    };
}, UnsetMarker>
opts
) => {
return { secret: stringsecret: 'sauce', }; }), }), });

Building on a protected procedure

Since a procedure's middlewares run in order, you can add more checks on top of protectedProcedure, where ctx.user is already non-null:

server/trpc.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC, class TRPCErrorTRPCError } from '@trpc/server';

type 
type Context = {
    user: {
        name: string;
        role: "admin" | "user";
    } | null;
}
Context
= {
user: {
    name: string;
    role: "admin" | "user";
} | null
user
: { name: stringname: string; role: "admin" | "user"role: 'admin' | 'user' } | null };
export const
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.context<Context>(): TRPCBuilder<Context, object>context<
type Context = {
    user: {
        name: string;
        role: "admin" | "user";
    } | null;
}
Context
>().
TRPCBuilder<Context, object>.create<RuntimeConfigOptions<Context, object>>(opts?: RuntimeConfigOptions<Context, object> | undefined): TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
export const
const protectedProcedure: ProcedureBuilder<Context, object, {
    user: {
        name: string;
        role: "admin" | "user";
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
protectedProcedure
=
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure.
ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
    user: {
        name: string;
        role: "admin" | "user";
    };
}>(fn: MiddlewareBuilder<{
    user: {
        name: string;
        role: "admin" | "user";
    } | null;
}, object, {
    user: {
        name: string;
        role: "admin" | "user";
    };
}, UnsetMarker> | MiddlewareFunction<Context, object, object, {
    user: {
        name: string;
        role: "admin" | "user";
    };
}, UnsetMarker>): ProcedureBuilder<Context, object, {
    user: {
        name: string;
        role: "admin" | "user";
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(async (
opts: {
    ctx: {
        user: {
            name: string;
            role: "admin" | "user";
        } | null;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) => {
if (!
opts: {
    ctx: {
        user: {
            name: string;
            role: "admin" | "user";
        } | null;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
ctx: {
    user: {
        name: string;
        role: "admin" | "user";
    } | null;
}
ctx
.
user: {
    name: string;
    role: "admin" | "user";
} | null
user
) {
throw new
new TRPCError(opts: {
    message?: string;
    code: TRPC_ERROR_CODE_KEY;
    cause?: unknown;
}): TRPCError
TRPCError
({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'UNAUTHORIZED' });
} return
opts: {
    ctx: {
        user: {
            name: string;
            role: "admin" | "user";
        } | null;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: <{
    user: {
        name: string;
        role: "admin" | "user";
    };
}>(opts: {
    ctx?: {
        user: {
            name: string;
            role: "admin" | "user";
        };
    } | undefined;
    input?: unknown;
}) => Promise<MiddlewareResult<{
    user: {
        name: string;
        role: "admin" | "user";
    };
}>> (+2 overloads)
next
({
ctx?: {
    user: {
        name: string;
        role: "admin" | "user";
    };
} | undefined
ctx
: {
user: {
    name: string;
    role: "admin" | "user";
}
user
:
opts: {
    ctx: {
        user: {
            name: string;
            role: "admin" | "user";
        } | null;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
ctx: {
    user: {
        name: string;
        role: "admin" | "user";
    } | null;
}
ctx
.
user: {
    name: string;
    role: "admin" | "user";
}
user
} });
}); export const
const adminProcedure: ProcedureBuilder<Context, object, {
    user: {
        name: string;
        role: "admin" | "user";
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
adminProcedure
=
const protectedProcedure: ProcedureBuilder<Context, object, {
    user: {
        name: string;
        role: "admin" | "user";
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
protectedProcedure
.
ProcedureBuilder<Context, object, { user: { name: string; role: "admin" | "user"; }; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
    user: {
        name: string;
        role: "admin" | "user";
    };
}>(fn: MiddlewareBuilder<{
    user: {
        name: string;
        role: "admin" | "user";
    };
}, object, {
    user: {
        name: string;
        role: "admin" | "user";
    };
}, UnsetMarker> | MiddlewareFunction<Context, object, {
    user: {
        name: string;
        role: "admin" | "user";
    };
}, {
    user: {
        name: string;
        role: "admin" | "user";
    };
}, UnsetMarker>): ProcedureBuilder<Context, object, {
    user: {
        name: string;
        role: "admin" | "user";
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(async (
opts: {
    ctx: {
        user: {
            name: string;
            role: "admin" | "user";
        };
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<{
            user: {
                name: string;
                role: "admin" | "user";
            };
        }>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) => {
if (
opts: {
    ctx: {
        user: {
            name: string;
            role: "admin" | "user";
        };
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<{
            user: {
                name: string;
                role: "admin" | "user";
            };
        }>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
ctx: {
    user: {
        name: string;
        role: "admin" | "user";
    };
}
ctx
.user.role: "admin" | "user"role !== 'admin') {
user: {
    name: string;
    role: "admin" | "user";
}
throw new
new TRPCError(opts: {
    message?: string;
    code: TRPC_ERROR_CODE_KEY;
    cause?: unknown;
}): TRPCError
TRPCError
({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'FORBIDDEN' });
} return
opts: {
    ctx: {
        user: {
            name: string;
            role: "admin" | "user";
        };
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<{
            user: {
                name: string;
                role: "admin" | "user";
            };
        }>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: () => Promise<MiddlewareResult<{
    user: {
        name: string;
        role: "admin" | "user";
    };
}>> (+2 overloads)
next
();
});

Throw UNAUTHORIZED (401) when the caller isn't signed in, and FORBIDDEN (403) when they are, but aren't allowed to do this.

Sending credentials from the client

  • Cookies are sent with every request when the client and server are on the same origin. For another origin, see CORS & Cookies.
  • Headers like Authorization can be set on any HTTP link with the headers option.
  • Subscriptions over SSE can't send custom headers with the browser's EventSource. See httpSubscriptionLink for the options.
  • WebSockets send connection params when the connection opens, which createContext reads from opts.info.connectionParams.

Common mistakes

  • Calling opts.next() without the narrowed ctx. Procedures after the middleware still see ctx.user as nullable. Pass opts.next({ ctx: { user: opts.ctx.user } }), as in Option 2.
  • Throwing in createContext for a missing or invalid token. That fails every procedure in the request, public ones too. Return user: null instead, and let a middleware reject the procedures that need a user.
  • Putting tokens in httpSubscriptionLink's connectionParams. They are sent in the URL, where servers and proxies may log them. Prefer cookies, or headers through an EventSource ponyfill.