Compile PDFluent to WASM and use it in the browser

Run PDF processing entirely client-side. No server round-trip, no file upload, no privacy risk.

rust
// src/lib.rs
use wasm_bindgen::prelude::*;
use pdfluent::PdfDocument;

#[wasm_bindgen]
pub fn page_count(bytes: &[u8]) -> Result<u32, JsValue> {
    let doc = PdfDocument::from_bytes(bytes)
        .map_err(|e| JsValue::from_str(&e.to_string()))?;
    Ok(doc.page_count())
}

#[wasm_bindgen]
pub fn extract_text(bytes: &[u8]) -> Result<String, JsValue> {
    let doc = PdfDocument::from_bytes(bytes)
        .map_err(|e| JsValue::from_str(&e.to_string()))?;
    doc.extract_text()
        .map_err(|e| JsValue::from_str(&e.to_string()))
}
Install:cargo add pdfluentDownload SDK →

Step by step

1

Add the wasm feature and wasm-bindgen

PDFluent has a wasm feature that disables std-only dependencies. wasm-bindgen generates the JS glue code.

rust
# Cargo.toml
[package]
name = "pdf-wasm"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
pdfluent = { version = "0.9", features = ["wasm"] }
wasm-bindgen = "0.2"
2

Expose Rust functions to JavaScript

Mark each function with #[wasm_bindgen]. Functions that receive PDF bytes take &[u8]. Return JsValue errors so JavaScript can catch them.

rust
use wasm_bindgen::prelude::*;
use pdfluent::PdfDocument;

#[wasm_bindgen]
pub fn page_count(bytes: &[u8]) -> Result<u32, JsValue> {
    let doc = PdfDocument::from_bytes(bytes)
        .map_err(|e| JsValue::from_str(&e.to_string()))?;
    Ok(doc.page_count())
}
3

Build the WASM module

Install wasm-pack and run the build command. This produces a pkg/ directory with .wasm and JS binding files.

rust
# Install wasm-pack
cargo install wasm-pack

# Build for the browser (ES module output)
wasm-pack build --target web --out-dir pkg

# Or build for Node.js
wasm-pack build --target nodejs --out-dir pkg
4

Import the module in JavaScript

Load the WASM module asynchronously, then pass ArrayBuffer data from a file input or fetch.

rust
import init, { page_count, extract_text } from './pkg/pdf_wasm.js';

await init();

const input = document.getElementById('file-input');
input.addEventListener('change', async (e) => {
    const file = e.target.files[0];
    const buffer = await file.arrayBuffer();
    const bytes = new Uint8Array(buffer);

    const pages = page_count(bytes);
    const text  = extract_text(bytes);

    console.log(`Pages: ${pages}`);
    console.log(`Text: ${text.slice(0, 200)}`);
});
5

Serve with correct MIME type

Browsers require .wasm files to be served with Content-Type: application/wasm. Most dev servers handle this automatically. In production, verify your CDN or server config.

rust
# Vite, webpack, and Parcel all handle WASM automatically.
# For a plain nginx config:
# types {
#     application/wasm wasm;
# }

# Quick local test with wasm-pack's built-in server:
npx serve .

Notes and tips

  • The WASM binary for a typical PDFluent build is around 1.5-2.5 MB. Use wasm-opt from binaryen to reduce it by 15-30%.
  • WASM runs in a single thread by default. For parallel operations in the browser, use Web Workers and pass PDF bytes via postMessage.
  • PDFluent in WASM mode disables features that require OS calls, such as file I/O and system fonts. All input must come through the &[u8] parameter.
  • SharedArrayBuffer (required for wasm-bindgen threads) needs the COOP and COEP headers. If you do not need threads, standard ArrayBuffer works everywhere.

Why PDFluent for this

Pure Rust

No JVM, no runtime, no DLL dependencies. Ships as a single native binary or WASM module.

Memory safe

Rust's ownership model prevents buffer overflows and use-after-free. No segfaults in PDF parsing.

Runs anywhere

Same code runs server-side, in Docker, on AWS Lambda, on Cloudflare Workers, or in the browser via WASM.

Frequently asked questions