Type Generation

Litestar Vite includes a type generation system that keeps your frontend in sync with your Python backend.

At a Glance

  • Run litestar assets generate-types to export OpenAPI + routes and generate TS types.

  • Generated artifacts live under src/generated by default.

  • .litestar.json is refreshed during generation so the Vite plugin stays in sync.

Type generation
Type generation demo

Export OpenAPI, routes, and Inertia page-prop metadata into frontend-friendly TypeScript artifacts.

Overview

The type generation pipeline:

  1. Exports your Litestar OpenAPI schema to JSON

  2. Generates TypeScript interfaces using @hey-api/openapi-ts

  3. Extracts route metadata from your application

  4. Generates a typed route() helper for type-safe URL generation

Configuration

Enable type generation in your ViteConfig:

from litestar_vite.config import TypeGenConfig

VitePlugin(
    config=ViteConfig(
        types=TypeGenConfig(
            output=Path("src/generated"),  # default
            generate_sdk=True,             # generate API client
            generate_routes=True,          # generate routes.ts
        )
    )
)

Or use the shortcut for defaults:

VitePlugin(config=ViteConfig(types=True))

TypeGenConfig Reference

Parameter

Default

Description

output

src/generated

Output directory for all generated files

openapi_path

{output}/openapi.json

Path for exported OpenAPI schema

routes_path

{output}/routes.json

Path for routes metadata JSON

routes_ts_path

{output}/routes.ts

Path for typed route helper (generated by Litestar type export)

schemas_ts_path

{output}/schemas.ts

Path for schemas.ts helper types (generated by litestar-vite typegen)

generate_zod

False

Generate Zod schemas for runtime validation

generate_sdk

True

Generate API client SDK via hey-api (fetch-based)

generate_routes

True

Generate typed routes.ts file

generate_page_props

True

Generate Inertia page props types (effective only when Inertia is enabled)

generate_schemas

True

Generate schemas.ts with ergonomic form/response type helpers

page_props_path

{output}/inertia-pages.json

Path for page props metadata

fallback_type

unknown

Fallback value type for untyped dict/list in page props (unknown or any)

type_import_paths

{}

Map schema/type names to TypeScript import paths for props types excluded from OpenAPI

Default Inertia shared-props types (User, AuthData, FlashMessages) are controlled by InertiaTypeGenConfig under InertiaConfig.type_gen.

hey-api Configuration

Litestar Vite uses hey-api/openapi-ts to generate TypeScript types from your OpenAPI schema. Create an openapi-ts.config.ts in your project root:

openapi-ts.config.ts
import { defineConfig } from "@hey-api/openapi-ts"

export default defineConfig({
  input: "./src/generated/openapi.json",
  output: "./src/generated/api",
  plugins: ["@hey-api/typescript", "@hey-api/schemas", "@hey-api/sdk", "@hey-api/client-axios"],
})

Available hey-api plugins:

  • @hey-api/typescript - Core TypeScript types (always included)

  • @hey-api/schemas - JSON Schema exports

  • @hey-api/sdk - Type-safe API client

  • @hey-api/client-axios - Axios-based HTTP client

  • @hey-api/client-fetch - Fetch-based HTTP client

  • @hey-api/zod - Zod runtime validators

Generating Types

Generate all types with a single command:

litestar assets generate-types

This runs the full pipeline:

  1. Exports OpenAPI schema to src/generated/openapi.json

  2. Exports route metadata to src/generated/routes.json and routes.ts (if enabled)

  3. Runs litestar-vite-typegen (invokes @hey-api/openapi-ts and generates schemas.ts / page props types)

The command also writes/updates .litestar.json and reports whether it was updated or unchanged, matching the output style for other generated files.

You can also export routes separately:

litestar assets export-routes

Generated Files

After running generate-types, your output directory contains:

src/generated/
├── openapi.json       # OpenAPI schema from Litestar
├── routes.json        # Route metadata
├── routes.ts          # Typed route() helper
├── schemas.ts         # Form/response type helpers (if generate_schemas=True)
└── api/               # hey-api output (if generate_sdk=True)
    ├── index.ts       # Barrel export
    ├── types.gen.ts   # TypeScript interfaces
    ├── schemas.gen.ts # JSON schemas (if @hey-api/schemas)
    └── sdk.gen.ts     # API client (if @hey-api/sdk)

Using Generated Types

Import types and the route helper in your frontend:

import { route } from './generated/routes';
import type { Book, Summary } from './generated/api';

// Type-safe URL generation
const bookUrl = route('api:books.detail', { book_id: 123 });
// => "/api/books/123"

// Type-safe API calls
const response = await fetch(route('api:summary'));
const summary: Summary = await response.json();

Using the Generated SDK

If generate_sdk=True, hey-api generates a fully typed API client:

import { client, getApiSummary, getApiBooks } from './generated/api';

// Configure base URL (optional - defaults to same origin)
client.setConfig({ baseUrl: '/api' });

// Type-safe API calls with full IntelliSense
const { data: summary } = await getApiSummary();
const { data: books } = await getApiBooks();

// Parameters are typed
const { data: book } = await getApiBooksBookId({ path: { book_id: 1 } });

Typed Routes

The generated routes.ts provides Ziggy-style type-safe routing:

// If OpenAPI schemas include a `format`, `routes.ts` also emits semantic aliases
// and uses them in route parameter types (aliases are plain primitives, no runtime parsing).
export type UUID = string;
export type DateTime = string;

// Generated types
export type RouteName = "home" | "api:summary" | "api:books" | "api:books.detail";

export interface RoutePathParams {
  "api:books.detail": { book_id: number };
  "home": Record<string, never>;
  // ... other routes
}

export interface RouteQueryParams {
  "api:books.detail": Record<string, never>;
  "home": Record<string, never>;
  // ... other routes
}

type EmptyParams = Record<string, never>
type MergeParams<A, B> =
  A extends EmptyParams ? (B extends EmptyParams ? EmptyParams : B) : B extends EmptyParams ? A : A & B

export type RouteParams<T extends RouteName> =
  MergeParams<RoutePathParams[T], RouteQueryParams[T]>;

// Type-safe route function (params required only when needed)
export function route<T extends RoutesWithoutRequiredParams>(name: T): string;
export function route<T extends RoutesWithoutRequiredParams>(name: T, params?: RouteParams<T>): string;
export function route<T extends RoutesWithRequiredParams>(name: T, params: RouteParams<T>): string;

// Usage
route("home");                                  // "/"
route("api:books.detail", { book_id: 123 });   // "/api/books/123"
route("api:books.detail");                     // TypeScript Error!

Note

For URL generation, route params never require null values. Optionality is represented by optional query parameters (?:) and omission, matching how route() serializes values.

Form and Schema Types

The generated schemas.ts provides ergonomic type helpers for working with request bodies and response types. These helpers use route names (like routes.ts) for a clean, consistent developer experience.

Generated Types:

// schemas.ts provides:
import type {
    OperationName,    // Union of all API operation names
    FormInput,        // Extract request body type
    FormResponse,     // Extract response type by status code
    SuccessResponse,  // Extract 200/201 response
    ErrorResponse,    // Extract error response type
    PathParams,       // Extract path parameters
    QueryParams,      // Extract query parameters
} from './generated/schemas'

Usage with Inertia.js Forms (React):

import { useForm } from '@inertiajs/react'
import type { FormInput, SuccessResponse } from '@/generated/schemas'
import { route } from '@/generated/routes'

function LoginForm() {
    // Type-safe form data using route names
    const form = useForm<FormInput<'api:login'>>({
        username: '',
        password: '',
    })

    const submit = () => {
        form.post(route('api:login'))
    }

    return (
        <form onSubmit={(e) => { e.preventDefault(); submit() }}>
            <input
                value={form.data.username}
                onChange={e => form.setData('username', e.target.value)}
            />
            {/* TypeScript knows 'username' is a valid field */}
        </form>
    )
}

Usage with REST APIs:

import type { FormInput, SuccessResponse, FormResponse } from '@/generated/schemas'
import { route } from '@/generated/routes'

async function login(credentials: FormInput<'api:login'>) {
    const response = await fetch(route('api:login'), {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials),
    })

    if (response.ok) {
        const data: SuccessResponse<'api:login'> = await response.json()
        return data.access_token  // TypeScript knows this exists
    } else {
        const error: FormResponse<'api:login', 400> = await response.json()
        throw new Error(error.detail)  // TypeScript knows this exists
    }
}

Available Helper Types:

Type

Description

FormInput<T>

Request body type for operation T (e.g., form fields)

FormResponse<T, S>

Response type for operation T with status code S

SuccessResponse<T>

Response type for 200/201/204 status

ErrorResponse<T>

Error response type for operation T

PathParams<T>

Path parameter type for operation T

QueryParams<T>

Query parameter type for operation T

PartialFormInput<T>

All form fields optional (for initialization)

Configuration:

Schema type generation is enabled by default. Disable with:

TypeGenConfig(generate_schemas=False)

Inertia Page Props

When using Inertia.js, enable page props generation:

VitePlugin(
    config=ViteConfig(
        types=TypeGenConfig(generate_page_props=True),
        inertia=True,
    )
)

This generates inertia-pages.json which the Vite plugin uses to create page-props.ts with typed props for each page component.

See Type Generation for Inertia-specific details.

See Also