Understanding Native APIs

Learn what native APIs are and how Tauri v2 exposes them to your React frontend through a secure IPC bridge

Desktop applications can read files, show notifications, open file picker dialogs, and interact with the clipboard. A regular web page, running inside a browser’s sandbox, cannot do most of those things by itself. Tauri closes that gap: it gives your React frontend controlled access to the operating system’s capabilities through native APIs, without forcing you to leave the JavaScript and TypeScript you already know.

This page explains what native APIs are as a general concept, then walks through how Tauri v2 specifically makes them available to your frontend. It sets the foundation for every native feature covered in later sections — file system access, dialogs, clipboard, notifications, and more.

What Are Native APIs?

A native API is a programming interface provided by an operating system or platform that lets software talk directly to hardware and system services. The file system, the display, the network stack, the notification system — each of these exposes functions that programs can call. Those functions are the native API of that platform.

Contrast this with a web API: a web API runs on a remote server and your app talks to it over HTTP. A native API runs on the user’s own machine, and the calls never leave the device. This has two practical consequences. First, native APIs are significantly faster for operations that need local resources — reading a thousand files, for instance, involves no network round trips. Second, native APIs can do things a web API simply cannot, like access the user’s file system, manipulate the clipboard, or open a native dialog window.

From a developer’s perspective, native APIs exist at different abstraction levels. At the lowest level, you might call a C function that reads raw bytes from a disk. At a higher level, you might call a Rust function that returns a string with proper error handling. Higher still, you might call a JavaScript function that looks and behaves like a regular web API. Tauri builds the highest level of that stack so that frontend developers can use native capabilities without ever touching the low-level system calls.

Native versus platform:

“Native” in this context means “belongs to the operating system,” not “written in a compiled language.” A Tauri app’s native API calls are still made from JavaScript — they just go through a Rust bridge to reach the OS.

A mental model that helps: think of a web app as living inside a sealed room. The browser gives it a small window to the outside world (network requests, DOM, a few device APIs), but the walls are thick. Native APIs are the doors you deliberately cut into those walls — each one opening a specific, controlled path to the operating system.

Native APIs in Tauri v2

Tauri v2 ships with a collection of JavaScript modules that wrap common system capabilities. These modules are part of the @tauri-apps/api package, and they communicate with a Rust backend through an Inter-Process Communication (IPC) bridge — the same model described in Connecting Backend to Frontend. The Native APIs in Tauri v2 page covers this surface in isolation. Your React code calls a JavaScript function; that function serializes the request into JSON, sends it across the bridge to Rust, and Rust performs the actual system call. The return value travels back the same way.

The JavaScript API modules

The @tauri-apps/api package organizes its modules by feature area. When you work with the topics in this chapter — file system, dialogs, clipboard, notifications, shell commands, and path resolution — you will import functions from the corresponding module. The full set of available modules includes:

ModuleWhat it provides
windowCreate and manage application windows, change title, size, position
webviewWindowOpen and control secondary webview windows
pathAccess standard system directories (documents, desktop, home, etc.)
eventListen to and emit events between the frontend and backend
appQuery app metadata and manage the application lifecycle
coreInvoke custom Rust commands (the invoke function lives here)
trayControl the system tray icon and menu
menuBuild and manage native application menus
dpiGet the physical screen density for high-DPI displays

Several additional modules — file system, dialog, clipboard, notification, and shell — are available as official plugins.

Importing the API

There are two ways to import Tauri’s API into your frontend code. The approach you choose depends on whether your project uses a bundler (React + Vite always does) or plain JavaScript.

// Inside a React component file
import { invoke } from "@tauri-apps/api/core";
import { appDataDir } from "@tauri-apps/api/path";
// ...

The bundler method is the standard for any Tauri project created with Vite. You use standard ES module imports, and the bundler resolves them directly. The global method exists for environments without a build step; it relies on the withGlobalTauri configuration option in tauri.conf.json, which exposes a __TAURI__ object on the window.

How IPC works in Tauri v2

The JavaScript functions you import do not perform system calls themselves. They package your request and hand it off to the Tauri core, which transmits it to the Rust process that runs alongside your webview. That transmission happens over a local IPC channel — essentially a high-speed, in-process message pipe that carries JSON payloads.

┌──────────────────────────┐
│  React frontend          │
│  (WebView)               │
│                          │
│  import { invoke } ...   │
└────────────┬─────────────┘
             │  JSON over IPC (local, no network)
┌────────────▼─────────────┐
│  Rust backend             │
│                          │
│  #[tauri::command]       │
│  fn greet(name) -> ...   │
└────────────┬─────────────┘
             │  actual system call
┌────────────▼─────────────┐
│  Operating System         │
└──────────────────────────┘

For custom Rust commands you write yourself, the JavaScript side always uses the invoke function from the @tauri-apps/api/core module. Built-in plugin APIs (like reading a file or showing a dialog) use the same IPC mechanism under the hood, but they provide a friendlier, purpose-built JavaScript wrapper so you never need to call invoke directly.

Calling a Rust command from React

The shortest path to understanding the pattern is to write a Rust command and call it from the frontend. Below is a complete example. The Rust function receives a string, returns a greeting, and the React component calls it when a button is clicked.

// The command must be annotated with #[tauri::command]
#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You called a Rust function.", name)
}
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
import { useState } from "react";
import { invoke } from "@tauri-apps/api/core";
function App() {
  const [message, setMessage] = useState("");
  async function handleGreet() {
    try {
      const response = await invoke<string>("greet", { name: "React Developer" });
      setMessage(response);
    } catch (error) {
      console.error("Failed to call Rust command:", error);
    }
  }
  return (
    <div>
      <button onClick={handleGreet}>Call Rust</button>
      <p>{message}</p>
    </div>
  );
}
export default App;

The invoke call takes the command name as the first argument and an object of parameters as the second. It returns a Promise that resolves to whatever the Rust function returns. Because the data crosses the IPC boundary as serialized JSON, the types you use in Rust and TypeScript must be compatible — strings, numbers, booleans, arrays, and objects that implement Serialize / Deserialize.

Missing command registration:

If you write a #[tauri::command] but forget to register it inside generate_handler![], the frontend will receive an error: “command not found.” This is one of the most common early mistakes. Always add every new command to the handler list.

Everything is working:

After clicking the button, you should see “Hello, React Developer! You called a Rust function.” appear on screen. If that text appears, the IPC bridge is functioning correctly and you have successfully called native code from JavaScript.

The overhead of a single invoke call is typically measured in microseconds. For occasional operations — opening a file, sending a notification — this is imperceptible. For high-frequency calls (hundreds per second, such as streaming audio samples), it is better to batch data into fewer, larger payloads to keep the IPC crossing count low.

Native APIs and the permission model

Every Tauri native API call — whether it is a custom command or a plugin function — must be explicitly allowed by the application’s capability configuration. Tauri v2 does not grant broad OS-level access by default. If your app tries to read a file without the appropriate permission, the call will fail with a permission error.

Permission errors are a deliberate safety net:

A permission rejection is not a bug in your code; it is a security mechanism doing its job. Tauri’s capability system ensures that an app can only do what its configuration explicitly permits. You will configure these permissions when you add specific native features in the following sections.

The entire security model — capabilities, permission scopes, and configuration files — is vital for restricting access. Every native API you use will require you to declare that usage up front.

The plugin ecosystem

While Tauri’s core provides essential platform abstractions like window management and the IPC bridge itself, many native capabilities ship as official plugins. File system access (@tauri-apps/plugin-fs), dialogs, clipboard, notifications, shell commands, and others are all plugins that you add to your project explicitly. They follow the same invoke-under-the-hood pattern and provide ergonomic JavaScript APIs, but they also come with their own permission requirements.

Plugins let the Tauri team keep the core small while allowing your app to depend only on the capabilities it actually needs. When a later section says “install the dialog plugin,” that step adds the Rust crate, the JavaScript module, and a capability entry — all managed through a few configuration lines.

Why this approach matters for React developers

You do not need to write Rust to use any of the built-in native APIs covered in this chapter. The JavaScript modules provide everything you need to read and write files, open dialogs, manipulate the clipboard, send notifications, and run shell commands. You call familiar async functions, handle errors with try/catch, and keep your entire UI in React — the same workflow you use for any web app.

Rust enters the picture only when your app needs a custom computation that would be too slow in JavaScript, or when you want to orchestrate several native calls together in a single transaction that runs reliably on the backend. Even then, you expose Rust functions as commands and call them with invoke — no new paradigm, just a function that happens to execute on the other side of a local pipe.

Understanding the IPC bridge and the JavaScript API modules prepares you to work with any native feature.

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

Native APIs in Tauri v2

How Tauri v2 exposes operating system features to your React frontend through built-in modules, plugin-based APIs, and a secure inter-process communication model.