src/content/docs/develop/sidecar.mdx
You may need to embed external binaries to add additional functionality to your application or prevent users from installing additional dependencies (e.g., Node.js or Python). We call this binary a sidecar.
Binaries are executables written in any programming language. Common use cases are Python CLI applications or API servers bundled using pyinstaller.
To bundle the binaries of your choice, you can add the externalBin property to the tauri > bundle object in your tauri.conf.json.
The externalBin configuration expects a list of strings targeting binaries either with absolute or relative paths.
Here is a Tauri configuration snippet to illustrate a sidecar configuration:
{
"bundle": {
"externalBin": [
"/absolute/path/to/sidecar",
"../relative/path/to/binary",
"binaries/my-sidecar"
]
}
}
:::note
The relative paths are relative to the tauri.conf.json file which is in the src-tauri directory.
So binaries/my-sidecar would represent <PROJECT ROOT>/src-tauri/binaries/my-sidecar.
:::
To make the external binary work on each supported architecture, a binary with the same name and a -$TARGET_TRIPLE suffix must exist on the specified path.
For instance, "externalBin": ["binaries/my-sidecar"] requires a src-tauri/binaries/my-sidecar-x86_64-unknown-linux-gnu executable on Linux or src-tauri/binaries/my-sidecar-aarch64-apple-darwin on Mac OS with Apple Silicon.
You can find your current platform's -$TARGET_TRIPLE suffix by running the following command:
rustc --print host-tuple
This directly outputs your host's target triple (e.g., x86_64-unknown-linux-gnu or aarch64-apple-darwin).
:::note
The --print host-tuple flag was added in Rust 1.84.0. If you're using an older version, you'll need to parse the output of rustc -Vv instead:
# Unix (Linux/macOS)
rustc -Vv | grep host | cut -f2 -d' '
# Windows PowerShell
rustc -Vv | Select-String "host:" | ForEach-Object {$_.Line.split(" ")[1]}
:::
Here's a Node.js script to append the target triple to a binary:
import { execSync } from 'child_process';
import fs from 'fs';
const extension = process.platform === 'win32' ? '.exe' : '';
const targetTriple = execSync('rustc --print host-tuple').toString().trim();
if (!targetTriple) {
console.error('Failed to determine platform target triple');
}
fs.renameSync(
`src-tauri/binaries/sidecar${extension}`,
`src-tauri/binaries/sidecar-${targetTriple}${extension}`
);
Note that this script will not work if you compile for a different architecture than the one its running on, so only use it as a starting point for your own build scripts.
:::note Please follow the shell plugin guide first to set up and initialize the plugin correctly. Without the plugin being initialized and configured the example won't work. :::
On the Rust side, import the tauri_plugin_shell::ShellExt trait and call the shell().sidecar() function on the AppHandle:
use tauri_plugin_shell::ShellExt;
use tauri_plugin_shell::process::CommandEvent;
use tauri::Emitter;
let sidecar_command = app.shell().sidecar("my-sidecar").unwrap();
let (mut rx, mut child) = sidecar_command
.spawn()
.expect("Failed to spawn sidecar");
tauri::async_runtime::spawn(async move {
// read events such as stdout
while let Some(event) = rx.recv().await {
if let CommandEvent::Stdout(line_bytes) = event {
let line = String::from_utf8_lossy(&line_bytes);
app
.emit("message", Some(format!("'{}'", line)))
.expect("failed to emit event");
// write to stdin
child.write("message from Rust\n".as_bytes()).unwrap();
}
}
});
:::note
The sidecar() function expects just the filename, NOT the whole path configured in the externalBin array.
Given the following configuration:
{
"bundle": {
"externalBin": ["binaries/app", "my-sidecar", "../scripts/sidecar"]
}
}
The appropriate way to execute the sidecar is by calling app.shell().sidecar(name) where name is either "app", "my-sidecar" or "sidecar"
instead of "binaries/app" for instance.
:::
You can place this code inside a Tauri command to easily pass the AppHandle or you can store a reference to the AppHandle in the builder script to access it elsewhere in your application.
When running the sidecar, Tauri requires you to give the sidecar permission to run the execute or spawn method on the child process. To grant this permission, go to the file <PROJECT ROOT>/src-tauri/capabilities/default.json and add the section below to the permissions array. Don't forget to name your sidecar according to the relative path mentioned earlier.
{
"permissions": [
"core:default",
{
"identifier": "shell:allow-execute",
"allow": [
{
"name": "binaries/app",
"sidecar": true
}
]
}
]
}
:::note
The shell:allow-execute identifier is used because the sidecar's child process will be started using the command.execute() method. To run it with command.spawn(), you need to change the identifier to shell:allow-spawn or add another entry to the array with the same structure as the one above, but with the identifier set to shell:allow-spawn.
:::
In the JavaScript code, import the Command class from the @tauri-apps/plugin-shell module and use the sidecar static method.
import { Command } from '@tauri-apps/plugin-shell';
const command = Command.sidecar('binaries/my-sidecar');
const output = await command.execute();
:::note
The string provided to Command.sidecar must match one of the strings defined in the externalBin configuration array.
:::
You can pass arguments to Sidecar commands just like you would for running normal Command.
Arguments can be either static (e.g. -o or serve) or dynamic (e.g. <file_path> or localhost:<PORT>). A value of true will allow any arguments to be passed to the command. false will disable all arguments. If neither true or false is set, you define the arguments in the exact order in which you'd call them. Static arguments are defined as-is, while dynamic arguments can be defined using a regular expression.
First, define the arguments that need to be passed to the sidecar command in src-tauri/capabilities/default.json:
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
{
"identifier": "shell:allow-execute",
"allow": [
{
"args": [
"arg1",
"-a",
"--arg2",
{
"validator": "\\S+"
}
],
"name": "binaries/my-sidecar",
"sidecar": true
}
]
}
]
}
:::note
If you are migrating from Tauri v1, the migrate command in Tauri v2 CLI should take care of this for you. Read Automated Migration for more.
:::
Then, to call the sidecar command, simply pass in all the arguments as an array.
In Rust:
use tauri_plugin_shell::ShellExt;
#[tauri::command]
async fn call_my_sidecar(app: tauri::AppHandle) {
let sidecar_command = app
.shell()
.sidecar("my-sidecar")
.unwrap()
.args(["arg1", "-a", "--arg2", "any-string-that-matches-the-validator"]);
let (mut _rx, mut _child) = sidecar_command.spawn().unwrap();
}
In JavaScript:
import { Command } from '@tauri-apps/plugin-shell';
// notice that the args array matches EXACTLY what is specified in `capabilities/default.json`.
const command = Command.sidecar('binaries/my-sidecar', [
'arg1',
'-a',
'--arg2',
'any-string-that-matches-the-validator',
]);
const output = await command.execute();