Getting Started with @zubridge/tauri
September 7, 2025 ยท View on GitHub
This guide will walk you through setting up Zubridge in your Tauri application using the official tauri-plugin-zubridge plugin.
Installation
1. Backend (Rust)
Add the plugin to your Cargo.toml:
[dependencies]
tauri-plugin-zubridge = "0.1.0"
serde = { version = "1.0", features = ["derive"] }
2. Frontend (JavaScript/TypeScript)
Install the frontend library and its peer dependencies:
# Using npm
npm install @zubridge/tauri zustand @tauri-apps/api
# Using yarn
yarn add @zubridge/tauri zustand @tauri-apps/api
# Using pnpm
pnpm add @zubridge/tauri zustand @tauri-apps/api
Core Concepts
Zubridge connects your Tauri Rust backend with your frontend using a plugin-based architecture:
- Rust Backend State: Your application's authoritative state lives in your Rust backend, managed by the plugin.
- StateManager: You implement this trait to define how state is managed and actions are processed.
- Frontend Hooks: Access state with
useZubridgeStoreand dispatch actions withuseZubridgeDispatch. - Automatic Synchronization: The plugin handles all the communication between frontend and backend.
Step-by-Step Setup
1. Define Your State in Rust
First, define your application state structure:
use serde::{Deserialize, Serialize};
// Your application state
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AppState {
counter: i32,
theme: ThemeState,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ThemeState {
is_dark: bool,
}
impl Default for AppState {
fn default() -> Self {
Self {
counter: 0,
theme: ThemeState { is_dark: false },
}
}
}
2. Implement the StateManager Trait
Create a struct that will manage your state and implement the StateManager trait:
use std::sync::Mutex;
use tauri_plugin_zubridge::{StateManager, ZubridgeAction};
struct AppStateManager {
state: Mutex<AppState>,
}
impl AppStateManager {
fn new() -> Self {
Self {
state: Mutex::new(AppState::default()),
}
}
}
impl StateManager for AppStateManager {
// Return the current state
fn get_state(&self) -> serde_json::Value {
let state = self.state.lock().unwrap();
serde_json::to_value(&*state).unwrap()
}
// Process actions from the frontend
fn process_action(&self, action: &ZubridgeAction) -> Result<(), String> {
let mut state = self.state.lock().unwrap();
match action.action_type.as_str() {
"INCREMENT" => {
state.counter += 1;
println!("Counter incremented to {}", state.counter);
Ok(())
},
"DECREMENT" => {
state.counter -= 1;
println!("Counter decremented to {}", state.counter);
Ok(())
},
"THEME:TOGGLE" => {
state.theme.is_dark = !state.theme.is_dark;
println!("Theme toggled to {}", if state.theme.is_dark { "dark" } else { "light" });
Ok(())
},
_ => Err(format!("Unknown action: {}", action.action_type)),
}
}
}
3. Create and Register the Plugin
Create a function to instantiate the plugin and register it with your Tauri application:
use tauri::{plugin::TauriPlugin, Runtime};
use tauri_plugin_zubridge::ZubridgePlugin;
// Create a plugin setup function
pub fn zubridge<R: Runtime>() -> TauriPlugin<R> {
let state_manager = AppStateManager::new();
ZubridgePlugin::new(state_manager)
}
// In main.rs
fn main() {
tauri::Builder::default()
.plugin(zubridge())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
4. Initialize the Bridge in Frontend
At the root of your application, initialize the bridge with Tauri's invoke and listen functions:
// main.tsx (React example)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { initializeBridge } from '@zubridge/tauri';
import { invoke } from '@tauri-apps/api/core'; // For Tauri v2
import { listen } from '@tauri-apps/api/event';
// For Tauri v1, use:
// import { invoke } from '@tauri-apps/api/tauri';
// import { listen } from '@tauri-apps/api/event';
// Initialize Zubridge once before rendering
initializeBridge({ invoke, listen });
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
5. Use the Hooks in Your Components
Now you can use the hooks to access state and dispatch actions:
// Counter.tsx
import React from 'react';
import { useZubridgeStore, useZubridgeDispatch } from '@zubridge/tauri';
function Counter() {
// Access state with a selector
const counter = useZubridgeStore((state) => state.counter);
const isDark = useZubridgeStore((state) => state.theme.is_dark);
// Get bridge status
const status = useZubridgeStore((state) => state.__bridge_status);
// Get dispatch function
const dispatch = useZubridgeDispatch();
if (status !== 'ready') {
return <div>Loading...</div>;
}
return (
<div className={isDark ? 'dark-theme' : 'light-theme'}>
<h1>Counter: {counter}</h1>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
<button onClick={() => dispatch({ type: 'THEME:TOGGLE' })}>Toggle Theme ({isDark ? 'Dark' : 'Light'})</button>
</div>
);
}
export default Counter;
Framework Compatibility
Despite the use prefix in hook names, Zubridge works with any JavaScript framework:
React
import { useZubridgeStore, useZubridgeDispatch } from '@zubridge/tauri';
function Counter() {
const counter = useZubridgeStore((state) => state.counter);
const dispatch = useZubridgeDispatch();
return (
<div>
<h1>Counter: {counter}</h1>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
</div>
);
}
Vue
<script setup>
import { computed, onMounted } from 'vue';
import { useZubridgeStore, useZubridgeDispatch } from '@zubridge/tauri';
const counter = computed(() => useZubridgeStore((state) => state.counter));
const dispatch = useZubridgeDispatch();
const increment = () => dispatch({ type: 'INCREMENT' });
</script>
<template>
<div>
<h1>Counter: {{ counter }}</h1>
<button @click="increment">+</button>
</div>
</template>
Vanilla JavaScript
import { useZubridgeStore, useZubridgeDispatch } from '@zubridge/tauri';
// Get initial state
const counterElement = document.getElementById('counter');
const updateUI = () => {
const counter = useZubridgeStore.getState().counter;
counterElement.textContent = counter;
};
// Subscribe to changes
const unsubscribe = useZubridgeStore.subscribe(updateUI);
// Setup action handlers
const dispatch = useZubridgeDispatch();
document.getElementById('increment').addEventListener('click', () => {
dispatch({ type: 'INCREMENT' });
});
// Clean up on page unload
window.addEventListener('beforeunload', () => {
unsubscribe();
});
Next Steps
- Check out the API Reference for detailed documentation
- Learn about Backend Contract for advanced customization
- See a complete example in the Tauri Example App