Understanding Sidecars

What sidecar binaries are in Tauri v2, the problems they solve, and how the bundling and naming system works before you start adding your own.

A Tauri application ships as a single package, but sometimes you need more than what the Rust core can provide. You might have a Python script that crunches data, a Node.js server that handles real-time communication, or a compiled tool like ffmpeg that your app relies on. Rather than asking users to install those dependencies separately, Tauri lets you bundle extra executables right inside your app. These bundled programs are called sidecars.

The term comes from the idea of a motorcycle sidecar — it runs alongside the main vehicle but is not part of it. In a Tauri app, the sidecar binary is a completely separate process, written in any language, that you manage from your Rust backend or your frontend JavaScript. It does not share memory with your app and communicates through standard input, output, and exit codes.

What a Sidecar Actually Is

A sidecar is any self-contained executable file that you place inside your Tauri project and instruct the bundler to include in the final application package. When a user installs your app, that executable is placed inside the app bundle and can be launched at runtime by your Tauri code. The executable itself can be written in any language — Python bundled with PyInstaller, a Go binary, a Node.js runtime with a bundled script, or a system tool like sqlite3.

Unlike a library or a plugin, a sidecar does not link into your Rust code. It runs as a child process. You start it, optionally pass arguments, read its output, and kill it when you are done. This separation is both powerful and dangerous: you get the full flexibility of any external program, but you also take on the responsibility of managing its lifecycle. Data files that are not executables belong in resources instead.

Sidecars Are Not Plugins:

Tauri plugins are Rust crates that integrate directly with the Tauri core and run in the same process. Sidecars are separate operating system processes. Use a plugin when you want tight integration with low overhead; use a sidecar when the functionality you need already exists as a standalone executable or is easier to build in another language.

Why Tauri Apps Use Sidecars

Desktop applications often depend on tools or runtimes that a user may not have installed. A video converter needs ffmpeg, a data science tool might need a Python environment with pandas, and a developer tool might shell out to node. If your application calls Command::new("python") in Rust or uses a shell command from the frontend, it will fail on any machine where Python is not installed or is not on the system PATH.

Sidecars solve this by embedding the dependency directly. The user installs your app and everything works without a separate installer or a multi-step setup guide. This makes your application self-contained and predictable — the version of the tool you ship is the version that runs, regardless of what is on the user’s machine.

A secondary benefit is that you can use languages other than Rust for parts of your application logic. If your team has deep Python expertise for data processing, you can keep that code in Python, bundle it as a sidecar, and call it from Tauri. The frontend experience remains a single app; the language boundary is invisible to the user.

When Sidecars Make Sense

Not every dependency should be a sidecar. The binary adds to your app’s download size, and launching a separate process is slower than calling a native function. Sidecars are a good fit when:

  • The dependency is a precompiled CLI tool that would be difficult or impossible to rewrite in Rust (for example, ffmpeg, imagemagick, or a proprietary binary).
  • You need to run a long-lived background service, such as a local API server or a WebSocket process, that continues while the UI is open.
  • You have an existing codebase in another language and want to reuse it without a full rewrite.
  • The tool requires a specific runtime (Node.js, Python, Java) and you do not want to force the user to install that runtime.

If the functionality can be achieved with a Rust crate, a Tauri plugin, or native Web API calls, those are usually lighter and simpler. Sidecars are for when those options are not enough.

The Tauri Bundling System for Sidecars

Tauri needs to know which files to include and how to locate them at runtime. The first step is declaring your sidecar in the bundle section of tauri.conf.json.

src-tauri/tauri.conf.json
{
  "bundle": {
    "externalBin": [
      "binaries/my-sidecar"
    ]
  }
}

The path you list is relative to the src-tauri directory. Tauri will look for the actual binary file with a specific naming convention: the name you provide, followed by a hyphen and the target triple of the current platform. For the configuration above, Tauri expects to find files like these:

src-tauri/binaries/my-sidecar-aarch64-apple-darwin

The target triple encodes the CPU architecture, the vendor, and the operating system. Tauri uses this convention to automatically select the correct binary for the platform your app is running on. When you call app.shell().sidecar("my-sidecar") in Rust or Command.sidecar("binaries/my-sidecar") in JavaScript, Tauri strips the suffix and finds the right file.

To determine the triple for your own machine, run this command in a terminal:

rustc --print host-tuple

On an Apple Silicon Mac, this prints aarch64-apple-darwin. On a 64-bit Windows machine, it prints x86_64-pc-windows-msvc.

The Target Triple Is Not Optional:

If you place a binary named my-sidecar without a suffix next to the configuration file, Tauri will not find it. The filename must include the target triple. This is the most common mistake when setting up a sidecar for the first time. Always rename your binary to match the expected pattern before you try to build or run the app.

During development (tauri dev), the binary with the triple suffix is copied to the same directory as your compiled Rust executable (inside target/debug/). When you build for production (tauri build), Tauri bundles the binary inside the final .app (macOS), .msi/.exe (Windows), or AppImage/deb (Linux) and strips the suffix — the file inside the bundle is named just my-sidecar. Your code does not need to handle this difference because the sidecar() API resolves the path for you.

A Minimal Invocation from Rust and JavaScript

Running a sidecar requires the tauri-plugin-shell plugin. Add it to your Cargo.toml and to your frontend dependencies. The following snippet shows the basic pattern on both sides — the Rust code spawns the process, and the JavaScript code can call that command from the frontend.

Rust side (inside a Tauri command):

src-tauri/src/main.rs
use tauri_plugin_shell::ShellExt;
use tauri_plugin_shell::process::CommandEvent;
use tauri::Emitter;
#[tauri::command]
async fn run_my_sidecar(app: tauri::AppHandle) -> Result<(), String> {
    let sidecar_command = app
        .shell()
        .sidecar("my-sidecar")
        .map_err(|e| e.to_string())?;
    let (mut rx, _child) = sidecar_command
        .spawn()
        .map_err(|e| e.to_string())?;
    tauri::async_runtime::spawn(async move {
        while let Some(event) = rx.recv().await {
            if let CommandEvent::Stdout(line_bytes) = event {
                let line = String::from_utf8_lossy(&line_bytes);
                app.emit("sidecar-output", line.to_string())
                    .expect("failed to emit event");
            }
        }
    });
    Ok(())
}

This code obtains the sidecar command from the app handle, spawns it as a child process, and starts listening for output from the process’s stdout. Every line printed by the sidecar is emitted to the frontend as a sidecar-output event. The _child handle should be stored if you need to kill the process later or write to its stdin — a topic covered in the later sections on running sidecars.

JavaScript side (React frontend):

src/App.jsx
import { Command } from '@tauri-apps/plugin-shell';
async function runSidecarFromJs() {
    const command = Command.sidecar('binaries/my-sidecar');
    const output = await command.execute();
    console.log('Sidecar stdout:', output.stdout);
}

The JavaScript Command.sidecar() method takes the same identifier that you declared in externalBin (including the binaries/ prefix) and runs it. The execute() call waits for the process to finish. For long-running processes, you would use spawn() instead and listen for events — similar to the Rust pattern.

Permissions Are Required Before Anything Runs:

Neither the Rust nor the JavaScript example will work unless the sidecar has been granted permission in the capability file. Without this, Tauri will block the process from spawning. Add an entry like the following to your src-tauri/capabilities/default.json:

{
  "identifier": "shell:allow-spawn",
  "allow": [
    {
      "name": "binaries/my-sidecar",
      "sidecar": true
    }
  ]
}

The name must match the path used in Command.sidecar() and in externalBin. Forgetting to update the capability is a common source of silent failures.

How Beginners Should Think About Sidecars

The simplest mental model is to treat a sidecar as a black box that takes input and produces output. You give it arguments when you start it, you read what it prints, and you shut it down when you no longer need it. You do not need to understand the internals of the binary — just its expected interface.

Think of it like hiring a contractor: you give them a task, they work in their own office (a separate process), and they hand you the result through a door (stdout). The door can also deliver progress updates (line by line output) or error messages (stderr). Your job is to open the door correctly and to make sure the contractor leaves when the job is done.

Common Misconceptions

A few points that newcomers often misunderstand:

  • Sidecars are not sandboxed. The binary runs with the same operating system permissions as your Tauri app. A Python sidecar that calls os.system("rm -rf /") can cause real damage. Only bundle binaries you trust and validate their behavior.
  • The binary must be compiled for the target platform. You cannot bundle a Windows .exe and expect it to run on macOS. Each platform needs its own build of the sidecar, placed with the correct target triple filename.
  • spawn() returning successfully does not mean the program is healthy. The OS can start the process, but the binary might crash immediately due to a missing dependency or a bad config. Always verify that the sidecar is actually doing its job — for example, by reading its stdout or checking an API it exposes.

When Everything Works:

If you have placed a correctly named binary, added the permission, and called Command.sidecar(...).execute(), you should see the binary's output appear in your JavaScript console or your Rust log. A quick test sidecar — a shell script that echoes "hello from sidecar" — can confirm the entire chain is wired up before you integrate the real tool.

The Road Ahead

Understanding the role sidecars play and how Tauri names and locates them is the foundation. The key takeaway from this page is the naming convention and the permission model — everything else builds on top of that.