tauri-plugin-typegen

Crates.iotauri-plugin-typegen
lib.rstauri-plugin-typegen
version0.1.6
created_at2025-09-05 17:44:49.261208+00
updated_at2025-11-13 19:16:16.923008+00
descriptionDEPRECATED - This crate has been renamed. Please use the new crate: **[tauri-typegen](https://crates.io/crates/tauri-typegen)**
homepage
repository
max_upload_size
id1825855
size539,760
Stefan Poindl (thwbh)

documentation

README

⚠️ DEPRECATED - This crate has been renamed

This crate tauri-plugin-typegen is deprecated and no longer maintained.

Please use the new crate: tauri-typegen

All future development and updates will be published under the new name. Please update your dependencies:

# Old (deprecated)
cargo install tauri-plugin-typegen

# New (current)
cargo install tauri-typegen

Tauri TypeGen

A command-line tool that automatically generates TypeScript models and bindings from your Tauri commands, eliminating the manual process of creating frontend types and validation.

Features

  • 🔍 Automatic Discovery: Scans your Rust source files to find all #[tauri::command] functions
  • 📝 TypeScript Generation: Creates TypeScript interfaces for all command parameters and return types
  • Validation Support: Generates validation schemas using Zod or plain TypeScript types
  • 🚀 Command Bindings: Creates strongly-typed frontend functions that call your Tauri commands
  • 🎯 Type Safety: Ensures frontend and backend types stay in sync
  • 🛠️ CLI Integration: Generate types with a simple command: cargo tauri-typegen generate
  • 📊 Dependency Visualization: Optional dependency graph generation for complex projects
  • ⚙️ Configuration Support: Supports both standalone config files and Tauri project integration

Table of Contents

Quick Start

  1. Install the CLI tool:

    cargo install tauri-plugin-typegen
    
  2. Generate TypeScript bindings from your Tauri project:

    # In your Tauri project root
    cargo tauri-typegen generate
    
  3. Use the generated bindings in your frontend:

    import { createUser, getUsers } from './src/generated';
    
    const user = await createUser({ request: { name: "John", email: "john@example.com" } });
    const users = await getUsers({ filter: null });
    

CLI Commands

cargo tauri-typegen generate [OPTIONS]

Options:
  -p, --project-path <PATH>      Path to Tauri source directory [default: ./src-tauri]
  -o, --output-path <PATH>       Output path for TypeScript files [default: ./src/generated]
  -v, --validation <LIBRARY>     Validation library: zod or none [default: zod]
      --verbose                  Verbose output
      --visualize-deps           Generate dependency graph visualization
  -c, --config <CONFIG_FILE>     Configuration file path
cargo tauri-typegen init [OPTIONS]

Options:
  -p, --project-path <PATH>      Path to Tauri source directory [default: ./src-tauri]
  -g, --generated-path <PATH>    Output path for generated files [default: ./src/generated]
  -o, --output <PATH>            Output path for config file [default: tauri.conf.json]
  -v, --validation <LIBRARY>     Validation library: zod or none [default: zod]
      --verbose                  Verbose output
      --visualize-deps           Generate dependency graph visualization
      --force                    Force overwrite existing configuration

Installation

CLI Tool Installation

Install the CLI tool globally:

cargo install tauri-plugin-typegen

TypeScript Bindings (Optional)

If you need TypeScript bindings for frontend integration:

npm install @thwbh/tauri-plugin-typegen

Note: This plugin requires manual installation. The cargo tauri add command only works with official Tauri plugins.

Configuration Setup

Initialize Configuration

Add typegen configuration to your existing Tauri project:

# Add configuration to your existing tauri.conf.json (default)
cargo tauri-typegen init

# Specify custom validation library
cargo tauri-typegen init --validation none

# Create a standalone config file
cargo tauri-typegen init --output my-config.json --validation zod

Note: The init command requires an existing tauri.conf.json file in your Tauri project. It will update the file to add the plugins.typegen configuration section.

Configuration File

Configuration can be stored in a standalone JSON file or within your tauri.conf.json:

{
  "project_path": "./src-tauri",
  "output_path": "./src/generated",
  "validation_library": "zod",
  "verbose": true,
  "visualize_deps": false
}

Package.json Integration

Add generation to your build scripts:

{
  "scripts": {
    "generate-types": "cargo tauri-typegen generate",
    "tauri:dev": "npm run generate-types && tauri dev", 
    "tauri:build": "npm run generate-types && tauri build"
  }
}

Usage Examples

Example: E-commerce App

Let's say you have these Tauri commands in your Rust backend:

src-tauri/src/commands/products.rs:

use serde::{Deserialize, Serialize};
use tauri::command;
use validator::Validate;

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Product {
    pub id: i32,
    pub name: String,
    pub description: String,
    pub price: f64,
    pub in_stock: bool,
    pub category_id: i32,
}

#[derive(Debug, Deserialize, Validate)]
#[serde(rename_all = "camelCase")]
pub struct CreateProductRequest {
    #[validate(length(min = 1, max = 100))]
    pub name: String,
    #[validate(length(max = 500))]
    pub description: String,
    #[validate(range(min = 0.01, max = 10000.0))]
    pub price: f64,
    pub category_id: i32,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProductFilter {
    pub search: Option<String>,
    pub category_id: Option<i32>,
    pub min_price: Option<f64>,
    pub max_price: Option<f64>,
    pub in_stock_only: Option<bool>,
}

#[command]
pub async fn create_product(request: CreateProductRequest) -> Result<Product, String> {
    // Implementation here
    Ok(Product {
        id: 1,
        name: request.name,
        description: request.description,
        price: request.price,
        in_stock: true,
        category_id: request.category_id,
    })
}

#[command]
pub async fn get_products(filter: Option<ProductFilter>) -> Result<Vec<Product>, String> {
    // Implementation here
    Ok(vec![])
}

#[command]
pub async fn delete_product(id: i32) -> Result<(), String> {
    // Implementation here
    Ok(())
}

Generate TypeScript Bindings

Command Line Generation

Generate bindings with the CLI tool:

# Basic generation with defaults
cargo tauri-typegen generate

# Custom paths and validation
cargo tauri-typegen generate \
  --project-path ./src-tauri \
  --output-path ./src/lib/generated \
  --validation zod \
  --verbose

# Generate with dependency visualization
cargo tauri-typegen generate --visualize-deps

# Use configuration file
cargo tauri-typegen generate --config my-config.json

# Quick examples for different setups
cargo tauri-typegen generate -p ./backend -o ./frontend/types -v zod
cargo tauri-typegen generate --validation none  # No validation schemas

Build Integration

The recommended approach is to use Tauri's built-in build hooks to ensure types are generated before the frontend build starts. This solves the chicken-and-egg problem where the frontend needs the generated types but builds before the Rust backend.

Method 1: Tauri Build Hooks (Recommended)

First, add configuration to your tauri.conf.json:

{
  "build": {
    "beforeDevCommand": "cargo tauri-typegen generate && npm run dev",
    "beforeBuildCommand": "cargo tauri-typegen generate && npm run build",
    "devUrl": "http://localhost:1420",
    "frontendDist": "../dist"
  },
  "plugins": {
    "tauri-typegen": {
      "project_path": "./src-tauri",
      "output_path": "./src/generated",
      "validation_library": "zod",
      "verbose": false,
      "visualize_deps": false
    }
  }
}

Then use standard Tauri commands:

# Development - types generated automatically before frontend starts
npm run tauri dev

# Production build - types generated before frontend build
npm run tauri build

Method 2: Package.json Scripts (Alternative)

If you prefer explicit control in package.json:

{
  "scripts": {
    "generate-types": "cargo tauri-typegen generate",
    "dev": "npm run generate-types && npm run tauri dev", 
    "build": "npm run generate-types && npm run tauri build",
    "tauri": "tauri"
  }
}

Method 3: Cargo Build Scripts (Advanced)

For tighter integration, add type generation to your Rust build process.

Add tauri-typegen as build dependency to your project.

cargo add --build tauri-plugin-typegen

In src-tauri/build.rs:

fn main() {
    // Generate TypeScript bindings before build
    tauri_plugin_typegen::BuildSystem::generate_at_build_time()
        .expect("Failed to generate TypeScript bindings");

    tauri_build::build()
}

Generated Files Structure

After running the generator:

src/generated/
├── types.ts                 # TypeScript interfaces
├── schemas.ts               # Zod validation schemas (if using zod)
├── commands.ts              # Typed command functions
├── index.ts                 # Barrel exports
├── dependency-graph.txt     # Text dependency visualization (if --visualize-deps)
└── dependency-graph.dot     # DOT format graph (if --visualize-deps)

Generated types.ts:

export interface Product {
  id: number;
  name: string;
  description: string;
  price: number;
  inStock: boolean;
  categoryId: number;
}

export interface CreateProductRequest {
  name: string;
  description: string;
  price: number;
  categoryId: number;
}

export interface CreateProductParams {
  request: CreateProductRequest;
}

export interface GetProductsParams {
  filter?: ProductFilter | null;
}

export interface DeleteProductParams {
  id: number;
}

Generated commands.ts:

import { invoke } from '@tauri-apps/api/core';
import * as schemas from './schemas';
import type * as types from './types';

export async function createProduct(params: types.CreateProductParams): Promise<types.Product> {
  const validatedParams = schemas.CreateProductParamsSchema.parse(params);
  return invoke('create_product', validatedParams);
}

export async function getProducts(params: types.GetProductsParams): Promise<types.Product[]> {
  const validatedParams = schemas.GetProductsParamsSchema.parse(params);
  return invoke('get_products', validatedParams);
}

export async function deleteProduct(params: types.DeleteProductParams): Promise<void> {
  const validatedParams = schemas.DeleteProductParamsSchema.parse(params);
  return invoke('delete_product', validatedParams);
}

Using Generated Bindings in Frontend

React Example

import React, { useEffect, useState } from 'react';
import { getProducts, createProduct, deleteProduct } from '../lib/generated';
import type { Product, ProductFilter } from '../lib/generated';

export function ProductList() {
  const [products, setProducts] = useState<Product[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    loadProducts();
  }, []);

  const loadProducts = async () => {
    try {
      setLoading(true);
      const result = await getProducts({ filter: null });
      setProducts(result);
    } catch (error) {
      console.error('Failed to load products:', error);
    } finally {
      setLoading(false);
    }
  };

  const handleCreateProduct = async () => {
    try {
      const newProduct = await createProduct({
        request: {
          name: 'New Product',
          description: 'A new product',
          price: 19.99,
          categoryId: 1,
        }
      });
      
      setProducts([...products, newProduct]);
    } catch (error) {
      console.error('Failed to create product:', error);
    }
  };

  const handleDeleteProduct = async (productId: number) => {
    try {
      await deleteProduct({ id: productId });
      setProducts(products.filter(p => p.id !== productId));
    } catch (error) {
      console.error('Failed to delete product:', error);
    }
  };

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      <h2>Products</h2>
      <button onClick={handleCreateProduct}>Create Product</button>

      <div className="products">
        {products.map((product) => (
          <div key={product.id} className="product-card">
            <h3>{product.name}</h3>
            <p>{product.description}</p>
            <p>${product.price}</p>
            <p>Stock: {product.inStock ? '✅' : '❌'}</p>
            <button onClick={() => handleDeleteProduct(product.id)}>
              Delete
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}

Vue Example

<template>
  <div class="product-manager">
    <h2>Product Manager</h2>
    
    <form @submit.prevent="createProduct" class="create-form">
      <input v-model="newProduct.name" placeholder="Product name" required />
      <textarea v-model="newProduct.description" placeholder="Description"></textarea>
      <input v-model.number="newProduct.price" type="number" step="0.01" placeholder="Price" required />
      <button type="submit">Create Product</button>
    </form>

    <div class="products-list">
      <div v-for="product in products" :key="product.id" class="product-item">
        <h4>{{ product.name }}</h4>
        <p>{{ product.description }}</p>
        <p>${{ product.price }}</p>
        <button @click="deleteProduct(product.id)">Delete</button>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getProducts, createProduct as createProductCmd, deleteProduct as deleteProductCmd } from '../lib/generated';
import type { Product, CreateProductRequest } from '../lib/generated';

const products = ref<Product[]>([]);
const newProduct = ref<CreateProductRequest>({
  name: '',
  description: '',
  price: 0,
  categoryId: 1,
});

onMounted(async () => {
  await loadProducts();
});

const loadProducts = async () => {
  try {
    const result = await getProducts({ filter: null });
    products.value = result;
  } catch (error) {
    console.error('Failed to load products:', error);
  }
};

const createProduct = async () => {
  try {
    const product = await createProductCmd({ request: { ...newProduct.value } });
    products.value.push(product);
    newProduct.value = { name: '', description: '', price: 0, categoryId: 1 };
  } catch (error) {
    console.error('Failed to create product:', error);
  }
};

const deleteProduct = async (id: number) => {
  try {
    await deleteProductCmd({ id });
    products.value = products.value.filter(p => p.id !== id);
  } catch (error) {
    console.error('Failed to delete product:', error);
  }
};
</script>

Svelte Example

src/lib/ProductStore.ts:

import { writable } from 'svelte/store';
import { getProducts, createProduct, deleteProduct } from './generated';
import type { Product, ProductFilter } from './generated';

export const products = writable<Product[]>([]);
export const loading = writable(false);

export const productStore = {
  async loadProducts(filter: ProductFilter = {}) {
    loading.set(true);
    try {
      const result = await getProducts({ filter });
      products.set(result);
    } catch (error) {
      console.error('Failed to load products:', error);
    } finally {
      loading.set(false);
    }
  },

  async createProduct(request: CreateProductRequest) {
    try {
      const newProduct = await createProduct({ request });
      products.update(items => [...items, newProduct]);
      return newProduct;
    } catch (error) {
      console.error('Failed to create product:', error);
      throw error;
    }
  },

  async deleteProduct(id: number) {
    try {
      await deleteProduct({ id });
      products.update(items => items.filter(p => p.id !== id));
    } catch (error) {
      console.error('Failed to delete product:', error);
      throw error;
    }
  }
};

Benefits of Using Generated Bindings

✅ Type Safety

// ❌ Before: Manual typing, prone to errors
const result = await invoke('create_product', {
  name: 'Product',
  price: '19.99', // Oops! Should be number
  category_id: 1   // Oops! Should be camelCase
});

// ✅ After: Generated bindings with validation
const result = await createProduct({
  request: {
    name: 'Product',
    price: 19.99,      // Correct type
    categoryId: 1      // Correct naming
  }
});

✅ Runtime Validation

// Automatically validates input at runtime
try {
  await createProduct({
    request: {
      name: '', // Will throw validation error
      price: -5 // Will throw validation error
    }
  });
} catch (error) {
  console.error('Validation failed:', error);
}

✅ IntelliSense & Autocomplete

Your IDE will provide full autocomplete and type hints for all generated functions and types.

✅ Automatic Updates

When you change your Rust commands, just re-run the generator to get updated TypeScript bindings.

TypeScript Compatibility

The generated TypeScript code is compatible with modern TypeScript environments and follows current best practices.

Version Requirements

  • TypeScript 3.7+ (for optional chaining support)
  • ES2018+ compilation target
  • Zod 3.x (when using Zod validation)

Generated Code Features

The generated TypeScript code uses modern language features:

  • ES Modules: import/export statements
  • Async/Await: All command functions are async
  • Union Types: string | null, optional properties
  • Generic Types: Array<T>, Promise<T>, Record<K, V>
  • Tuple Types: [string, number] for Rust tuples
  • Template Literal Types: Advanced string manipulation (when needed)

TypeScript Configuration

Ensure your tsconfig.json is compatible with the generated code:

{
  "compilerOptions": {
    "target": "ES2018",
    "module": "ESNext",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true
  }
}

Generated Type Mappings

Rust Type Generated TypeScript Notes
String, &str string Basic string types
i32, f64, etc. number All numeric types → number
bool boolean Boolean type
() void Unit type
Option<T> T | null Nullable types
Vec<T> T[] Arrays
HashMap<K, V> Map<K, V> Map type
BTreeMap<K, V> Map<K, V> Consistent with HashMap
HashSet<T> T[] Arrays for JSON compatibility
(T, U) [T, U] Tuple types
Result<T, E> T Errors handled by Tauri runtime

API Reference

CLI Commands

cargo tauri-typegen generate [OPTIONS]

OPTIONS:
    -p, --project-path <PATH>      Path to the Tauri project source directory
                                  [default: ./src-tauri]
    -o, --output-path <PATH>       Output path for generated TypeScript files  
                                  [default: ./src/generated]
    -v, --validation <LIBRARY>     Validation library to use
                                  [default: zod] [possible values: zod, none]
        --verbose                 Enable verbose output
        --visualize-deps          Generate dependency graph visualization
    -c, --config <CONFIG_FILE>     Configuration file path
    -h, --help                    Print help information

Library Usage (Advanced)

For programmatic usage in build scripts:

use tauri_plugin_typegen::interface::{GenerateConfig, generate_from_config};

let config = GenerateConfig {
    project_path: "./src-tauri".to_string(),
    output_path: "./src/generated".to_string(),
    validation_library: "zod".to_string(),
    verbose: Some(true),
    visualize_deps: Some(false),
    ..Default::default()
};

let files = generate_from_config(&config)?;

Configuration Options

Validation Libraries

  • zod - Generates Zod schemas with validation
  • none - No validation schemas generated, only TypeScript types

Example Project Structure

my-tauri-app/
├── src-tauri/
│   ├── src/
│   │   ├── commands/
│   │   │   ├── user.rs      # Contains #[command] functions
│   │   │   └── mod.rs
│   │   └── lib.rs
│   └── Cargo.toml
├── src/
│   ├── generated/           # Generated by this plugin
│   │   ├── types.ts
│   │   ├── schemas.ts
│   │   ├── commands.ts
│   │   └── index.ts
│   └── App.tsx
└── package.json

Development

Building the Plugin

cargo build

Running Tests

cargo test

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests if applicable
  5. Submit a pull request

License

This project is licensed under the MIT License.

Commit count: 0

cargo fmt