Use llmkit in WebAssembly
This guide runs the Rust SDK in the browser. You build it for
wasm32-unknown-unknown, then call providers from a page, a Web Worker or
Node. WebAssembly support arrives in the next Rust release.
The TypeScript SDK already runs in browsers without WebAssembly. Use it if your app is written in TypeScript.
Before you start
You need the WebAssembly target and a bundler such as trunk or wasm-pack:
rustup target add wasm32-unknown-unknownAdd the dependencies
Do not add tokio. It has no timer in the browser, and llmkit does not need it there.
[dependencies]
llmkit-rust = "<next release>"
wasm-bindgen-futures = "0.4"Make a call
The calls are the same as on native targets. Start them with
wasm_bindgen_futures::spawn_local, because the browser has no
multi-threaded runtime.
use llmkit::builders::google;
pub async fn generate_png(api_key: String) -> Result<Option<Vec<u8>>, llmkit::Error> {
let img = google(api_key)
.image()
.model("gemini-3.1-flash-image-preview")
.aspect_ratio("16:9")
.generate("A lighthouse on a granite island at dusk")
.await?;
Ok(img.images.first().map(|image| image.bytes.clone()))
}
pub fn start(api_key: String) {
wasm_bindgen_futures::spawn_local(async move {
match generate_png(api_key).await {
Ok(Some(png)) => { /* show the image */ }
Ok(None) => { /* the model returned no image */ }
Err(err) => { /* show err.to_string() */ }
}
});
}Check that the browser can reach the provider
A browser can read a provider’s response only when the provider sends CORS headers. Ask llmkit before you call:
use llmkit::providers::info;
use llmkit::ProviderName;
let direct = info(ProviderName::Google).browser_callable;Today only Google returns true. For any other provider, send requests
through your own proxy and point the client at it:
use llmkit::builders::anthropic;
let c = anthropic(user_token).base_url("https://your-app.example.com/proxy/anthropic");The proxy must forward only the headers the provider needs. It must not forward the browser’s cookies.
Keep API keys safe
Code in a browser is readable by its user. A key in the page belongs to that user. Either let users enter their own key, or keep your key on the proxy.
What differs from native
- Streaming.
streamreads the whole response before it calls your callback, so every delta arrives at once when the response completes. - Timeouts. The client’s timeout has no effect. The request waits as long as the browser’s fetch allows.
- Futures are not
Send. Awaiting aVideoHandle,BatchHandleorTranscriptionHandlereturns a future withoutSend. Browser futures are neverSend. - No file system or environment. Uploading from a path, reading AWS
credentials from the environment, and the built-in HTTP telemetry exporter
return an error. Upload from bytes, pass credentials explicitly, and write
your own telemetry
exportcallback.
Video generation polls with setTimeout, so VideoHandle waits work in a
page and in a worker.