tauri-plugins-http
Use when making HTTP requests from a Tauri v2 frontend — `fetch()` from `@tauri-apps/plugin-http`, the URL-pattern + method allowlist scope, why this exists (CORS bypass, no preflight, custom headers, proxy support), streaming responses, the `http:default` and per-method permissions, and when to use it instead of browser `fetch`.
Tauri v2: HTTP Plugin
A fetch() that runs in the Core (Rust) process instead of the WebView. Same API shape as the
browser’s Fetch, but:
- No CORS — your desktop app is not a website.
- No preflight, no opaque responses, no
mode: 'no-cors'weirdness. - Forbidden headers (
Cookie,Origin,User-Agent, etc.) are settable. - Proxy and TLS knobs (via the underlying
reqwest).
Use it whenever the response source isn’t your own dev server.
Install
npm run tauri add http
Manual: tauri-plugin-http = "2" in Cargo, @tauri-apps/plugin-http in npm,
.plugin(tauri_plugin_http::init()) in the builder.
JS API — same shape as Web Fetch
import { fetch } from '@tauri-apps/plugin-http';
const res = await fetch('https://api.example.com/v1/things', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
// Forbidden in browsers, allowed here:
'User-Agent': 'MyApp/1.0',
},
body: JSON.stringify({ name: 'thing' }),
// Plugin-specific extras:
connectTimeout: 30_000,
maxRedirections: 5,
// proxy: { all: 'http://localhost:8080' },
});
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
const data = await res.json();
Streaming responses work the same way browsers do — res.body is a ReadableStream.
const res = await fetch('https://example.com/big.bin');
const reader = res.body!.getReader();
for (;;) {
const { done, value } = await reader.read();
if (done) break;
process(value); // Uint8Array chunk
}
Critical: pick this over browser fetch when
| Case | Use |
|---|---|
| Calling your own bundled dev server | browser fetch (zero-cost) |
| Third-party API that does CORS | plugin fetch (faster, no preflight) |
Need a Cookie or User-Agent header | plugin fetch (browser blocks them) |
| Need a proxy | plugin fetch |
| Need to bypass cert validation in dev | plugin fetch with danger-accept-invalid-certs (Rust feature) |
| Streaming large download | either, plugin is fine |
The plugin re-exports reqwest on the Rust side — if you’d rather do the request from a
#[tauri::command], just use tauri_plugin_http::reqwest; and reqwest::Client::new().get(...).
Scope
Capability filters by URL and HTTP method. Both have to match.
{
"identifier": "http:default",
"allow": [
{ "url": "https://api.example.com/**" },
{ "url": "https://*.githubusercontent.com/**" }
],
"deny": [{ "url": "https://api.example.com/admin/**" }]
}
URL pattern grammar:
- Scheme is matched exactly (
https://≠http://). - Host:
*= one label,**= unused; the*.hostform is most common. - Path:
**= any tail.
Method scoping is per-permission identifier:
| Identifier | Methods |
|---|---|
http:default | Reasonable safe set (GET, HEAD, others) per current docs |
http:allow-fetch | All methods, gated only by URL allow/deny |
For a finer cut, configure on the URL entry — newer schema variants support methods: ["GET","POST"] on a per-URL basis. Prefer narrower over broader: http:default with a specific
allow list beats http:allow-fetch with a wide one.
Rust side
Re-exports reqwest. Use it for backend-only requests (no JS scope check applies since you’re
already in Core):
use tauri_plugin_http::reqwest;
#[tauri::command]
async fn fetch_status() -> Result<u16, String> {
let res = reqwest::Client::new()
.get("https://api.example.com/v1/status")
.header("user-agent", "MyApp/1.0")
.send()
.await
.map_err(|e| e.to_string())?;
Ok(res.status().as_u16())
}
unsafe-headers feature
Some headers (Origin, Cookie, Set-Cookie) are gated by default. Opt in with the
unsafe-headers Cargo feature:
tauri-plugin-http = { version = "2", features = ["unsafe-headers"] }
Only do this if you genuinely need it; the gate is there to discourage accidentally turning your client into something that smells like a logged-in browser session against APIs it shouldn’t.
Diagnosing “url not allowed”
- Scheme mismatch (
httpvshttps). - Host glob too narrow —
https://api.example.comdoes not matchhttps://api.example.com/v1unless you add the/**tail. - A
denyrule wins. Check both this capability and any window-merged ones. - Method not granted at this identifier. Try
http:allow-fetchto isolate.
Templates
templates/setup.rs— plugin init + a Rust-sidereqwestcommand.templates/usage.ts— JSfetchwith streaming and custom headers.templates/capability.json— narrow allow + deny + admin carve-out.
Related
tauri-plugins— install flow and decision matrix.tauri-security-scopes— URL pattern semantics, deny precedence.tauri-commands— for backend-side requests viareqwest.