Guide
HTML to PDF in Tauri
Tauri splits your desktop app into two processes: a Rust core that handles system calls and secrets, and a WebView frontend that handles the UI. PDF generation belongs in the Rust core, where your API key never touches the renderer. You define a generate_pdf Tauri command in Rust, call it from your TypeScript frontend with invoke(), and get a document URL back. No Chromium subprocess to manage, no file system temp directories, and the key stays out of the WebView entirely.
Tauri command
Annotate a Rust function with #[tauri::command] to expose it to the frontend. The function reads PDFPIPE_API_KEY from the environment, posts the HTML to the PDF API with store: true, and returns the response JSON. Errors are surfaced as Err(String) so the frontend can catch them cleanly:
// src-tauri/src/main.rs
use tauri::command;
use std::env;
#[derive(serde::Deserialize)]
struct PdfApiResponse {
document_url: Option<String>,
document_id: Option<String>,
}
#[command]
async fn generate_pdf(html: String, filename: String) -> Result<serde_json::Value, String> {
let api_key = env::var("PDFPIPE_API_KEY")
.map_err(|_| "PDFPIPE_API_KEY is not set".to_string())?;
let client = reqwest::Client::new();
let body = serde_json::json!({
"html": html,
"store": true,
"filename": filename,
"options": { "format": "A4" }
});
let response = client
.post("https://api.pdfpipe.xyz/v1/pdf")
.header("Authorization", format!("Bearer {}", api_key))
.header("Content-Type", "application/json")
.json(&body)
.send()
.await
.map_err(|e| e.to_string())?;
if !response.status().is_success() {
let err: serde_json::Value = response.json().await.unwrap_or_default();
return Err(err["detail"]
.as_str()
.unwrap_or("render failed")
.to_string());
}
let result: serde_json::Value = response.json().await.map_err(|e| e.to_string())?;
Ok(result)
}Registering the command
Pass the function to tauri::generate_handler! in your main.rs. Tauri uses the macro to wire the command name to the Rust function at compile time:
// src-tauri/src/main.rs — register the command
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![generate_pdf])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}Frontend invocation
From your TypeScript frontend, call the command with invoke() from @tauri-apps/api/core. The argument names must match the Rust function parameters exactly. The return value is the JSON body from the API, which includes document_url for the stored PDF:
// In your TypeScript/Vite frontend
import { invoke } from "@tauri-apps/api/core";
interface PdfResult {
document_url: string;
document_id: string;
}
async function exportAsPdf(html: string, filename: string): Promise<string> {
const result = await invoke<PdfResult>("generate_pdf", { html, filename });
return result.document_url;
}
// Example usage from a button handler:
const url = await exportAsPdf(
"<h1>My Report</h1><p>Generated by my Tauri app.</p>",
"my-report.pdf"
);
console.log("PDF available at:", url);Cargo.toml dependencies
Add reqwest for the HTTP client, serde_json for JSON handling, and tokio as the async runtime. The default-features = false on reqwest keeps the binary lean by omitting TLS backends you do not need on a desktop target:
# src-tauri/Cargo.toml — add to [dependencies]
[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
[dependencies.reqwest]
version = "0.12"
features = ["json"]
default-features = falseEnvironment variable setup
Set PDFPIPE_API_KEY as an OS environment variable before launching the app. For local development a .env file works well when sourced before cargo tauri dev. For production builds, set it in CI or use a platform keychain integration. The key is read by the Rust core process only and is never bundled into the frontend assets or accessible from the WebView:
# .env (project root, for local development)
PDFPIPE_API_KEY=your_api_key_here
# Load it before running tauri dev:
# On Unix: export $(cat .env | xargs) && cargo tauri dev
# On Windows PowerShell:
# $env:PDFPIPE_API_KEY = "your_api_key_here"; cargo tauri dev
# For production builds, set the variable in your CI environment or
# via your OS keychain integration. Never embed the key in the
# compiled binary or expose it in the WebView/frontend bundle.Getting an API key
The Hobby plan gives 500 free documents a month with no credit card required. Paid plans start at $19 and include a longer document archive, higher limits, and email support.
Full API reference on the docs page. Try it now in the live playground with no signup.
Get an API key →