Introduction to the SQL Plugin
Learn what the Tauri SQL plugin is, which databases it supports, and how to set it up in a Tauri v2 project with React and Vite
Desktop applications often need to store structured data locally or connect to a remote database. Without a database, you end up writing fragile file-parsing logic every time you want to persist something beyond a few key-value pairs. The SQL plugin gives your Tauri app direct access to a real relational database from the frontend without any separate backend service.
What the SQL Plugin Provides
The plugin wraps the sqlx Rust library and exposes a JavaScript API that lets your React code open connections, run queries, and manage database schema. It supports three widely used database engines out of the box: SQLite, MySQL, and PostgreSQL.
This means you can build an offline-first task manager that stores data locally with SQLite, a tool that syncs with a remote MySQL server, or an analytics dashboard that queries a PostgreSQL instance — all from the same plugin, using the same JavaScript interface.
Supported Database Engines
The plugin can be compiled with one or more of the following drivers enabled. You pick the engine that fits your application’s deployment model.
- SQLite — A file-based database with no server required. The database file lives on the user’s machine. Ideal for local-first or offline-capable apps.
- MySQL — A popular client-server database. Your app connects to a remote MySQL instance over TCP. Useful when your desktop app is a client for a shared backend.
- PostgreSQL — A powerful open-source relational database. Similar to MySQL in deployment model, but uses a different wire protocol and SQL dialect. Often chosen for complex queries or JSON support.
Each engine is enabled by a Cargo feature flag. If you don’t enable at least one, the plugin compiles but will throw a runtime error when you try to open a connection.
No database driver enabled:
Forgetting to enable a database feature in Cargo.toml is the most common setup mistake. The error reads invalid connection url: … - No database driver enabled!. The fix is simple: add features = ["sqlite"] (or mysql, postgres) to the plugin dependency, which this guide covers in the setup section.
Core Capabilities
The plugin’s JavaScript API exposes four main operations. You will encounter all of them as you build real features.
- load / get — Open a connection pool to a database.
loadis asynchronous and waits for the connection to be established;getreturns immediately and defers the actual connection until the first query. - execute — Run statements that modify data or schema:
INSERT,UPDATE,DELETE,CREATE TABLE, and similar. - select — Run
SELECTqueries and retrieve typed rows. You can pass bind parameters to prevent SQL injection. - close — Gracefully shut down a connection pool when the database is no longer needed.
On the Rust side, the plugin also supports database migrations — a way to version-control your schema changes directly in Rust code so the database structure stays in sync with your application version.
How the Plugin Works Under the Hood
The plugin registers Tauri commands that the JavaScript frontend can invoke. When you call Database.load("sqlite:test.db"), the frontend sends a message to the Rust backend, which opens a connection pool using sqlx. All subsequent queries flow through that pool.
This architecture keeps the database logic in Rust, where sqlx can compile queries and manage connections safely, while you write ordinary JavaScript (or TypeScript) to interact with the data.
Because the frontend can execute arbitrary SQL by default, the plugin enforces a permission system. You must explicitly allow the commands your app needs (like sql:allow-execute). This prevents an attacker who gains control of the frontend from running destructive queries you never intended to expose. See Permissions & Security for the deny-by-default model.
Setting Up the Plugin
The setup involves both Rust and JavaScript dependencies. You can use the automatic Tauri CLI or perform each step manually. The general Installing Plugins guide matches this flow.
Step 1: Add the plugin via the Tauri CLI
Run this command from your project root. It adds the Rust crate to src-tauri/Cargo.toml and installs the JavaScript package.
npm run tauri add sql
Step 2: Enable your database engine
By default, no database driver is activated. Choose the engine you need and run the corresponding command in the src-tauri folder.
cargo add tauri-plugin-sql --features sqlite
Replace sqlite with mysql or postgres if you need a different engine. You can enable more than one.
After these steps, the plugin is installed but not yet usable from the frontend.
Registering the Plugin in Rust
The Rust side needs to know about the plugin at startup. The automatic CLI method may already have added the line to lib.rs, but it’s worth verifying.
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_sql::Builder::default().build())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
This single call registers all the SQL commands (load, execute, select, close) with Tauri’s IPC system. Without it, the frontend will not find any of the database functions.
lib.rs vs main.rs:
Tauri v2 templates keep the application logic in lib.rs and leave main.rs as a thin wrapper that calls run(). Always place plugin registration inside lib.rs, not main.rs. If the official docs for a plugin mention main.rs, it’s likely an older example; the maintainers recommend using lib.rs.
Using the Plugin in Your React Frontend — A First Example
After installation and registration, you can import the Database class and open a connection. The following React component opens a SQLite database, creates a table if it doesn’t exist, inserts a row, and reads it back. It serves as a smoke test to confirm the plugin works end to end.
import { useState, useEffect } from "react";
import Database from "@tauri-apps/plugin-sql";
function App() {
const [message, setMessage] = useState("Connecting...");
useEffect(() => {
async function testDatabase() {
// SQLite connection string. The path is relative to the app's config directory.
const db = await Database.load("sqlite:test.db");
// Create a minimal table
await db.execute(
"CREATE TABLE IF NOT EXISTS hello (id INTEGER PRIMARY KEY, greeting TEXT)"
);
// Insert a row
await db.execute("INSERT INTO hello (greeting) VALUES ($1)", [
"Hello from Tauri SQL plugin!",
]);
// Read it back
const rows = await db.select<{ greeting: string }[]>(
"SELECT greeting FROM hello"
);
if (rows.length > 0) {
setMessage(rows[0].greeting);
} else {
setMessage("No data found");
}
}
testDatabase().catch((err) => setMessage(`Error: ${err}`));
}, []);
return (
<main className="p-4">
<p>{message}</p>
</main>
);
}
export default App;
The connection string "sqlite:test.db" tells the plugin to use SQLite and store the database file in the app’s configuration directory. For MySQL or PostgreSQL, you would use a full URL like "mysql://user:password@host/database".
The $1 placeholder follows SQLite’s parameter syntax. When you switch to MySQL, you need to change the placeholders to ?. The plugin adopts sqlx’s query syntax, so the placeholder style depends on the database engine.
Everything is working correctly:
If your React app displays the greeting string without errors, the plugin is installed, registered, and permitted correctly. This test covers the entire IPC pipeline from frontend to Rust database driver and back.
Permissions You Must Configure
By default, all SQL plugin commands are denied. You need to explicitly allow the ones your app uses in the capabilities configuration.
Open src-tauri/capabilities/default.json (or whichever capability file your app uses) and add the required permissions.
{
"permissions": [
"core:default",
"sql:default",
"sql:allow-load",
"sql:allow-execute",
"sql:allow-select"
]
}
sql:default— enables read-only operations and connection management (load, close, select). You must still addsql:allow-loadandsql:allow-selectif you use them explicitly, because the default permission set only grants the capability; theallow-*strings enable the specific IPC command.sql:allow-execute— required for any statement that modifies data or schema (INSERT,UPDATE,DELETE,CREATE TABLE, etc.).sql:allow-loadandsql:allow-select— needed to open connections and run queries, respectively.
Missing permissions cause silent failures:
If you forget to add sql:allow-execute but call db.execute(), the frontend promise will reject with a permissions error. The error message will include the name of the missing permission, making it easy to identify what to add. Always grant the minimum set of permissions your feature actually uses.
The full list of permission identifiers is:
| Permission | Controls |
|---|---|
sql:allow-load | Opening database connections |
sql:allow-close | Closing database connections |
sql:allow-execute | Running non-SELECT statements |
sql:allow-select | Running SELECT queries |
sql:deny-* variants | Explicitly revoke a permission |
You only need the deny variants if you want to override a broader permission set from another capability file.
With the plugin installed, registered, and permitted, you are ready to establish and manage database connections.