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 { initTRPC } from '@trpc/server';

// [...]

interface Meta {
  authRequired: boolean;
}

export const t = initTRPC.context<Context>().meta<Meta>().create();

export const appRouter = t.router({
  // [...]
});

Example with per route authentication settings

server.ts
import { initTRPC } from '@trpc/server';

// [...]

interface Meta {
  authRequired: boolean;
}

export const t = initTRPC.context<Context>().meta<Meta>().create();

export const authedProcedure = t.procedure.use(async (opts) => {
  const { meta, next, ctx } = opts;
  // only check authorization if enabled
  if (meta?.authRequired && !ctx.user) {
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }
  return next();
});

export const appRouter = t.router({
  hello: authedProcedure.meta({ authRequired: false }).query(() => {
    return {
      greeting: 'hello world',
    };
  }),
  protectedHello: authedProcedure.meta({ authRequired: true }).query(() => {
    return {
      greeting: '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 { initTRPC } from '@trpc/server';

interface Meta {
  authRequired: boolean;
  role?: 'user' | 'admin'
}

export const t = initTRPC
  .context<Context>()
  .meta<Meta>()
  .create({
    // Set a default value
    defaultMeta: { authRequired: false }
  });

const publicProcedure = t.procedure
// ^ Default Meta: { authRequired: false }

const authProcedure = publicProcedure
  .use(authMiddleware)
  .meta({
    authRequired: true;
    role: 'user'
  });
// ^ Meta: { authRequired: true, role: 'user' }

const adminProcedure = authProcedure
  .meta({
    role: 'admin'
  });
// ^ Meta: { authRequired: true, role: 'admin' }