Tauri TypeGen
A command-line tool that automatically generates TypeScript bindings from your Tauri commands, eliminating manual frontend type creation.
Features
- 🔍 Automatic Discovery: Scans Rust source for
#[tauri::command]functions - 📝 TypeScript Generation: Creates TypeScript interfaces for command parameters and return types
- ✅ Validation Support: Optional Zod schema generation with runtime validation
- 🚀 Command Bindings: Strongly-typed frontend functions
- 📡 Event Support: Discovers and types
app.emit()events - 📞 Channel Support: Types for streaming
Channel<T>parameters - 🏷️ Serde Support: Respects
#[serde(rename)]and#[serde(rename_all)]attributes - 🎯 Type Safety: Keeps frontend and backend types in sync
- 🛠️ Build Integration: Works as standalone CLI or build dependency
Table of Contents
- Installation
- Quick Setup
- Recommended Setup
- Generated Code
- Using Generated Bindings
- TypeScript Compatibility
- API Reference
- Configuration
- Examples
- Contributing
Installation
Install globally as a CLI tool:
Or add as a build dependency to your Tauri project:
Quick Setup
For trying it out or one-time generation:
# Install CLI
# Generate types once
# Use generated bindings
This generates TypeScript files in ./src/generated/ from your ./src-tauri/ code.
Recommended Setup
For integrated development workflow:
1. Install and Initialize
# Install CLI
# Initialize configuration (adds to tauri.conf.json)
# Or with custom settings
This creates a configuration block in your tauri.conf.json:
2. Add Build Hook
Add to src-tauri/build.rs:
Now types auto-generate on every Rust build:
Generated Code
Example Rust Code
use ;
use Channel;
// Simple command
pub async
// Command with custom types
pub async
// Command with Channel for progress streaming
pub async
// Event emission
Generated Files
src/generated/
├── types.ts # TypeScript interfaces
├── commands.ts # Typed command functions
└── events.ts # Event listener functions (if events detected)
Generated types.ts:
import type { Channel } from '@tauri-apps/api/core';
export interface User {
id: number;
name: string;
email: string;
}
export interface CreateUserRequest {
name: string;
email: string;
}
export interface ProgressUpdate {
percentage: number;
message: string;
}
export interface GetUserParams {
id: number;
}
export interface CreateUserParams {
request: CreateUserRequest;
}
export interface DownloadFileParams {
url: string;
onProgress: Channel<ProgressUpdate>;
}
Generated commands.ts:
import { invoke, Channel } from '@tauri-apps/api/core';
import * as types from './types';
export async function getUser(params: types.GetUserParams): Promise<types.User> {
return invoke('get_user', params);
}
export async function createUser(params: types.CreateUserParams): Promise<types.User> {
return invoke('create_user', params);
}
export async function downloadFile(params: types.DownloadFileParams): Promise<string> {
return invoke('download_file', params);
}
Generated events.ts:
import { listen } from '@tauri-apps/api/event';
export async function onUserNotification(handler: (event: string) => void) {
return listen('user-notification', (event) => handler(event.payload as string));
}
With Zod Validation
When using --validation zod, generated commands include runtime validation:
export async function createUser(
params: types.CreateUserParams,
hooks?: CommandHooks<types.User>
): Promise<types.User> {
try {
const result = types.CreateUserParamsSchema.safeParse(params);
if (!result.success) {
hooks?.onValidationError?.(result.error);
throw result.error;
}
const data = await invoke<types.User>('create_user', result.data);
hooks?.onSuccess?.(data);
return data;
} catch (error) {
if (!(error instanceof ZodError)) {
hooks?.onInvokeError?.(error);
}
throw error;
} finally {
hooks?.onSettled?.();
}
}
Using Generated Bindings
Basic Usage
import { getUser, createUser, downloadFile } from './generated';
import { Channel } from '@tauri-apps/api/core';
// Simple command
const user = await getUser({ id: 1 });
// With custom types
const newUser = await createUser({
request: {
name: "John Doe",
email: "john@example.com"
}
});
// With Channel for streaming
const onProgress = new Channel<ProgressUpdate>();
onProgress.onmessage = (progress) => {
console.log(`${progress.percentage}%: ${progress.message}`);
};
const result = await downloadFile({
url: "https://example.com/file.zip",
onProgress
});
With Event Listeners
import { onUserNotification } from './generated';
// Listen for events
const unlisten = await onUserNotification((message) => {
console.log('Notification:', message);
});
// Stop listening
unlisten();
React Example
import React, { useState } from 'react';
import { createUser } from './generated';
import type { User } from './generated';
export function CreateUserForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const user = await createUser({
request: { name, email }
});
console.log('Created:', user);
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={e => setName(e.target.value)} />
<input value={email} onChange={e => setEmail(e.target.value)} />
<button type="submit">Create User</button>
</form>
);
}
With Zod Validation Hooks
import { createUser } from './generated';
import { toast } from 'sonner';
await createUser(
{ request: userData },
{
onValidationError: (err) => toast.error(err.errors[0].message),
onInvokeError: (err) => toast.error('Failed to create user'),
onSuccess: (user) => toast.success(`Created ${user.name}!`),
}
);
TypeScript Compatibility
Requirements
- TypeScript 5.0+
- Zod 4.x (when using Zod validation)
- ES2018+ target
tsconfig.json
Type Mappings
| Rust Type | TypeScript |
|---|---|
String, &str |
string |
i32, f64, etc. |
number |
bool |
boolean |
Option<T> |
T | null |
Vec<T> |
T[] |
HashMap<K,V> |
Map<K,V> |
(T,U) |
[T,U] |
Channel<T> |
Channel<T> |
Result<T,E> |
T (errors via Promise rejection) |
Serde Attribute Support
Tauri-typegen respects serde serialization attributes to ensure generated TypeScript types match your JSON API:
Field Renaming
use ;
Generates:
export interface User {
userId: number; // Field renamed as specified
name: string;
}
Struct-Level Naming Convention
Generates:
export interface ApiResponse {
userId: number; // snake_case → camelCase
userName: string; // snake_case → camelCase
isActive: boolean; // snake_case → camelCase
}
Supported naming conventions:
camelCasePascalCasesnake_caseSCREAMING_SNAKE_CASEkebab-caseSCREAMING-KEBAB-CASE
Field Precedence
Field-level rename takes precedence over struct-level rename_all:
Skip Fields
Enum Support
Enums also support serde rename attributes:
Generates:
export type MyEnum = "HELLO_WORLD" | "BYE_WORLD";
// With Zod:
export const MyEnumSchema = z.enum(["HELLO_WORLD", "BYE_WORLD"]);
Variant-level rename also works:
API Reference
CLI Commands
# Generate bindings
# Initialize configuration
Build Script API
// In src-tauri/build.rs
Programmatic API
use ;
let config = GenerateConfig ;
let files = generate_from_config?;
Configuration
Standalone Config File
Tauri Config Integration
In tauri.conf.json:
Validation Options
none(default): TypeScript types only, no runtime validationzod: Generate Zod schemas with runtime validation and hooks
Examples
See the examples repository: https://github.com/thwbh/tauri-typegen-examples
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
License
This project is licensed under the MIT license.