Claude
Skills
Sign in
Back

embedding-tauri-sidecars

Included with Lifetime
$97 forever

Teaches the assistant how to embed and execute external binaries (sidecars) in Tauri applications, including configuration, cross-platform executable naming, and Rust/JavaScript APIs for spawning sidecar processes.

Backend & APIs

What this skill does


# Tauri Sidecars: Embedding External Binaries

This skill covers embedding and executing external binaries (sidecars) in Tauri applications, including configuration, cross-platform considerations, and execution from Rust and JavaScript.

## Overview

Sidecars are external binaries embedded within Tauri applications to extend functionality or eliminate the need for users to install dependencies. They can be executables written in any programming language.

**Common Use Cases:**
- Python CLI applications packaged with PyInstaller
- Go or Rust compiled binaries for specific tasks
- Node.js applications bundled as executables
- API servers or background services

## Plugin Dependency

Sidecars require the shell plugin:

**Cargo.toml:**
```toml
[dependencies]
tauri-plugin-shell = "2"
```

**Register in main.rs:**
```rust
fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

**Frontend package:**
```bash
npm install @tauri-apps/plugin-shell
```

## Configuration

### Registering Sidecars

Configure sidecars in `tauri.conf.json` under `bundle.externalBin`. Paths are relative to `src-tauri`:

```json
{
  "bundle": {
    "externalBin": [
      "binaries/my-sidecar",
      "../external/processor"
    ]
  }
}
```

**Important:** The path is a stem. Tauri appends the target triple suffix at build time.

### Cross-Platform Binary Naming

Each sidecar requires platform-specific variants with target triple suffixes:

| Platform | Architecture | Required Filename |
|----------|--------------|-------------------|
| Linux | x86_64 | `my-sidecar-x86_64-unknown-linux-gnu` |
| Linux | ARM64 | `my-sidecar-aarch64-unknown-linux-gnu` |
| macOS | Intel | `my-sidecar-x86_64-apple-darwin` |
| macOS | Apple Silicon | `my-sidecar-aarch64-apple-darwin` |
| Windows | x86_64 | `my-sidecar-x86_64-pc-windows-msvc.exe` |

**Determine your target triple:**
```bash
rustc --print host-tuple    # Rust 1.84.0+
rustc -Vv | grep host       # Older versions
```

### Directory Structure

```
src-tauri/
  binaries/
    my-sidecar-x86_64-unknown-linux-gnu
    my-sidecar-aarch64-apple-darwin
    my-sidecar-x86_64-apple-darwin
    my-sidecar-x86_64-pc-windows-msvc.exe
  tauri.conf.json
  src/main.rs
```

## Executing Sidecars from Rust

### Basic Execution

```rust
use tauri_plugin_shell::ShellExt;

#[tauri::command]
async fn run_sidecar(app: tauri::AppHandle) -> Result<String, String> {
    let output = app
        .shell()
        .sidecar("my-sidecar")
        .map_err(|e| e.to_string())?
        .output()
        .await
        .map_err(|e| e.to_string())?;

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    } else {
        Err(String::from_utf8_lossy(&output.stderr).to_string())
    }
}
```

**Note:** Pass only the filename to `sidecar()`, not the full path from configuration.

### With Arguments

```rust
#[tauri::command]
async fn process_file(app: tauri::AppHandle, file_path: String) -> Result<String, String> {
    let output = app
        .shell()
        .sidecar("processor")
        .map_err(|e| e.to_string())?
        .args(["--input", &file_path, "--format", "json"])
        .output()
        .await
        .map_err(|e| e.to_string())?;

    Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
```

### Spawning Long-Running Processes

For sidecars that run continuously (API servers, watchers):

```rust
use tauri_plugin_shell::{ShellExt, process::CommandEvent};

#[tauri::command]
async fn start_server(app: tauri::AppHandle) -> Result<u32, String> {
    let (mut rx, child) = app
        .shell()
        .sidecar("api-server")
        .map_err(|e| e.to_string())?
        .args(["--port", "8080"])
        .spawn()
        .map_err(|e| e.to_string())?;

    let pid = child.pid();

    tauri::async_runtime::spawn(async move {
        while let Some(event) = rx.recv().await {
            match event {
                CommandEvent::Stdout(line) => println!("{}", String::from_utf8_lossy(&line)),
                CommandEvent::Stderr(line) => eprintln!("{}", String::from_utf8_lossy(&line)),
                CommandEvent::Terminated(payload) => {
                    println!("Terminated: {:?}", payload.code);
                    break;
                }
                _ => {}
            }
        }
    });

    Ok(pid)
}
```

### Managing Sidecar Lifecycle

```rust
use std::sync::Mutex;
use tauri::State;
use tauri_plugin_shell::{ShellExt, process::CommandChild};

struct SidecarState {
    child: Mutex<Option<CommandChild>>,
}

#[tauri::command]
async fn start_sidecar(app: tauri::AppHandle, state: State<'_, SidecarState>) -> Result<(), String> {
    let (_, child) = app.shell().sidecar("service").map_err(|e| e.to_string())?
        .spawn().map_err(|e| e.to_string())?;
    *state.child.lock().unwrap() = Some(child);
    Ok(())
}

#[tauri::command]
async fn stop_sidecar(state: State<'_, SidecarState>) -> Result<(), String> {
    if let Some(child) = state.child.lock().unwrap().take() {
        child.kill().map_err(|e| e.to_string())?;
    }
    Ok(())
}
```

## Executing Sidecars from JavaScript

### Permission Configuration

Grant shell execution permissions in `src-tauri/capabilities/default.json`:

```json
{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "windows": ["main"],
  "permissions": [
    "core:default",
    {
      "identifier": "shell:allow-execute",
      "allow": [{ "name": "binaries/my-sidecar", "sidecar": true }]
    }
  ]
}
```

### Basic Execution

```typescript
import { Command } from '@tauri-apps/plugin-shell';

async function runSidecar(): Promise<string> {
  const command = Command.sidecar('binaries/my-sidecar');
  const output = await command.execute();
  if (output.code === 0) return output.stdout;
  throw new Error(output.stderr);
}
```

### With Arguments

```typescript
async function processFile(filePath: string): Promise<string> {
  const command = Command.sidecar('binaries/processor', [
    '--input', filePath, '--format', 'json'
  ]);
  const output = await command.execute();
  return output.stdout;
}
```

### Handling Streaming Output

```typescript
import { Command, Child } from '@tauri-apps/plugin-shell';

async function runWithStreaming(): Promise<Child> {
  const command = Command.sidecar('binaries/long-task');

  command.on('close', (data) => console.log(`Finished: ${data.code}`));
  command.on('error', (error) => console.error(error));
  command.stdout.on('data', (line) => console.log(line));
  command.stderr.on('data', (line) => console.error(line));

  return await command.spawn();
}
```

### Managing Long-Running Processes

```typescript
let serverProcess: Child | null = null;

async function startServer(): Promise<number> {
  const command = Command.sidecar('binaries/api-server', ['--port', '8080']);
  command.stdout.on('data', console.log);
  serverProcess = await command.spawn();
  return serverProcess.pid;
}

async function stopServer(): Promise<void> {
  if (serverProcess) {
    await serverProcess.kill();
    serverProcess = null;
  }
}
```

## Argument Validation

Configure argument validation in capabilities:

```json
{
  "identifier": "shell:allow-execute",
  "allow": [{
    "name": "binaries/my-sidecar",
    "sidecar": true,
    "args": [
      "-o",
      "--verbose",
      { "validator": "\\S+" }
    ]
  }]
}
```

**Argument types:**
- **Static string**: Exact match required (`-o`, `--verbose`)
- **Validator object**: Regex pattern for dynamic values
- **`true`**: Allow any argument (use with caution)

## Cross-Platform Considerations

### Building Platform-Specific Binaries

**Rust sidecars:**
```bash
cargo build --release --target x86_64-unknown-linux-gnu
cp target/x86_64-unknown-linux-gnu/release/my-tool \
   src-tauri/binaries/my-tool-x86_64-unknown-linux-gnu
```

**Python with PyInstaller:**
```bash
pyinstaller --o

Related in Backend & APIs