BETTER-CONVEX

Type Inference

Infer input and output types from your Convex API.

In this guide, we'll explore type inference in cRPC. You'll learn to extract input and output types from your API for use in components, utilities, and type-safe operations.

Overview

cRPC provides type inference utilities similar to tRPC:

TypeDescription
ApiOutputsReturn types of all procedures
ApiInputsInput/argument types of all procedures
ApiCombined API type with HTTP router

Let's set it up.

convex/types.ts
import type {
  inferApiInputs,
  inferApiOutputs,
  WithHttpRouter,
} from 'better-convex/server';
import type { api } from './_generated/api';
import type { appRouter } from './http'; // optional, if using HTTP routes

// WithHttpRouter combines API with HTTP router (http is optional)
export type Api = WithHttpRouter<typeof api, typeof appRouter>;
export type ApiInputs = inferApiInputs<Api>;
export type ApiOutputs = inferApiOutputs<Api>;

Usage

Access nested procedure types using bracket notation:

src/components/user-card.tsx
import type { ApiInputs, ApiOutputs } from '@convex/types';

// Output types
type User = ApiOutputs['user']['get'];
type UserList = ApiOutputs['user']['list'];
type Post = ApiOutputs['post']['get'];

// Input types
type GetUserArgs = ApiInputs['user']['get'];
type CreatePostArgs = ApiInputs['post']['create'];

In Components

Use output types for component props:

src/components/user-profile.tsx
import type { ApiOutputs } from '@convex/types';

type User = ApiOutputs['user']['get'];

function UserProfile({ user }: { user: User }) {
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

In Utility Functions

Use both input and output types for type-safe utilities:

src/lib/utils/user.ts
import type { ApiInputs, ApiOutputs } from '@convex/types';

type User = ApiOutputs['user']['get'];
type UpdateUserArgs = ApiInputs['user']['update'];

function formatUserName(user: User): string {
  return user.name ?? 'Anonymous';
}

function validateUpdateArgs(args: UpdateUserArgs): boolean {
  return !!args.name || !!args.email;
}

Deep Nested Types

For deeply nested APIs, chain bracket notation:

// api.organization.members.list
type OrgMember = ApiOutputs['organization']['members']['list'][number];

// api.project.settings.get
type ProjectSettings = ApiOutputs['project']['settings']['get'];

Tip: Use [number] to extract the item type from array return types.

Migrate from Convex

If you're coming from vanilla Convex, here's what changes.

What stays the same

  • Type inference from function definitions
  • Full TypeScript support

What's new

Before (vanilla Convex):

import type { FunctionReturnType, FunctionArgs } from 'convex/server';
import { api } from '@convex/_generated/api';

type User = FunctionReturnType<typeof api.user.get>;
type GetUserArgs = FunctionArgs<typeof api.user.get>;

After (cRPC):

import type { ApiInputs, ApiOutputs } from '@convex/types';

type User = ApiOutputs['user']['get'];
type GetUserArgs = ApiInputs['user']['get'];

Key differences:

FeatureDescription
Bracket notationCleaner than typeof api.path
Centralized typesAll definitions in one file
Consistent patternSame across all procedures
Nested namespacesWorks with deep nesting

Next Steps

On this page