HTTP Plugin
Learn how to use the HTTP plugin to send secure network requests from a Tauri v2 application using a Rust backend that respects URL scope restrictions
The HTTP plugin gives a Tauri application the ability to make HTTP requests from JavaScript running in the webview. Instead of using the browser’s built‑in fetch, which would be subject to the browser’s same‑origin policy and won’t work in many desktop‑app scenarios, this plugin sends requests through a Rust HTTP client running in the Tauri backend. The HTTP Plugin introduction explains why that move out of the webview matters.
The JavaScript API is modeled after the standard Fetch API so that developers feel at home immediately. The Rust side is a re‑export of the popular reqwest crate, so backend code can use the same HTTP client with an identical security pipeline.
No open internet by default:
The plugin will block every URL until you configure an allowlist in your capabilities file. This is an intentional security boundary that prevents your app from exfiltrating data or being abused as an open proxy. Always restrict to the smallest set of domains you actually need.
Setup
You need to install both the Rust crate and the JavaScript bindings, then register the plugin and configure the URL scope. Installing Plugins is the same four-step pattern used here.
Automatic installation
The Tauri CLI can add both dependencies and update the necessary source files in one step. Run the command for your package manager:
npm run tauri add http
After the command finishes, the plugin is already initialized in src-tauri/src/lib.rs and the JavaScript package is in your node_modules.
Check your lib.rs:
If you ran the automatic command, you should see .plugin(tauri_plugin_http::init()) already added to the builder chain. If not, add it manually as shown.
Manual installation
If you prefer to wire everything by hand, follow these steps in order.
Add the Rust crate
In src-tauri/Cargo.toml, add the dependency:
[dependencies]
tauri-plugin-http = "2"
Register the plugin in lib.rs
Open src-tauri/src/lib.rs and call .plugin() on the builder:
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_http::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Install the JavaScript package
Choose your package manager:
npm install @tauri-apps/plugin-http
Configure the URL scope
The plugin will reject every request until you declare which URLs are allowed. Edit your capabilities file (usually src-tauri/capabilities/default.json) and add the http:default permission with an allowlist:
{
"permissions": [
{
"identifier": "http:default",
"allow": [{ "url": "https://api.example.com/**" }],
"deny": [{ "url": "https://api.example.com/internal/**" }]
}
]
}
The allow list accepts glob patterns. deny is optional and overrides allow for specific sub‑paths. If your app needs to reach multiple APIs, you can add several entries to the allow array.
At this point you can import and use fetch in your frontend code.
Making Requests
The plugin exports a fetch function that mirrors the browser’s global fetch. It accepts a URL and an optional configuration object, and it returns a Promise that resolves to a Response. Making Requests covers GET, POST, PUT, DELETE, proxies, and timeouts.
A simple GET request
import { fetch } from '@tauri-apps/plugin-http';
const response = await fetch('https://api.example.com/data.json');
console.log(response.status); // e.g. 200
console.log(response.statusText); // "OK"
const json = await response.json();
console.log(json);
This sends a GET request to the allowed URL. The response object works exactly like the web Response — you can call .json(), .text(), or .arrayBuffer() to extract the body. All of that happens in the Rust backend, not in the webview’s JavaScript engine.
Always check the URL scope:
If the URL does not match any pattern in your allow list, the promise will be rejected with a permission error. The request never leaves your machine.
Sending data with POST
You can send JSON, plain text, or binary data. The body field accepts strings, Uint8Array, or a plain object that will be serialized to JSON automatically.
import { fetch } from '@tauri-apps/plugin-http';
const response = await fetch('https://api.example.com/items', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'New item', quantity: 3 }),
});
const createdItem = await response.json();
console.log(createdItem);
If you pass a plain object directly as body, the plugin will JSON‑stringify it for you — but you still need to set the Content-Type header yourself.
Custom headers and authentication
The headers field is a plain object of key‑value pairs. Header names are case‑insensitive on the receiving side.
const response = await fetch('https://api.example.com/me', {
headers: {
Authorization: 'Bearer tok_abc123',
Accept: 'application/json',
},
});
console.log(await response.json());
All standard HTTP headers are supported. Keep in mind that custom headers like Authorization are sent directly from the Rust backend, so they aren’t subject to any browser‑specific CORS restrictions.
Timeouts and redirect behavior
The plugin exposes several options that go beyond the standard Web API because the underlying Rust client gives you finer control.
| Option | Type | Description |
|---|---|---|
connectTimeout | number | Timeout in milliseconds for the initial TCP connection. |
maxRedirections | number | Maximum number of redirects the client will follow. Set to 0 to disable. |
proxy | object | Proxy configuration (see below). |
danger | object | Danger‑zone settings for disabling SSL verification. |
Example with a connection timeout and no redirects:
const response = await fetch('https://api.example.com/slow', {
method: 'GET',
connectTimeout: 5000, // 5 seconds
maxRedirections: 0,
});
If the timeout is hit, the promise rejects with an error.
Using a proxy
If your users need to route traffic through a corporate proxy, the plugin supports HTTP, HTTPS, and a catch‑all proxy.
const response = await fetch('https://api.example.com/data', {
proxy: {
all: 'http://proxy.corp.local:8080',
},
});
You can also set separate proxies for HTTP and HTTPS, and even supply basic authentication credentials:
const response = await fetch('https://api.example.com/data', {
proxy: {
https: {
url: 'http://proxy.corp.local:8443',
basicAuth: {
username: 'user',
password: 'pass',
},
},
noProxy: 'localhost,*.internal.local',
},
});
noProxy is a comma‑separated list of hosts that should bypass the proxy.
Disabling SSL verification (dangerous)
For testing environments with self‑signed certificates, you can relax certificate and hostname verification.
Never use in production:
Disabling SSL verification exposes your app to man‑in‑the‑middle attacks. Only ever enable this for local development with self‑signed certificates, and never distribute an app with these settings on.
const response = await fetch('https://localhost:8443/api', {
danger: {
acceptInvalidCerts: true,
acceptInvalidHostnames: true,
},
});
Making requests from Rust code
The plugin re‑exports the reqwest crate under tauri_plugin_http::reqwest. This means any Rust code in your Tauri app can use the exact same HTTP client that the JavaScript side uses — same security pipeline, same domain allowlist.
use tauri_plugin_http::reqwest;
let res = reqwest::get("https://api.example.com/data.json").await?;
println!("{:?}", res.status());
let body = res.text().await?;
println!("{}", body);
The URL must be covered by your capabilities scope just like a request from JavaScript. If it isn’t, the request will fail.
Working with Responses
The Response object you get back is a close match to the web’s Response. You read the body once using .json(), .text(), or .arrayBuffer(), and you can inspect status, headers, and the final URL after any redirects. See Working with Responses for status checks and error handling.
Reading the body
const response = await fetch('https://api.example.com/data');
// As a parsed JSON object
const data = await response.json();
// As a plain string
const text = await response.text();
// As raw bytes
const bytes = await response.arrayBuffer();
// bytes is an ArrayBuffer — convert to Uint8Array if needed
const uint8 = new Uint8Array(bytes);
Each body‑reading method consumes the body; you can only call one of them per response.
Inspecting status and headers
const response = await fetch('https://api.example.com/items');
console.log(response.status); // HTTP status code, e.g. 200 or 404
console.log(response.statusText); // "OK", "Not Found", etc.
console.log(response.ok); // true if status is in the 200–299 range
console.log(response.url); // final URL after redirects
console.log(response.redirected); // true if the request was redirected
console.log(response.headers); // A Headers object (case‑insensitive)
const contentType = response.headers.get('content-type');
response.headers behaves like the standard Headers class: you can use .get(), .has(), .forEach(), and iterate over entries.
Handling errors
A rejected promise means the request failed at the network level or was blocked by the plugin’s security policy. A successful HTTP response that returns a status code like 404 or 500 does not reject the promise — you must check response.ok or response.status yourself.
import { fetch } from '@tauri-apps/plugin-http';
try {
const response = await fetch('https://api.example.com/private');
if (!response.ok) {
console.error(`Server returned ${response.status}`);
return;
}
const data = await response.json();
} catch (error) {
// Network error, timeout, or a URL blocked by the scope
console.error('Request failed:', error);
}
Permission errors surface as rejections:
If the URL does not match your allowlist, the promise rejects immediately with an error whose message indicates denied access. Always wrap fetch calls in try/catch.
Cancelling an in‑flight request
The plugin supports aborting requests via the standard AbortController API. You create a controller, pass its signal into the fetch options, and call .abort() whenever you need to cancel.
import { fetch } from '@tauri-apps/plugin-http';
const controller = new AbortController();
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('https://api.example.com/slow', {
signal: controller.signal,
});
} catch (error) {
if (error.name === 'AbortError') {
console.log('Request was cancelled');
}
}
This will invoke the fetch_cancel command on the Rust side, which drops the underlying connection. The promise rejects with an AbortError.
Security and Permissions Deep Dive
The HTTP plugin’s permission system is what keeps a Tauri app from becoming a general‑purpose network client. Understanding it helps you debug blocked requests and avoid shipping overly permissive apps.
The default permission set (http:default) includes:
allow-fetch— to issue requestsallow-fetch-cancel— to abort requestsallow-fetch-send— to send request bodiesallow-fetch-read-body— to read response bodiesallow-fetch-cancel-body— to cancel while a body is being transferred
These are all enabled by default, but the URL scope is empty. That’s why you must explicitly define allow entries.
The scope pattern uses glob syntax:
https://api.example.com/**— matches any path under that domainhttps://*.example.com— matches all subdomainshttp://localhost:3000/**— commonly used for development
The deny list takes priority. If a URL matches both allow and deny, the request is rejected. This lets you whitelist a broad domain but carve out sensitive endpoints.
Why Rust instead of the browser’s fetch?:
In a desktop Tauri app, the webview does not have the same origin‑bound sandbox as a regular browser tab. That means you can reach any network endpoint, but you lose the safety net of the same‑origin policy. The plugin uses Rust’s HTTP stack to give you the freedom to access external APIs while enforcing a declarative security policy that is checked before any bytes leave your app.
Introduction to the HTTP Plugin
Understand what the Tauri HTTP plugin is, why it exists, how native requests differ from browser requests, and the security model that controls access.
Making Requests
How to send HTTP requests from the frontend of a Tauri v2 application using the HTTP plugin
Working with Responses
Process HTTP responses returned by the Tauri HTTP plugin - parse JSON bodies, read headers, check status codes, and handle errors properly in a React frontend.