Resources API

This guide covers embedding and accessing static files like images, fonts, and data in Tauri v2 using the resource bundling system.

When you build a Tauri app, you often need more than just the application code — logos, icons, data files, fonts, or even pre-built binaries that your app should ship with. The Tauri Resources system handles exactly that. It lets you declare which files and folders to bundle alongside your executable and provides APIs to access them at runtime, from both Rust and the frontend. The Introduction covers when to use resources versus frontend assets.

Think of it as the equivalent of an assets/ folder in a web project, except Tauri manages how those files are packaged for each operating system and makes them available through a safe, cross-platform protocol.

How Resource Bundling Works

You declare resources in tauri.conf.json under the bundle.resources key. At build time, Tauri collects all files matching those patterns, embeds them into the application bundle, and maps them to paths that are consistent regardless of whether the app is installed via a .msi installer, a .dmg disk image, or a Linux package.

Bundled, not compiled:

Resource files remain as separate files in the bundle — they are not compiled into the Rust binary. This means you can update or inspect them without recompiling your app, but it also means they are not encrypted or hidden from the user.

Two separate mechanisms exist under the same resource umbrella, and they serve different needs:

  • Bundle resources (from config) — arbitrary files you list in tauri.conf.json. They are placed in a platform-specific resource directory and can be read with standard file I/O or loaded through the asset protocol.
  • Embedded assets (compile-time macros) — files baked directly into the Rust binary at compile time using macros like include_bytes! or Tauri's own tauri::assets::EmbeddedAssets. These are served via the asset:// protocol without any filesystem I/O.

This guide focuses on the first approach — the config-driven resource system — because it covers the vast majority of real-world use cases and requires no compile-time code changes to swap out a file. Embedded assets are useful for very small, fixed resources that must not be separable from the binary, but they come with their own set of trade-offs around binary size and mutability.

Configuring Resources

Open your project's src-tauri/tauri.conf.json and add a resources array inside the bundle section. Each entry is a glob pattern relative to the src-tauri directory (the same directory that contains Cargo.toml). Managing Resources walks through adding files and reading them at runtime.

{
  "bundle": {
    "resources": [
      "assets/**/*",
      "data/config.json",
      "fonts/custom-font.ttf"
    ]
  }
}

Tauri resolves these patterns using glob rules, so assets/**/* includes all files recursively under src-tauri/assets/. The paths you specify here determine how you later resolve them inside your app — a file at src-tauri/data/config.json will be accessible under the resource base directory as data/config.json.

No trailing slash for directories:

Patterns like assets/ (a single directory without a glob) are not recognized. Always use a wildcard: assets/**/* to capture everything inside a folder, or assets/*.png for specific file types.

Files placed directly in src-tauri can be included with just their filename. If your tauri.conf.json already contains a bundle section with other settings (like icon or targets), add resources as a new field — don't nest it somewhere else.

Accessing Resources from Rust

From the Rust side, you get the absolute path to a bundled resource by resolving it against BaseDirectory::Resource from the Path API. This works because Tauri sets up the runtime to know where resources were extracted.

// src-tauri/src/lib.rs
use tauri::Manager;
use tauri::path::BaseDirectory;
#[tauri::command]
fn read_config(app_handle: tauri::AppHandle) -> Result<String, String> {
    let resource_path = app_handle
        .path()
        .resolve("data/config.json", BaseDirectory::Resource)
        .map_err(|e| e.to_string())?;
    std::fs::read_to_string(&resource_path)
        .map_err(|e| format!("Failed to read config: {}", e))
}

This command resolves the path, reads the file, and returns its content as a string. On Windows, the resolved path might look like C:\Program Files\YourApp\resources\data\config.json; on macOS, it's inside the .app bundle at Contents/Resources/data/config.json.

Resources are read-only at runtime:

On macOS and Windows, the resource directory is inside the application bundle, which is typically not writable by the user. If you need to store data that your app modifies, use the app data directory (accessed via BaseDirectory::AppData) instead.

There is no special API to "open" a resource — once you have the path, you use standard Rust I/O (std::fs::read, File::open, etc.) to work with the file. For binary data like images, std::fs::read returns Vec<u8> directly.

Accessing Resources from the Frontend

The frontend cannot read files directly from disk because browser security models block it. Tauri bridges this gap with the convertFileSrc function, which takes an absolute file path and returns a URL the webview can load.

The URL uses a custom protocol (https://asset.localhost/... or asset://... depending on platform) that Tauri's internal server handles. It serves the file directly from the filesystem without any network overhead.

Here is a complete React component that loads a bundled image:

// src/App.tsx
import { useState, useEffect } from "react";
import { invoke } from "@tauri-apps/api/core";
import { convertFileSrc } from "@tauri-apps/api/core";
function App() {
  const [imageSrc, setImageSrc] = useState<string | null>(null);
  useEffect(() => {
    async function loadImage() {
      try {
        // Step 1: Ask Rust to resolve the resource path
        const absolutePath: string = await invoke("get_resource_path", {
          fileName: "assets/logo.png",
        });
        // Step 2: Convert to a URL the webview can use
        const assetUrl = convertFileSrc(absolutePath);
        setImageSrc(assetUrl);
      } catch (error) {
        console.error("Failed to load resource:", error);
      }
    }
    loadImage();
  }, []);
  if (!imageSrc) return <p>Loading...</p>;
  return <img src={imageSrc} alt="Logo" style={{ maxWidth: "100%" }} />;
}
export default App;

The corresponding Rust command simply resolves the path:

// src-tauri/src/lib.rs
use tauri::path::BaseDirectory;
#[tauri::command]
fn get_resource_path(app_handle: tauri::AppHandle, file_name: String) -> Result<String, String> {
    app_handle
        .path()
        .resolve(&file_name, BaseDirectory::Resource)
        .map(|path| path.to_string_lossy().to_string())
        .map_err(|e| e.to_string())
}

The convertFileSrc call is where the magic happens. It takes a path like C:\Program Files\MyApp\resources\assets\logo.png and produces a URL like https://asset.localhost/path/to/encoded/path. The webview's <img> tag loads it just as if it were a regular HTTP resource, even though the file never touches a network. This also sidesteps cross-origin issues that would block fetch requests to local files.

All set:

If your image renders, the resource pipeline is working end-to-end: bundling, path resolution, and asset protocol serving.

For data files like JSON or text, you can choose to read them entirely in Rust (as in the read_config example above) and return the content to the frontend via invoke, or you can resolve the path on the frontend and fetch the file using fetch(convertFileSrc(path)). The Rust approach is usually faster and avoids the overhead of an HTTP request, even a local one.

Common Use Cases

Each of the following examples assumes you have already added the relevant files to bundle.resources in tauri.conf.json and that the directory structure under src-tauri/ matches what the code expects.

Loading a bundled image in the UI

Above, we showed the full useEffect + invoke pattern. A cleaner alternative when you know the resource path in advance is to resolve it once during app startup and make it available globally. For a single image, the inline approach is fine; for many assets, consider a custom useResourceAsset hook that batches path resolution.

One non-obvious detail: convertFileSrc encodes the file path into a URL-safe format, which means the resulting URL is longer than the original path and does not directly reveal the filesystem location. If you see a blank image, inspect the resolved URL in the browser devtools and verify the path exists on disk in the bundled app (not just in your development workspace).

Reading a bundled JSON configuration

// src-tauri/src/lib.rs
#[tauri::command]
fn get_settings(app_handle: tauri::AppHandle) -> Result<serde_json::Value, String> {
    let path = app_handle
        .path()
        .resolve("data/settings.json", BaseDirectory::Resource)
        .map_err(|e| e.to_string())?;
    let content = std::fs::read_to_string(&path)
        .map_err(|e| format!("Cannot read settings: {}", e))?;
    serde_json::from_str(&content)
        .map_err(|e| format!("Invalid JSON: {}", e))
}

On the frontend:

import { invoke } from "@tauri-apps/api/core";
async function loadSettings() {
  const settings = await invoke("get_settings");
  console.log(settings);
}

This keeps all file I/O in Rust, where error handling is explicit and you can validate the JSON structure before it reaches the UI. If settings.json is malformed, the frontend receives a clean error message rather than a cryptic parsing failure from JSON.parse.

Using a custom font shipped with the app

Fonts are loaded differently than images because the CSS engine resolves font files through @font-face declarations, which expect a URL. The same convertFileSrc pattern works:

// In your CSS-in-JS or a <style> tag
const fontUrl = convertFileSrc(resolvedFontPath);
const style = `
  @font-face {
    font-family: 'CustomFont';
    src: url('${fontUrl}') format('truetype');
  }
`;

Then you can use fontFamily: 'CustomFont' anywhere in your styles. Keep in mind that the font file must be bundled — Tauri does not automatically include anything from your src-tauri directory unless you list it in resources.

Embedding and playing an audio file

import { invoke } from "@tauri-apps/api/core";
import { convertFileSrc } from "@tauri-apps/api/core";
async function playSound(fileName: string) {
  const path: string = await invoke("get_resource_path", { fileName });
  const audio = new Audio(convertFileSrc(path));
  await audio.play();
}

This works identically to loading an image. The Audio constructor treats the asset URL like any other remote audio source. One gotcha: some platforms require user interaction before audio can play (autoplay policy). If your sound doesn't start, ensure it's triggered by a click handler, not a page-load effect.

Watch out for large resources:

Every resource you bundle increases your installer size. If you need to ship gigabytes of data, consider downloading assets on first launch instead and storing them in the app data directory. The resource system is ideal for assets that are essential to the app's initial experience — icons, default configurations, and core media.

Summary

Tauri's resource system bridges the gap between a web app's assets/ folder and the reality of a packaged native application. You declare what to include, Tauri bundles it for every platform, and then you access those files through standard I/O in Rust or the asset protocol in the frontend.

The biggest takeaway: bundle what the app cannot function without, and always resolve paths through BaseDirectory::Resource — never hardcode relative paths that will break when the app is installed. For configuration that changes at runtime, use AppData; for read-only assets that define the app's look and behavior, the resource system is the right tool.

Introduction to the Resources API

Learn what bundled resources are in Tauri v2, when to use them, and how to configure and access them from Rust and a React frontend.

Managing Resources

Embed custom files into your Tauri v2 application and access them at runtime using the Resources API

Common Use Cases - Resources API

Learn how to embed and serve images, fonts, config files, and templates with Tauri’s Resources API using a React frontend