Understanding Resources

Learn what resources are in Tauri v2 - how they differ from assets, when to use them, and how to configure and access them in your React plus Vite app

A Tauri application bundles two distinct kinds of files: the web frontend (HTML, CSS, JavaScript) that runs inside the webview, and a separate set of files called resources. Resources are raw data files shipped alongside your app binary, kept outside the web-facing asset system. They are the configuration templates, database seeds, machine learning models, or translation files that your Rust backend or your frontend might need at runtime without being served directly as web assets. The Resources API chapter covers the JavaScript helpers for the same files.

What Are Resources?

Resources are files you tell Tauri to copy into the final application bundle during the build step. They end up in a dedicated resource directory that your Rust code can read with standard file system operations, and that your frontend can access (with the right permissions) via a special asset protocol.

Unlike the files inside the public directory of your Vite project, which are served at known URLs like /logo.png, resource files are not part of the web root. They live in a platform‑specific location inside the app package:

  • On macOS: inside YourApp.app/Contents/Resources/
  • On Windows: next to the .exe file
  • On Linux: in the same directory as the binary or under lib/

A resource could be anything—a JSON configuration file, a SQLite database, a trained model, a .csv translation table, or a private key that should not be visible to the webview unless you explicitly expose it.

Resource access is controlled:

Resources are not automatically visible to the frontend. If you want an image or a data file to be shown in the UI, you must either read it from Rust and pass the content to the frontend, or convert its path to an asset URL using Tauri’s convertFileSrc helper and ensure you have the appropriate permissions in your capability file.

Resources vs Assets

The distinction between resources and assets is one of the most common points of confusion for developers new to Tauri. Both are files that end up inside your app, but they serve different purposes and are accessed differently.

ResourcesAssets
Location in bundleCopied to the platform‑specific resource directory (e.g. Resources/ on macOS)Bundled into the frontend’s dist folder (or the public directory during development)
Primary useData files consumed by the Rust backend or loaded programmaticallyImages, fonts, stylesheets, or any file meant to be displayed or served directly in the webview
How the frontend sees themOnly accessible through Rust commands or by converting a file path to an asset protocol URL (requires resources permission)Accessible by URL: ./logo.png or /static/data.json (as long as the web server or bundler serves them)
When the file is neededAt runtime, often changed or read by native codeAt render time, inside the UI
Typical examplesDefault settings, database seeds, ML models, license keys, plugin filesApp icons, CSS files, favicon, splash screen images

If a file is supposed to appear in an <img> tag or be fetched with a fetch() call from the frontend without any Rust involvement, it should be an asset. If it is data that Rust needs to process—or data you want to keep away from the public web root—it belongs in resources.

When to Use Resources

Choose resources in these scenarios:

  • Bundled default data that your app needs on first launch: a starter SQLite database, a default configuration file, or a set of demo files.
  • Files that the Rust backend owns: a machine learning model loaded with ort or tract, a binary dictionary, or a file used by a custom Rust command.
  • Private files you do not want exposed to the webview’s JavaScript context until you decide exactly what to share. The resource directory is off‑limits to the frontend unless you grant resources permissions.
  • Large binary files that would bloat the frontend bundle and slow down page loads. Keeping them as resources loads them only when needed, and the Rust side can stream or process them efficiently.
  • Platform‑specific content: a resource that should only be included on Windows (e.g., a .dll or a registry template) can be conditionally added via platform‑specific configuration.

Do not use resources for UI assets:

If the file is a public image, a font, or a CSS file you reference directly in your React components with a relative URL, place it in the public directory (or in your src/assets folder and let Vite handle it). Using resources for these files adds unnecessary complexity and forces you to manually convert every path.

Supported Resource Types

Tauri places no restrictions on file types for resources. Any file format that your Rust or JavaScript code can read is allowed:

  • Text files: JSON, YAML, TOML, CSV, XML, plain text
  • Binary files: SQLite databases, images (PNG, JPEG, WebP), PDFs, fonts
  • Compiled libraries: DLLs, .so files, .dylib files (often used with sidecars)
  • Archives: ZIP files, tarballs
  • Executables: extra binaries or sidecar programs (sidecars have their own configuration, but they share the same bundling philosophy)

The only requirement is that the file exists somewhere in your project’s src-tauri directory (or a location relative to it) and is listed in the bundle > resources configuration.

How Resources Are Bundled

When you run npm run tauri build, the build process takes every entry in the bundle.resources array from tauri.conf.json and copies those files (respecting glob patterns) into the platform‑specific resource directory inside the final application package. The copying happens after the Rust compilation and after the frontend build, so you can even include files that are generated during the Rust build step.

During development (npm run tauri dev), the resource directory is resolved relative to the project’s src-tauri folder. This means your Rust code can already read resource files during development—no build step required—as long as the paths are configured correctly.

Configuring Resources

Adding a resource is a three‑step process: create the file, declare it in tauri.conf.json, and ensure the necessary permissions are granted if you want the frontend to access it.

1

Create the resource directory and files

Inside the src-tauri folder, create a resources directory (the name is not enforced, but this convention keeps things tidy). Place your files there. For example, create src-tauri/resources/config.json and src-tauri/resources/logo.png.

src-tauri/
├── resources/
│   ├── config.json
│   └── logo.png
├── Cargo.toml
├── tauri.conf.json
└── src/
    ├── main.rs
    └── lib.rs
2

Add the resource entry in tauri.conf.json

Open src-tauri/tauri.conf.json and locate the bundle section. Add a resources array with paths relative to the src-tauri directory. You can list individual files or use glob patterns.

src-tauri/tauri.conf.json
{
  "bundle": {
    "resources": [
      "resources/config.json",
      "resources/*.png"
    ]
  }
}

The glob resources/*.png picks up all PNG images in that folder. Paths are resolved starting from the src-tauri directory. If you have resources elsewhere (e.g., in a sibling directory), use a relative path like ../shared-data/model.bin.

Platform‑specific resources:

You can also scope resources to specific platforms using a map:

"resources": {
  "linux": ["resources/linux-only.so"],
  "macOS": ["resources/mac-only.dylib"],
  "windows": ["resources/windows-only.dll"]
}
3

Grant resource access permissions (for frontend)

If your frontend code needs to load a resource via its file path (e.g., to display an image with convertFileSrc), you must add the resources:default permission to a capability file. By default, only the Rust backend has unrestricted access to the resource directory.

Create or edit a capability file inside src-tauri/capabilities. For example, src-tauri/capabilities/main.json:

src-tauri/capabilities/main.json
{
  "$schema": "./schemas/desktop-schema.json",
  "identifier": "main-capability",
  "description": "Main window capabilities",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "resources:default"
  ]
}

Then ensure tauri.conf.json references this capability:

src-tauri/tauri.conf.json
{
  "app": {
    "security": {
      "capabilities": ["main-capability"]
    }
  }
}

Without the resources:default permission, any attempt to convert a resource path to an asset URL or call a resource‑related API from JavaScript will be blocked by Tauri’s runtime authority.

Accessing Resources at Runtime

Once bundled, a resource can be read by the Rust backend using standard file APIs, or surfaced to the frontend with a little help from Tauri’s path utilities. The approach you choose depends on where the data needs to live.

The Rust process has direct access to the resource directory. You can obtain the path via app.path().resource_dir() and use std::fs to read the file. The result can then be returned to the frontend through a Tauri command.

src-tauri/src/lib.rs
use tauri::Manager;
use std::fs;
#[tauri::command]
fn read_config(app: tauri::AppHandle) -> Result<String, String> {
    let resource_dir = app.path().resource_dir()
        .map_err(|e| e.to_string())?;
    let config_path = resource_dir.join("resources/config.json");
    let content = fs::read_to_string(&config_path)
        .map_err(|e| format!("Failed to read config: {}", e))?;
    Ok(content)
}
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![read_config])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

This command reads config.json as a string and sends it to JavaScript. The resource directory path is obtained through the AppHandle, which Tauri injects into command functions when they have a parameter of type AppHandle or when you use app: tauri::AppHandle (the name can be anything). The resource_dir() method returns a PathBuf pointing to the correct location on every platform—no manual path construction needed.

Do not hardcode resource paths:

Never write platform‑specific paths like ../Resources/config.json or ./config.json in your Rust code. Always resolve the base directory from app.path().resource_dir(). On Windows during development the resource directory might be the project’s src-tauri folder, but in production it is next to the executable. A hardcoded relative path will break silently.

Common Resource Types in Practice

JSON Configuration Files

A bundled config.json gives your app a set of default settings without requiring the user to configure anything on first launch. The Rust backend reads the file, deserializes it (e.g., with serde_json), and exposes the values to the frontend via a command. This keeps the sensitive defaults out of the web bundle and lets Rust validate the structure before the UI even sees it.

src-tauri/src/lib.rs
use serde::Deserialize;
#[derive(Deserialize)]
struct AppConfig {
    theme: String,
    language: String,
}
#[tauri::command]
fn get_default_config(app: tauri::AppHandle) -> Result<AppConfig, String> {
    let dir = app.path().resource_dir().map_err(|e| e.to_string())?;
    let raw = std::fs::read_to_string(dir.join("resources/config.json"))
        .map_err(|e| e.to_string())?;
    serde_json::from_str(&raw).map_err(|e| e.to_string())
}

Images and Icons

Images loaded from resources via convertFileSrc can be used anywhere an src attribute is accepted. This is useful when the image changes based on user selection or app state and is not simply a static asset. Because the URL uses the asset protocol, the image is cached by the webview and loads efficiently.

SQLite Databases

A seed SQLite database placed in resources can be copied to the app’s data directory on first run. The Rust side uses rusqlite to manage the database and exposes query results through commands. The resources folder contains the pristine template; your Rust logic decides when to copy it and where to keep the user’s working copy (typically in app.path().app_data_dir()).

Binary Models and Libraries

Machine learning models (.onnx, .bin) or shared libraries required by a Rust plugin are natural candidates for resources. They are loaded directly by the Rust process, bypassing the webview entirely. Because these files can be large, placing them in resources keeps the frontend bundle small and ensures they are only in memory when actually needed.

Common Mistakes

Mistakes with resources nearly always revolve around path resolution or missing permissions.

  • Hardcoding relative paths in Rust. A path like "./resources/config.json" will work during development (because the current working directory happens to be the project root) but fail in a packaged app. Always derive the base path from app.path().resource_dir().
  • Treating resources as web assets. Calling fetch('./resources/data.json') from JavaScript will not work because the file is not served by the webview. The frontend can only reach resources through Rust commands or convertFileSrc.
  • Omitting the resources permission. Even if you use convertFileSrc, the runtime authority checks the capability list. Without "resources:default", the conversion is blocked. This is a common source of silent failures where the image simply does not appear and the console shows a permission error.
  • Using resource paths in production without testing on all platforms. The resource directory structure differs across operating systems. Relying on resourceDir() is safe; building a path manually based on how your development machine organizes files is not.
  • Assuming resourceDir() returns the same value during development and after build. During development, resourceDir() points to the src-tauri directory. After build, it points to the real resource folder inside the bundle. That means a file that is present in src-tauri/resources/ during dev might not exist in the bundle if you forgot to add it to bundle.resources.

The most costly mistake: forgetting the resource list:

A file that sits in your src-tauri/resources folder but is not listed in bundle.resources will be available during development (because the dev path resolves to the project directory) and will mysteriously vanish in production builds. Always verify that every resource you use at runtime appears in the configuration array.

Best Practices

  • Keep a flat resources folder with clear subdirectories if needed (e.g., models/, configs/, images/). Avoid deep nesting, which makes path construction error‑prone.
  • Use glob patterns sparingly but effectively. A pattern like resources/**/* copies everything—including files you might not want, such as development notes or temporary backups. Prefer explicit patterns: resources/*.json and resources/icons/*.png.
  • Encapsulate resource access in dedicated Rust commands. Instead of sprinkling resource_dir() calls everywhere, create a small helper module that provides functions like get_config_path(), get_model_path(), etc. This centralizes path logic and makes it easy to adjust if Tauri’s API evolves.
  • Validate resource content at startup. If a required resource is missing or malformed, fail early with a clear error message. Use a dedicated setup hook in tauri::Builder to check that all critical files exist and are readable.
  • Avoid embedding sensitive secrets in resources. Resources are not encrypted. Anything in the resource directory can be extracted by anyone with access to the application bundle. Use environment variables or a proper secret management system for keys, tokens, and credentials.

Quick health check:

If you want to verify your resource setup is correct, add a temporary Rust command that returns the contents of a small test text file. Call it from the React frontend after the app loads. If you see the expected text, you have confirmed that the resource is bundled, the path resolution works, and the frontend can reach it through the command.

What Comes After Configuration