What are Native APIs
Understand the core concept of native APIs, how they give Tauri apps access to system-level features, and why Rust acts as the bridge between your web frontend and the operating system
Operating systems expose a set of programming interfaces that applications can call to perform tasks like reading files, displaying windows, or accessing hardware. These are native APIs — the software contracts that let a program ask the OS to do something on its behalf. In the context of Tauri, native APIs are what transform a web frontend from a browser‑tab experience into a real desktop or mobile application with access to file systems, system notifications, clipboard, and much more.
What Are Native APIs
A native API is a collection of functions, data structures, and protocols provided directly by an operating system. Windows has the Win32 API, macOS exposes frameworks like Foundation and AppKit, Linux relies on system calls and libraries like GTK or libc, and mobile platforms offer Android SDK and iOS UIKit/SwiftUI. Regardless of the platform, the purpose is the same: give software the ability to interact with the machine's hardware and OS‑managed resources.
When you save a file in a text editor, the application doesn’t talk to the hard drive itself. It calls a native API function (CreateFile on Windows, open on Unix‑like systems), and the OS kernel handles the rest. The application never sees the raw disk sectors, the file system journal, or the drive controller — it just gets back a file handle. That abstraction is what a native API provides.
For a Tauri app, these APIs live on the Rust side. The WebView running your JavaScript and React code is deliberately sandboxed, just like a regular browser tab. It cannot call the OS directly. Instead, Tauri gives you a controlled path to invoke those APIs through Rust commands, keeping the frontend secure while unlocking the full power of the platform.
Not the same as web APIs:
Web APIs like fetch, localStorage, or the Web Bluetooth API are provided by the browser environment, not by the operating system. Native APIs sit one layer deeper and can do things no browser API is allowed to do — like writing to an arbitrary file path or spawning a child process.
Why Native APIs Matter for Desktop and Mobile Apps
A web app running in a browser has limits by design. It can store data in the browser’s local storage, make network requests, and show notifications only after the user grants permission — and only in the specific way the browser allows. But it cannot read a user’s documents folder unless they manually pick a file through a file input. It cannot set a global keyboard shortcut, run a background process, or modify the system tray.
Desktop and mobile users expect more. They want to drag‑and‑drop files from the OS file manager, open files with the app by double‑clicking them, receive native push notifications even when the app is closed, and have the app remember window positions across restarts. None of that is possible with a web app running in a browser tab. Native APIs make all of it possible.
Tauri bridges this gap by letting you write your UI with React and Vite — the same stack you’d use for a web app — while delegating any OS‑level work to Rust, which can call whatever native APIs the platform offers. The result is an app that feels native to the user but stays approachable for a web developer.
JavaScript vs Native APIs in the Browser
Browsers do offer JavaScript access to some system‑adjacent features. The Geolocation API can request the device’s position. The Clipboard API can read and write the system clipboard (with restrictions). The Notification API can show desktop notifications. These are sometimes called “web platform APIs,” and they are incredibly useful.
The critical difference is scope and control. A web API exists in a heavily permissioned sandbox. The browser vendor decides what is available and under what conditions. For example, the File System Access API lets a web app read and write files, but only inside a user‑selected directory after an explicit picker gesture. You cannot programmatically list all files in a directory without user interaction.
Native APIs have no such sandbox (within the app’s own process). If your Rust code calls std::fs::read_dir, it can enumerate any directory the OS user has permission to access. If it calls a shell command, it can launch a subprocess with whatever arguments are needed. That power is why Tauri wraps native access inside a permission model: the frontend cannot call native APIs directly; it must go through Rust commands that you explicitly register and, in Tauri v2, explicitly allow through capabilities.
Rust as the Bridge Between JavaScript and the OS
Tauri’s architecture splits the application into two processes. The frontend (your React app) runs inside a system WebView — essentially a lightweight browser window. It has no access to native APIs, just like a webpage. The backend is a Rust binary that runs directly on the operating system with all the privileges of a native application. Communication between these two processes happens through Tauri’s own IPC (inter‑process communication) layer.
A mental model that helps: think of the frontend as a customer placing an order at a counter, and the Rust backend as the kitchen. The customer can ask for a dish (invoke a command) and can receive it when it’s ready, but the customer never walks into the kitchen or touches the stove. The kitchen does the real work using the OS’s tools — the native APIs — and returns the finished result.
In practice, you write a function in Rust, annotate it with #[tauri::command], and register it with the Tauri app builder. From the React side, you import the invoke function from @tauri-apps/api/core and call the command by name. The data flows like this:
React (invoke) → Tauri IPC → Rust command → Native API → OS
← ← ←
All arguments and return values are serialized automatically. The frontend never touches the file system, the system call, or the process — it only sees whatever data the Rust command chooses to return.
Native Desktop and Mobile Capabilities in Tauri v2
Tauri v2 ships with a growing set of official plugins and built‑in Rust APIs that wrap the most commonly needed native capabilities. Each one is backed by system‑level calls on every supported platform (Windows, macOS, Linux, iOS, Android). The main categories include:
- File System – read, write, create, and delete files and directories with full path control, not just user‑picked files.
- Dialogs – open native file/folder pickers, save dialogs, and message boxes that match the OS look and feel.
- Clipboard – read and write text, images, and other content to the system clipboard.
- Notifications – send local desktop notifications and manage notification permissions.
- Shell – open URLs in the default browser, launch external applications, or run shell commands (with strict security scoping).
- Path – retrieve standard system directories like the user’s home, documents, or app data folder.
- Global Shortcuts – register keyboard shortcuts that work even when the app is not in focus.
- Window management – control size, position, title, decorations, and create multiple windows programmatically.
Every capability in this list lives behind Tauri’s permission system. In v2, you must explicitly allow each command a window is allowed to call by configuring capability files. This is a deliberate design: the frontend cannot accidentally gain access to a dangerous API just because a plugin is installed.
How a Native API Call Works End-to-End
A concrete example makes the entire flow tangible. Below, a Rust command uses the whoami crate to fetch the machine’s hostname — a piece of information the JavaScript frontend cannot obtain on its own. Then a React component calls that command and displays the result.
Step 1: Define a Rust command that uses a native API
Add the whoami crate to src-tauri/Cargo.toml (whoami = "1"), then write the command inside src-tauri/src/main.rs:
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![get_hostname])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn get_hostname() -> String {
whoami::hostname()
}
The #[tauri::command] attribute marks the function so Tauri’s IPC layer can discover it. The function itself is plain Rust: it calls whoami::hostname(), which internally makes OS‑specific system calls to read the computer’s network name.
Step 2: Register the command with the Tauri app builder
The invoke_handler line in the builder tells Tauri which commands the frontend is allowed to call. Without this registration, calling get_hostname from JavaScript would result in a “command not found” error at runtime — even if the function exists in the binary.
In Tauri v2, registration alone isn’t enough. You must also permit the command in a capability file. For this example, the default capability (created by the Tauri CLI when you scaffold the project) usually allows all commands from the main window. If you remove that permission later, the call will be blocked.
Step 3: Call the command from your React component
Use the invoke function from @tauri-apps/api/core to send a request to the Rust backend:
import { useState } from 'react';
import { invoke } from '@tauri-apps/api/core';
function App() {
const [hostname, setHostname] = useState('');
const fetchHostname = async () => {
try {
const name = await invoke<string>('get_hostname');
setHostname(name);
} catch (error) {
console.error(error);
}
};
return (
<div>
<button onClick={fetchHostname}>Get Hostname</button>
{hostname && <p>Your machine hostname: {hostname}</p>}
</div>
);
}
export default App;
invoke returns a promise that resolves with whatever the Rust function returns. The generic <string> helps TypeScript infer the result type, but it’s optional. The call is fully asynchronous — it doesn’t block the UI thread — yet the Rust side executes synchronously on its own thread.
When you click the button, the React component sends a message through Tauri’s IPC. The Rust backend receives it, executes get_hostname, which calls the OS’s native hostname function, and sends the result back. The frontend never directly touches a system call, but the user sees the hostname appear on the screen.
Everything is working:
If you see your machine’s hostname displayed after clicking the button, the full native API pipeline — from React to Rust to OS and back — is functioning correctly. This is the same pattern used for every other native capability in Tauri.
The non‑obvious detail here is error handling. If the invoke call fails — perhaps because the command name is misspelled or the capability file doesn’t permit it — the promise rejects. In a real app, you would surface that error to the user rather than just logging it to the console. The catch block in the example is the minimum required to keep the app from crashing silently.
Common Misunderstandings About Native APIs
The mental model of “web developer → native APIs” through Tauri is powerful but prone to a few specific mistakes.
Forgetting to configure capabilities blocks the command:
In Tauri v2, registering a command in the builder is not enough. If the window’s capability file does not explicitly permit the command, invoking it will fail with an error like command not allowed. This is the single most frequent source of confusion when moving from v1’s allowlist to v2’s permissions. Always check your capability configuration when a command that is defined and registered still refuses to run.
- Native APIs are not the same as web APIs. The function
get_hostnameabove does not exist in the browser. It’s entirely a Tauri‑specific bridge. Assuming that any JavaScript API you find in browser documentation works the same way in a Tauri app will lead to frustration. - Native API calls run outside the WebView. The Rust command executes in a separate process, not in the JavaScript event loop. This means you can perform blocking operations (file I/O, heavy computation) on the Rust side without freezing the UI — a freedom you don’t have in pure JavaScript.
- Permissions are not automatic. Just because you installed a plugin doesn’t mean the frontend can use it. You must declare permissions in a capability file. Over‑granting permissions (allowing all plugins to all windows, for example) defeats the security model and should be avoided in production.
Exposing native APIs to untrusted content:
If you ever load remote content in a Tauri window — say, a third‑party dashboard — the permission model protects you. Only commands explicitly allowed for that window can be called. Loading remote content without restricting the capability scope can turn a controlled bridge into an open door to the operating system. Tauri v2’s capabilities can be scoped per window, so you can grant full native access only to your own trusted UI.