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.
This is the most important decision when using the plugin, so please read this before you start.
| PyO3 / CPython (default) | RustPython | |
|---|---|---|
| Cargo feature | pyo3 (enabled by default) |
rustpython |
| Interpreter | the real CPython | a Python interpreter written in Rust |
| Library compatibility | full – 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 machine | yes (libpython must be available, see below) | no – linked statically into your binary |
| Speed | fast (CPython) | slower |
| Deployment | more 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.
| Platform | Supported |
|---|---|
| Linux | ✓ |
| Windows | ✓ |
| MacOS | ✓ |
| Android | x* |
| iOS | ✓* |
x* There is currently a known issue on tauri+android that prevents reading files.
tauri-apps/tauri#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.
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.
# src-tauri/Cargo.toml
tauri-plugin-python = { version = "0.3", default-features = false, features = ["venv", "rustpython"] }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:
pyoxidizer generate-python-embedding-artifacts src-tauri/target/pyembedThen, add it to your cargo config:
# 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.
There is a sample Desktop application for Windows/Linux/MacOS using this plugin and vanilla
Javascript in examples/plain-javascript.
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.pyand modify it according to your needs, for example add
# 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/"],totauri.conf.jsonso 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 functiongreet_pythonwith parameter of js variablevalue
- add
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.
$ 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.pyand add python code, for example:
# 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"]))totauri::Builder::default(), usually insrc-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:
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:
import { call, registerJs } from 'tauri-plugin-python-api'
registerJs("greet_python");
console.log(await call.greet_python("input value"));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):
# 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
"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.
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:
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_functionslist inmain.py. The dynamicregistercommand is disabled by default (see Security).Cannot register '<name>': not found in Python–main.pydidn't define/import that name, ormain.pyitself failed to load. Make suresrc-python/main.pyexists, is bundled as a resource (see Deployment), and runs without errors on its own.- A
ModuleNotFoundError, missing-stdlib, codec (unknown encoding) orinspecterror 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
libfolder is shipped as a resource next tosrc-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).
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.
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 needs a libpython at runtime, which is what makes PyO3 deployment harder. Options, easiest first:
- Ship an embeddable / standalone Python with the app (recommended for end-user distribution). Bundle a self-contained Python (e.g. python-build-standalone or the Windows embeddable zip) as a resource and point the app at it before launch, so the target needs no system Python and there is no fragile dynamic linking. 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 rather than a recommendation).
- Ship the shared libpython next to the executable / inside the
.venvyou bundle. Check what is linked withotool -L tauri_app(macOS) orldd tauri_app(Linux); a line such as.../Python.framework/Versions/3.13/Pythontells you which version the target must provide at the same location. - 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.
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.:
import sys
sys.stdout = sys.stderr = open("python-debug.log", "a", buffering=1)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.
This is the most common deployment problem. Check, in order:
- Is
src-python/(incl.main.py) listed underbundle.resourcesintauri.conf.json? Inspect the installed app's resource folder to confirm the files are actually there with the same structure as locally. - PyO3 + pip deps not working in prod → your
.venvisn't bundled or isn't atsrc-python/.venv. Move it insidesrc-pythonand bundle it (see Using a venv). - PyO3 app won't launch at all → missing
libpythonon the target; ship an embeddable Python or the shared library (above). - 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).
- Still stuck? Temporarily build with the console visible (remove the
windows_subsystemline 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.
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.
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