Skip to content
tRPC

Metadata

On this page

Procedure metadata allows you to add an optional procedure specific meta property which will be available in all middleware function parameters.

Create router with typed metadata

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

type 
type Context = {
    user: {
        name: string;
    } | null;
}
Context
= {
user: {
    name: string;
} | null
user
: { name: stringname: string } | null };
interface Meta { Meta.authRequired: booleanauthRequired: boolean; } export const
const t: TRPCRootObject<Context, Meta, RuntimeConfigOptions<Context, Meta>, {
    ctx: Context;
    meta: Meta;
    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>.meta<Meta>(): TRPCBuilder<Context, Meta>meta<Meta>().
TRPCBuilder<Context, Meta>.create<RuntimeConfigOptions<Context, Meta>>(opts?: RuntimeConfigOptions<Context, Meta> | undefined): TRPCRootObject<Context, Meta, RuntimeConfigOptions<Context, Meta>, {
    ctx: Context;
    meta: Meta;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
export const
const appRouter: BuiltRouter<{
    ctx: Context;
    meta: Meta;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
appRouter
=
const t: TRPCRootObject<Context, Meta, RuntimeConfigOptions<Context, Meta>, {
    ctx: Context;
    meta: Meta;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<Context, Meta, RuntimeConfigOptions<Context, Meta>, { ctx: Context; meta: Meta; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{}>(_: {}) => BuiltRouter<{
    ctx: Context;
    meta: Meta;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{}>>
router
({
// [...] });

Example with per route authentication settings

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

type 
type Context = {
    user: {
        name: string;
    } | null;
}
Context
= {
user: {
    name: string;
} | null
user
: { name: stringname: string } | null };
interface Meta { Meta.authRequired: booleanauthRequired: boolean; } export const
const t: TRPCRootObject<Context, Meta, RuntimeConfigOptions<Context, Meta>, {
    ctx: Context;
    meta: Meta;
    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>.meta<Meta>(): TRPCBuilder<Context, Meta>meta<Meta>().
TRPCBuilder<Context, Meta>.create<RuntimeConfigOptions<Context, Meta>>(opts?: RuntimeConfigOptions<Context, Meta> | undefined): TRPCRootObject<Context, Meta, RuntimeConfigOptions<Context, Meta>, {
    ctx: Context;
    meta: Meta;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
export const const authedProcedure: ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>authedProcedure =
const t: TRPCRootObject<Context, Meta, RuntimeConfigOptions<Context, Meta>, {
    ctx: Context;
    meta: Meta;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<Context, Meta, RuntimeConfigOptions<Context, Meta>, { ctx: Context; meta: Meta; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, Meta, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure.
ProcedureBuilder<Context, Meta, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<object>(fn: MiddlewareBuilder<{
    user: {
        name: string;
    } | null;
}, Meta, object, UnsetMarker> | MiddlewareFunction<Context, Meta, object, object, UnsetMarker>): ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(async (
opts: {
    ctx: {
        user: {
            name: string;
        } | null;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: Meta | 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 meta: Meta | undefinedmeta,
const next: {
    (): Promise<MiddlewareResult<object>>;
    <$ContextOverride>(opts: {
        ctx?: $ContextOverride | undefined;
        input?: unknown;
    }): Promise<MiddlewareResult<$ContextOverride>>;
    (opts: {
        getRawInput: GetRawInputFn;
    }): Promise<MiddlewareResult<object>>;
}
next
,
const ctx: {
    user: {
        name: string;
    } | null;
}
ctx
} =
opts: {
    ctx: {
        user: {
            name: string;
        } | null;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: Meta | 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
;
// only check authorization if enabled if (const meta: Meta | undefinedmeta?.Meta.authRequired: boolean | undefinedauthRequired && !
const 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 const next: () => Promise<MiddlewareResult<object>> (+2 overloads)next(); }); export const
const appRouter: BuiltRouter<{
    ctx: Context;
    meta: Meta;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: void;
        output: {
            greeting: string;
        };
        meta: Meta;
        errorShape: DefaultErrorShape;
    }>;
    protectedHello: QueryProcedure<{
        input: void;
        output: {
            greeting: string;
        };
        meta: Meta;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
const t: TRPCRootObject<Context, Meta, RuntimeConfigOptions<Context, Meta>, {
    ctx: Context;
    meta: Meta;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<Context, Meta, RuntimeConfigOptions<Context, Meta>, { ctx: Context; meta: Meta; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
    hello: QueryProcedure<{
        input: void;
        output: {
            greeting: string;
        };
        meta: Meta;
        errorShape: DefaultErrorShape;
    }>;
    protectedHello: QueryProcedure<{
        input: void;
        output: {
            greeting: string;
        };
        meta: Meta;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    hello: QueryProcedure<{
        input: void;
        output: {
            greeting: string;
        };
        meta: Meta;
        errorShape: DefaultErrorShape;
    }>;
    protectedHello: QueryProcedure<{
        input: void;
        output: {
            greeting: string;
        };
        meta: Meta;
        errorShape: DefaultErrorShape;
    }>;
}) => BuiltRouter<...>
router
({
hello: QueryProcedure<{
    input: void;
    output: {
        greeting: string;
    };
    meta: Meta;
    errorShape: DefaultErrorShape;
}>
hello
: const authedProcedure: ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>authedProcedure.ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.meta(meta: Meta): ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>meta({ Meta.authRequired: booleanauthRequired: false }).
ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
    greeting: string;
}>(resolver: ProcedureResolver<Context, Meta, {}, UnsetMarker, UnsetMarker, {
    greeting: string;
}>): QueryProcedure<{
    input: void;
    output: {
        greeting: string;
    };
    meta: Meta;
    errorShape: DefaultErrorShape;
}>
query
(() => {
return { greeting: stringgreeting: 'hello world', }; }),
protectedHello: QueryProcedure<{
    input: void;
    output: {
        greeting: string;
    };
    meta: Meta;
    errorShape: DefaultErrorShape;
}>
protectedHello
: const authedProcedure: ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>authedProcedure.ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.meta(meta: Meta): ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>meta({ Meta.authRequired: booleanauthRequired: true }).
ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
    greeting: string;
}>(resolver: ProcedureResolver<Context, Meta, {}, UnsetMarker, UnsetMarker, {
    greeting: string;
}>): QueryProcedure<{
    input: void;
    output: {
        greeting: string;
    };
    meta: Meta;
    errorShape: DefaultErrorShape;
}>
query
(() => {
return { greeting: stringgreeting: 'hello-world', }; }), });

Default meta, chaining, and shallow merging

You can set default values for your meta type, and if you chain meta on top of a base procedure it will be shallow merged.

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

type 
type Context = {
    user: {
        name: string;
    } | null;
}
Context
= {
user: {
    name: string;
} | null
user
: { name: stringname: string } | null };
interface Meta { Meta.authRequired?: boolean | undefinedauthRequired?: boolean; Meta.role?: "user" | "admin" | undefinedrole?: 'user' | 'admin' } export const
const t: TRPCRootObject<Context, Meta, {
    defaultMeta: {
        authRequired: false;
    };
}, {
    ctx: Context;
    meta: Meta;
    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>.meta<Meta>(): TRPCBuilder<Context, Meta>meta<Meta>() .
TRPCBuilder<Context, Meta>.create<{
    defaultMeta: {
        authRequired: false;
    };
}>(opts?: {
    defaultMeta: {
        authRequired: false;
    };
} | undefined): TRPCRootObject<Context, Meta, {
    defaultMeta: {
        authRequired: false;
    };
}, {
    ctx: Context;
    meta: Meta;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
({
// Set a default value
defaultMeta: {
    authRequired: false;
}
defaultMeta
: { authRequired: falseauthRequired: false }
}); const const authMiddleware: MiddlewareBuilder<Context, Meta, object, unknown>authMiddleware =
const t: TRPCRootObject<Context, Meta, {
    defaultMeta: {
        authRequired: false;
    };
}, {
    ctx: Context;
    meta: Meta;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<Context, Meta, { defaultMeta: { authRequired: false; }; }, { ctx: Context; meta: Meta; errorShape: DefaultErrorShape; transformer: false; }>.middleware: <object>(fn: MiddlewareFunction<Context, Meta, object, object, unknown>) => MiddlewareBuilder<Context, Meta, object, unknown>middleware((
opts: {
    ctx: {
        user: {
            name: string;
        } | null;
    };
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: Meta | 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
) =>
opts: {
    ctx: {
        user: {
            name: string;
        } | null;
    };
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: Meta | 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: () => Promise<MiddlewareResult<object>> (+2 overloads)next());
const const publicProcedure: ProcedureBuilder<Context, Meta, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure =
const t: TRPCRootObject<Context, Meta, {
    defaultMeta: {
        authRequired: false;
    };
}, {
    ctx: Context;
    meta: Meta;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<Context, Meta, { defaultMeta: { authRequired: false; }; }, { ctx: Context; meta: Meta; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, Meta, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure
// ^ Default Meta: { authRequired: false } const const authProcedure: ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>authProcedure = const publicProcedure: ProcedureBuilder<Context, Meta, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure .
ProcedureBuilder<Context, Meta, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<object>(fn: MiddlewareBuilder<{
    user: {
        name: string;
    } | null;
}, Meta, object, UnsetMarker> | MiddlewareFunction<Context, Meta, object, object, UnsetMarker>): ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(const authMiddleware: MiddlewareBuilder<Context, Meta, object, unknown>authMiddleware)
.ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.meta(meta: Meta): ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>meta({ Meta.authRequired?: boolean | undefinedauthRequired: true, Meta.role?: "user" | "admin" | undefinedrole: 'user' }); // ^ Meta: { authRequired: true, role: 'user' } const const adminProcedure: ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>adminProcedure = const authProcedure: ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>authProcedure .ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.meta(meta: Meta): ProcedureBuilder<Context, Meta, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>meta({ Meta.role?: "user" | "admin" | undefinedrole: 'admin' }); // ^ Meta: { authRequired: true, role: 'admin' }