22 stable releases
| 1.8.1 | Jan 9, 2026 |
|---|---|
| 1.8.0 | Jan 8, 2026 |
| 1.2.0 | Dec 31, 2025 |
#184 in Web programming
280KB
6K
SLoC
tauri-ts-generator
A powerful CLI tool to automatically generate TypeScript bindings from your Rust Tauri commands, structs, and enums.
tauri-ts-generator scans your Rust source code, parses #[tauri::command] macros and data structures, and generates type-safe TypeScript interfaces and invocation functions. This eliminates manual typing boilerplate and ensures your frontend and backend are always in sync.
Features
- Automated Scanning: Recursively scans your
src-tauridirectory for commands and types. - Type Safety: Generates exact TypeScript definitions for Rust structs, enums, and type aliases.
- Serde Support:
- Respects
#[serde(rename = "...")]attributes, preserving the exact name and overriding camelCase conversion. - Handles
#[serde(rename_all = "...")]for enums and structs. - Supports
#[serde(tag = "...")],#[serde(content = "...")], and#[serde(untagged)]enum representations. - Supports
#[serde(flatten)]to generate TypeScript intersection types. - Fields with
#[serde(skip)]are excluded from TypeScript output. - Support for
#[ts(optional)]attribute onOptionfields to generateprop?: Tinstead ofT | null. - Provides
#[derive(tauri_ts_generator::TS)]to register thetsattribute namespace.
- Respects
- Smart Type Mapping:
- Maps common Rust types (
String,Vec,Option,Result) to TypeScript equivalents. - Handles external crate types like
chrono::DateTime,uuid::Uuid,url::Url, andrust_decimal::Decimal.
- Maps common Rust types (
- Async Handling: Correctly generates
Promise<T>for async commands. - Tauri Integration:
- Automatically imports
invokefrom@tauri-apps/api/core. - Supports
#[tauri::command(rename_all = "...")]to control argument casing (e.g.snake_case).
- Automatically imports
- Macro Support: Optional integration with
cargo-expandto resolve types generated by macros (e.g.,progenitor). - Conflict Resolution: Detects and handles naming conflicts or ambiguous imports.
Installation
cargo install tauri-ts-generator
Or run directly from source:
cargo run --release -- generate
Quick Start
-
Initialize Configuration (Run in your Tauri project root):
tauri-ts-generator initThis creates a
tauri-codegen.tomlfile. -
Generate Bindings:
tauri-ts-generator generate
Configuration (tauri-codegen.toml)
Customize the generator behavior using the TOML configuration file.
[input] Section
Defines where the generator looks for code.
| Key | Description | Default |
|---|---|---|
source_dir |
Root directory of your Rust source code. | "src-tauri/src" |
exclude |
List of directories or files to ignore. | ["tests", "target"] |
use_cargo_expand |
Enable if you use macro-generated types (requires cargo-expand). |
false |
cargo_manifest |
Path to Cargo.toml for cargo-expand. Auto-detected if empty. |
None |
[output] Section
Defines where the generated TypeScript files are saved.
| Key | Description | Default |
|---|---|---|
types_file |
Path for generated interfaces/types. | "src/generated/types.ts" |
commands_file |
Path for generated invoke functions. | "src/generated/commands.ts" |
[naming] Section
Customize naming conventions for generated types and functions.
| Key | Description | Default |
|---|---|---|
type_prefix |
Prefix added to all generated interface names (e.g., "I"). | "" |
type_suffix |
Suffix added to all generated interface names (e.g., "DTO"). | "" |
function_prefix |
Prefix for generated command functions. | "" |
function_suffix |
Suffix for generated command functions. | "" |
Type Mappings
The generator maps Rust types to TypeScript as follows:
| Rust Type | TypeScript Type |
|---|---|
String, &str, char |
string |
i8...i64, u8...u64, f32, f64 |
number |
bool |
boolean |
Option<T> |
T | null (default), or optional field ?: T (with #[ts(optional)]) |
Vec<T> |
T[] |
HashMap<K, V> |
Record<K, V> (if K is string/number) |
Result<T, E> |
Promise<T> (in return types) |
() / Unit |
void |
bytes::Bytes |
number[] |
serde_json::Value |
unknown |
Supported External Types
Common types from popular crates are mapped automatically:
- Chrono:
DateTime,NaiveDate,NaiveTime→string - Time:
OffsetDateTime,Date→string - Uuid:
Uuid→string - Url:
Url→string - Rust Decimal:
Decimal→string - Std:
Path,PathBuf,IpAddr→string;Duration→number
Examples
1. Basic Command & Struct
Rust:
#[derive(Serialize)]
pub struct User {
pub id: i32,
pub name: String,
}
#[tauri::command]
pub async fn get_user(id: i32) -> Result<User, String> { /* ... */ }
TypeScript Output:
export interface User {
id: number;
name: string;
}
export async function getUser(id: number): Promise<User> {
return invoke<User>("get_user", { id });
}
2. Serde Rename (Exact Casing)
When #[serde(rename = "...")] is used, the generator preserves the exact casing, skipping the default camelCase conversion.
Rust:
#[derive(Serialize)]
pub struct Config {
#[serde(rename = "API_KEY")]
pub api_key: String,
pub retries: i32,
}
TypeScript Output:
export interface Config {
API_KEY: string; // Exactly as renamed in Rust
retries: number; // Default camelCase
}
3. Enums
Supports various serde representations.
Rust:
#[derive(Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Status {
Active,
Inactive,
}
TypeScript Output:
export type Status = "ACTIVE" | "INACTIVE";
Supported rename_all values:
lowercase,UPPERCASEcamelCase,PascalCasesnake_case,SCREAMING_SNAKE_CASEkebab-case,SCREAMING-KEBAB-CASE
4. Command Arguments Rename
Use rename_all on commands to control argument keys in the invoke payload.
Rust:
#[tauri::command(rename_all = "snake_case")]
pub fn update_user(user_id: i32, new_email: String) { /* ... */ }
TypeScript Output:
export async function updateUser(userId: number, newEmail: string): Promise<void> {
// Arguments are mapped to snake_case in the payload
return invoke<void>("update_user", { user_id: userId, new_email: newEmail });
}
5. Option with Undefined
By default, Option<T> maps to T | null. You can use the #[ts(optional)] attribute to map it to prop?: T instead.
Note: You must add
#[derive(tauri_ts_generator::TS)]to enable the#[ts(...)]attribute on your structs.
Rust:
use tauri_ts_generator::TS;
#[derive(Serialize, TS)]
pub struct Config {
pub name: Option<String>,
#[ts(optional)]
pub volume: Option<f32>,
}
TypeScript Output:
export interface Config {
name: string | null; // Default behavior
volume?: number; // With #[ts(optional)]
}
6. Skipping Fields
Fields with #[serde(skip)] are excluded from the TypeScript output. Note that skip_serializing and skip_deserializing are not excluded, as they only affect one direction of serialization.
Rust:
#[derive(Serialize)]
pub struct User {
pub id: i32,
pub name: String,
#[serde(skip)]
pub internal_cache: Vec<u8>, // Excluded from TypeScript
#[serde(skip_serializing)]
pub password_hash: String, // Kept in TypeScript (needed for input)
}
TypeScript Output:
export interface User {
id: number;
name: string;
passwordHash: string; // skip_serializing fields are kept
// internal_cache is excluded due to #[serde(skip)]
}
7. Serde Flatten (Intersection Types)
Use #[serde(flatten)] to embed one struct's fields into another. The generator produces TypeScript intersection types.
Rust:
#[derive(Serialize)]
pub struct Address {
pub city: String,
pub country: String,
}
#[derive(Serialize)]
pub struct User {
pub name: String,
#[serde(flatten)]
pub address: Address,
}
TypeScript Output:
export interface Address {
city: string;
country: string;
}
export type User = {
name: string;
} & Address;
This works correctly for both command arguments (input) and return types (output).
CLI Reference
tauri-ts-generator <COMMAND> [OPTIONS]
Commands:
generate Generate TypeScript bindings
init Create a default configuration file
help Print help information
Options:
-v, --verbose Enable verbose logging (useful for debugging scanning/parsing)
-c, --config Path to config file (default: tauri-codegen.toml)
License
MIT
Dependencies
~1.6–4.5MB
~82K SLoC