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' }