GitPedia

Tauri plugin python

Tauri plugin to run python code in the backend instead of rust

From marcomq·Updated June 22, 2026·View on GitHub·

This [tauri](https://v2.tauri.app/) v2 plugin is supposed to make it easy to use Python as backend code. It can use either [PyO3](https://pyo3.rs) (the default) or [RustPython](https://github.com/RustPython/RustPython) as the interpreter to call python from rust. The project is written primarily in Rust, distributed under the MIT License license, first published in 2024. Key topics include: python, rust, tauri, tauri-plugin, tauri2.

Latest release: v0.3.9
June 19, 2026View Changelog →

Tauri Plugin Python

This tauri v2 plugin is supposed to make it easy to use Python as backend code.
It can use either PyO3 (the default) or RustPython as the interpreter to call python from rust.

Requirements: Tauri v2, a recent stable Rust toolchain, and (for the default PyO3 backend) a CPython
interpreter available when you build and run. The example apps are the canonical, always-current
reference if anything below drifts out of date.

Quick start

From an existing Tauri v2 app (or start from the example apps). This uses the default PyO3
backend and works immediately in tauri dev:

sh
npm run tauri add python

Create src-tauri/src-python/main.py:

python
# src-tauri/src-python/main.py _tauri_plugin_functions = ["greet_python"] # make this callable from the UI def greet_python(name): return f"Hello {name} from Python"

Make sure Python is bundled as a resource in src-tauri/tauri.conf.json:

json
"bundle": { "resources": ["src-python/"] }

Call it from your frontend:

javascript
import { callFunction } from 'tauri-plugin-python-api' console.log(await callFunction("greet_python", ["world"])) // "Hello world from Python"

That's it for development. Before you ship a release, read Deployment — especially the
resource-bundling rules and (for PyO3) how to provide libpython on the target machine. If you want a
single self-contained binary with no Python on the target, see RustPython.

Choosing an interpreter: PyO3 vs. RustPython

This is the most important decision when using the plugin, so please read this before you start.

PyO3 / CPython (default)RustPython
Cargo featurepyo3 (enabled by default)rustpython
Interpreterthe real CPythona Python interpreter written in Rust
Library compatibilityfull – anything that runs on CPython, incl. C extensions (numpy, pandas, …)limited – pure-Python only, and a meaningful part of the standard library is missing or incomplete
Needs Python on the target machineyes (libpython must be available, see below)no – linked statically into your binary
Speedfast (CPython)slower
Deploymentmore involved (ship/embed libpython)trivial (single binary)

[!IMPORTANT]
RustPython is convenient for deployment but it is not a complete Python. It is not a drop-in CPython.
Several standard-library modules are missing, incomplete or behave differently, and some text codecs
(e.g. latin-1) and modules such as inspect may not be importable. Code – including the plugin's own
optional registerFunction argument-count check – that relies on these will silently degrade or fail on
RustPython but work fine on PyO3. If a library or stdlib feature doesn't work under RustPython, switch
to the PyO3 backend before assuming it's a bug.

The plugin uses PyO3 by default because of its full compatibility. The trade-off is that PyO3 needs
libpython to be available for the target platform, which can be more complicated to deploy (see
Deployment), especially on mobile. Pick RustPython when you want a single self-contained
binary and your Python code stays within what RustPython supports.

The plugin reads by default the file src-tauri/src-python/main.py during
startup and runs it immediately. Make sure to add all your python source as tauri resource,
so it is shipped together with your production binaries. Python functions are all registered during plugin initialization
and can get called during application workflow.

PlatformSupported
Linux
Windows
MacOS
Androidx*
iOS✓*

x* There is currently a known issue on tauri+android that prevents reading files.
https://github.com/tauri-apps/tauri/issues/11823
So python code cannot be read on android right now. Android is going to be supported as soon as reading resource files will be fixed.

✓* Linux, Windows and MacOS support PyO3 and RustPython as interpreter. Android and iOS
currently only support RustPython.
Android and iOS might also be able to run with PyO3 in theory but would require to have CPython
to be compiled for the target platform. I still need to figure out how to
cross compile python and PyO3 for iOS and Android. Ping me if you know how to do that.

You can use this plugin for fast prototypes or for (early) production code.
It might be possible that you want to use some python library or code that
is not available for rust yet.
In case that you want to ship production software packages, you need
to make sure to also ship all your python code. If you use PyO3, you also need to ship libpython too.

Switch from PyO3 to RustPython

PyO3 is the default. To get a self-contained binary that does not require Python on the target
machine, switch to the RustPython backend by disabling the default features and enabling rustpython
(keep venv if you rely on automatic .venv loading). Remember the limitations above.

toml
# src-tauri/Cargo.toml tauri-plugin-python = { version = "0.3", default-features = false, features = ["venv", "rustpython"] }

PyO3 / libpython deployment

Using PyO3 supports many more python libraries than RustPython as it is using CPython.
The trade-off is that PyO3 uses a shared libpython by default, which makes
local development easy but makes
deployment of releases more complicated.
Therefore, it may be recommended to either use pyoxidizer to embed libpython statically
or try to ship the dynamic libpython together with your application, for example as part
of the .venv. Check out the PyO3 documentation for additional support.

Example of how to embed libpython statically using PyOxidizer:

This has just been tested locally on MacOS. It may be possible that this is more complicated and requires additional steps on your environment.

Install pyoxidizer pip install pyoxidizer in a venv and run it on bash:

bash
pyoxidizer generate-python-embedding-artifacts src-tauri/target/pyembed

Then, add it to your cargo config:

toml
# src-tauri/.cargo/config.toml PYO3_CONFIG_FILE = { value = "target/pyembed/pyo3-build-config-file.txt", relative = true }

You can check if the release binary has some shared libpython references by running otool -L tauri_app on MacOs or ldd tauri_app on linux.

Example app

There is a sample Desktop application for Windows/Linux/MacOS using this plugin and vanilla
Javascript in examples/plain-javascript.

Add the plugin to an existing tauri application

These steps assume that you already have a basic tauri application available. Alternatively, you can immediately start with the application in "example" directory.

  • run npm run tauri add python
  • add src-tauri/src-python/main.py and modify it according to your needs, for example add
python
# src-tauri/src-python/main.py _tauri_plugin_functions = ["greet_python"] # make "greet_python" callable from UI def greet_python(rust_var): return str(rust_var) + " from python"
  • add "bundle": {"resources": [ "src-python/"], to tauri.conf.json so that python files are bundled with your application
  • add the plugin in your js, so
    • add import { callFunction } from 'tauri-plugin-python-api'
    • add outputEl.textContent = await callFunction("greet_python", [value]) to get the output of the python function greet_python with parameter of js variable value

Check the examples for alternative function calls and code sugar.

Tauri events and calling js from python is currently not supported yet. You would need to use rust for that.

Alternative manual plugin installation

  • $ cargo add tauri-plugin-python
  • $ npm install tauri-plugin-python-api
  • modify permissions:[] in src-tauri/capabilities/default.json and add "python:default"
  • add file src-tauri/src-python/main.py and add python code, for example:
python
# src-tauri/src-python/main.py def greet_python(rust_var): return str(rust_var) + " from python"
  • add .plugin(tauri_plugin_python::init_and_register(vec!["greet_python"])) to tauri::Builder::default(), usually in src-tauri/src/lib.rs. This will initialize the plugin and make the python function "greet_python" available from javascript.
  • add javascript for python plugin in the index.html file directly or somewhere in your javascript application. For vanilla javascript / iife, the modules can be found in window.__TAURI__.python. For modern javascript:
javascript
import { callFunction } from 'tauri-plugin-python-api' console.log(await callFunction("greet_python", ["input value"]))

→ this will call the python function "greet_python" with parameter "input value". Of course, you can just pass in any available javascript value. This should work with "boolean", "integer", "double", "string", "string[]", "double[]" parameter types.

Alternatively, to have more readable code:

javascript
import { call, registerJs } from 'tauri-plugin-python-api' registerJs("greet_python"); console.log(await call.greet_python("input value"));

Using a venv

Using a python venv is highly recommended when using pip dependencies (PyO3 backend).

Put the venv at src-python/.venv. The plugin auto-loads <src-python>/.venv/lib at startup, and
keeping the venv inside src-python is the configuration that reliably works both in tauri dev and in
production builds (a venv that lives outside src-python works in dev but commonly breaks in the bundled
app – this was the root cause for several "works in dev, not in prod" reports):

sh
# from your src-tauri folder python3 -m venv src-python/.venv source src-python/.venv/bin/activate # Windows: src-python\.venv\Scripts\activate.bat pip install <your_lib>

Then ship the venv lib (and include) as resources so they sit next to src-python in the bundle:

tauri.conf.json

json
"bundle": { "resources": { "src-python/": "src-python/" } }

Because .venv lives inside src-python/, the single "src-python/" entry bundles your Python code and
the venv together. (If you keep the venv elsewhere, map it explicitly into src-python/.venv/lib/ instead.)

[!TIP]
Pure-Python wheels are portable, but packages with compiled C extensions (numpy, pandas, pydantic-core, …)
are platform-specific. Build the venv on / for the same OS and architecture you ship to, ideally in CI
per target.

Debugging

When a Python call fails, the plugin returns the error to the frontend (it is the rejected value of the
callFunction / call.* / runPython / readVariable promise), so the quickest first step is:

javascript
try { await call.greet_python("input value"); } catch (err) { console.error(err); // contains the Python error message / traceback }

In addition, in development builds (tauri dev, i.e. any non-release build) the plugin prints the full
error – including the Python traceback – to stderr, prefixed with [tauri-plugin-python]. Watch the
terminal running tauri dev to see it. Release builds do not log, so nothing leaks to end users.

The error message is also prefixed with what the plugin was doing, e.g.
Error calling Python function 'greet_python': ... or
Cannot register 'greet_python': not found in Python (is it defined/imported in main.py?): ....

Common causes:

  • ... has not been registered yet – the function was never registered. Functions must be registered
    from Rust during plugin init (init_and_register(vec!["greet_python"])) or via the _tauri_plugin_functions
    list in main.py. The dynamic register command is disabled by default (see Security).
  • Cannot register '<name>': not found in Pythonmain.py didn't define/import that name, or main.py
    itself failed to load. Make sure src-python/main.py exists, is bundled as a resource (see
    Deployment), and runs without errors on its own.
  • A ModuleNotFoundError, missing-stdlib, codec (unknown encoding) or inspect error only on RustPython
    this is almost always a RustPython limitation, not a bug.
    Try the same code under the PyO3 backend to confirm, and prefer PyO3 if you need that library.
  • Missing pip dependency – ensure you are using a venv and that its lib folder is shipped
    as a resource next to src-python.

To sanity-check your Python independently of Tauri, run python3 src-tauri/src-python/main.py directly
(this validates it against CPython; RustPython may still differ – see the limitations above).

Deployment

The file src-python/main.py is always required for the plugin to work correctly. All Python files must be
included in the tauri resource files (tauri.conf.json), and your bundled resource layout must mirror your
local layout so imports resolve the same way.

The bundle.resources entry can be written as a list or as a source→target map; both are equivalent:

json
"bundle": { "resources": ["src-python/"] } // equivalent to: "bundle": { "resources": { "src-python/": "src-python/" } }

After building, confirm the files actually landed in the installed app's resource folder (this is where the plugin
looks at runtime):

PlatformResource folder (relative to the app)
macOS<App>.app/Contents/Resources/
Linuxnext to the executable, e.g. usr/lib/<app>/ (AppImage/deb)
Windowsnext to the .exe (the install dir)

You should find src-python/main.py (and .venv/ if you bundled one) there with the same structure as locally.

RustPython

No extra steps – the interpreter is linked statically into the binary, so the target machine needs no Python
installed
. Just bundle your src-python/ resources. (Remember the
stdlib limitations.)

PyO3 / CPython

PyO3 needs a libpython at runtime, which is what makes PyO3 deployment harder.

[!IMPORTANT]
The plugin has no runtime setting to "point" at a Python install. Which libpython is used is fixed when
PyO3 is linked at build time; at runtime the OS dynamic loader finds it via the normal library search path
(rpath, DYLD_LIBRARY_PATH/LD_LIBRARY_PATH, system locations). The only environment variable this plugin reads
is TAURI_PLUGIN_PYTHON_TIMEOUT_SECS. So "shipping a Python" means controlling the build link
and making the matching libpython resolvable next to your binary at runtime — not configuring the plugin.

Options, easiest first:

  1. Ship the shared libpython next to the executable / inside the .venv you bundle. Check what your binary is
    linked against with otool -L tauri_app (macOS) or ldd tauri_app (Linux); a line such as
    .../Python.framework/Versions/3.13/Python tells you which version and location the target must provide. Bundle
    that library (and set an rpath / co-locate it) so the loader finds it. The target machine then needs no separately
    installed Python.
  2. Build against an embeddable / standalone Python, then ship it. Point PyO3 at a self-contained Python
    (e.g. python-build-standalone or the Windows embeddable
    zip) at build time via a PYO3_CONFIG_FILE (see PyO3 build & distribution),
    bundle that same Python as a resource, and ensure its libpython is on the runtime search path. There is no
    plugin-side "load this interpreter" call — it is standard PyO3 packaging. For one community example of wiring an
    embedded Python into a Tauri app, see @Qingbao's tauri-agent
    (third-party, not tested by us – treat it as a starting point, not a recommendation).
  3. PyOxidizer / static linking is possible but currently fragile (unmaintained pyembed, pyo3-version
    conflicts). Not recommended unless you specifically need it.

In all cases, also bundle your venv (src-python/.venv) for pip dependencies. Note the venv only
adds its site-packages to sys.path; it does not supply libpython, so you still need one of the options above.

Windows: hidden console & stdio

Tauri release builds hide the console (#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] in
main.rs). Without a console, Python's stdout/stderr handles are invalid, and historically a bare print()
could hang or crash the app on Windows release builds (issues #4/#15/#17). This plugin now wraps Python stdio at
startup
so writes can't crash the process, and applies a per-call timeout (see below) so a single blocking
call can't wedge every later call. You generally no longer need to remove the windows_subsystem line. If you want
to see Python output in a release build for debugging, redirect it to a file from main.py, e.g.:

python
import sys sys.stdout = sys.stderr = open("python-debug.log", "a", buffering=1)

Call timeout

Each Python call is bounded by a timeout (default 300s) so a stuck call can't hang the app forever. Override
it with the TAURI_PLUGIN_PYTHON_TIMEOUT_SECS environment variable – set a larger value for long-running work, or
0 to disable the timeout entirely. On timeout the call rejects with a timeout error; note the worker thread is
single and serial, so a call that never returns still occupies it until it finishes.

"Works in tauri dev but not in the production build" checklist

This is the most common deployment problem. Check, in order:

  1. Is src-python/ (incl. main.py) listed under bundle.resources in tauri.conf.json? Inspect the installed
    app's resource folder to confirm the files are actually there with the same structure as locally.
  2. PyO3 + pip deps not working in prod → your .venv isn't bundled or isn't at src-python/.venv. Move it
    inside src-python and bundle it (see Using a venv).
  3. PyO3 app won't launch at all → missing libpython on the target; ship an embeddable Python or the shared
    library (above).
  4. C-extension packages (numpy, …) fail in prod → the venv was built for a different OS/arch than you shipped.
    Rebuild it for the target platform (ideally in CI).
  5. Still stuck? Temporarily build with the console visible (remove the windows_subsystem line on Windows, or run
    the binary from a terminal) to see startup errors, and see Debugging.

Check the tauri and PyO3 documentation for additional info.

Security considerations

By default, this plugin cannot call arbitrary python code. Python functions can only be called if registered from rust during plugin initialization.
It may still be possible to read values from python. This can be prevented via additional tauri permissions.

Keep in mind that this plugin could make it possible to run arbitrary python code when using all allow permissions.
It is therefore highly recommended to make sure the user interface is not accessible by a network URL in production.

The "runPython" command is disabled by default via permissions. If enabled, it is possible to
inject python code directly via javascript.
Also, the function "register" is disabled by default. If enabled, it can
add control from javascript which functions can be called. This avoids to modify rust code when changing or adding python code.
Both functions can be enabled during development for rapid prototyping.

Alternatives

If you already know that you just want to develop completely in python, you might want to take a look at pytauri.
It is a different approach to have all tauri functionality completely in python.

This approach here with tauri-plugin-python is more lightweight and it is for you, if you

  • still want to write rust code
  • already have a tauri application and just need a specific python library
  • just want to simply support rare tauri plugins
  • want to embed python code directly in your javascript

Contributors

Showing top 3 contributors by commit count.

View all contributors on GitHub →

This article is auto-generated from marcomq/tauri-plugin-python via the GitHub API.Last fetched: 6/23/2026