Skip to content
tRPC

Content Types

On this page

tRPC supports multiple content types as procedure inputs: JSON-serializable data, FormData, File, Blob, and other binary types.

JSON (Default)

By default, tRPC sends and receives JSON-serializable data. No extra configuration is needed — any input that can be serialized to JSON works out of the box with all links (httpLink, httpBatchLink, httpBatchStreamLink).

import { z } from 'zod';

export const t = initTRPC.create();
const publicProcedure = t.procedure;

export const appRouter = t.router({
  hello: publicProcedure.input(z.object({ name: z.string() })).query((opts) => {
    return { greeting: `Hello ${opts.input.name}` };
  }),
});

Non-JSON Content Types

In addition to JSON, tRPC can use FormData, File, and other binary types as procedure inputs.

Client Setup

httpLink supports non-JSON content types out of the box — if you're only using this link, your existing setup should work immediately.

import { createTRPCClient, httpLink } from '@trpc/client';
import type { AppRouter } from './server';

createTRPCClient<AppRouter>({
  links: [
    httpLink({
      url: 'http://localhost:2022',
    }),
  ],
});

However, not all links support these content types. If you're using httpBatchLink or httpBatchStreamLink, you will need to include a splitLink and route requests based on the content type.

import {
  createTRPCClient,
  httpBatchLink,
  httpLink,
  isNonJsonSerializable,
  splitLink,
} from '@trpc/client';
import type { AppRouter } from './server';

const url = 'http://localhost:2022';

createTRPCClient<AppRouter>({
  links: [
    splitLink({
      condition: (op) => isNonJsonSerializable(op.input),
      true: httpLink({
        url,
      }),
      false: httpBatchLink({
        url,
      }),
    }),
  ],
});

If you are using transformer in your tRPC server, TypeScript requires that your tRPC client link defines transformer as well. Use this example as a base:

import {
  createTRPCClient,
  httpBatchLink,
  httpLink,
  isNonJsonSerializable,
  splitLink,
} from '@trpc/client';
import superjson from 'superjson';
import type { AppRouter } from './server';

const url = 'http://localhost:2022';

createTRPCClient<AppRouter>({
  links: [
    splitLink({
      condition: (op) => isNonJsonSerializable(op.input),
      true: httpLink({
        url,
        transformer: {
          // request - convert data before sending to the tRPC server
          serialize: (data) => data,
          // response - convert the tRPC response before using it in client
          deserialize: (data) => superjson.deserialize(data), // or your other transformer
        },
      }),
      false: httpBatchLink({
        url,
        transformer: superjson, // or your other transformer
      }),
    }),
  ],
});

Server Setup

FormData Input

FormData is natively supported, and for more advanced usage you could also combine this with a library like zod-form-data to validate inputs in a type-safe way.

import { z } from 'zod';

export const t = initTRPC.create();
const publicProcedure = t.procedure;

export const appRouter = t.router({
  hello: publicProcedure.input(z.instanceof(FormData)).mutation((opts) => {
    const data = opts.input;
    return {
      greeting: `Hello ${data.get('name')}`,
    };
  }),
});

For a more advanced code sample you can see our example project here

File and other Binary Type Inputs

tRPC converts many octet content types to a ReadableStream which can be consumed in a procedure. Currently these are Blob Uint8Array and File.

import { octetInputParser } from '@trpc/server/http';

export const t = initTRPC.create();
const publicProcedure = t.procedure;

export const appRouter = t.router({
  upload: publicProcedure.input(octetInputParser).mutation((opts) => {
    const data = opts.input;
    return {
      valid: true,
    };
  }),
});