The SDK is free to try. Production use, or shipping on any origin other than your development one, needs a commercial license with an unlocked, origin-bound build.
WebDecks SDK
A TypeScript API for the browser-native WebDecks engine. Open a document, spreadsheet or deck, read and edit its model, present it live on a WebGPU canvas, and export to the real Office formats — all from your own app, over one small, versioned C ABI.
WebDecks is a from-scratch office engine compiled to WebAssembly and drawn entirely on the GPU (see Technology). The SDK (@squareys/webdecks-sdk) is the supported way to build on it: a thin, zero-garbage wrapper that speaks to the wasm through curated*_sdk_* exports — not the raw engine surface. It is the same layer the WebDecks apps are built on, so what you get is dogfooded by construction.
Three engines share one core. Each is a module class you attach to a loaded wasm build, and each exposes an identical shape:
DocumentModule→.documents—Document,Paragraph,Run,Table,TableCell,InlineImage,Comment,DocumentSelection.SpreadsheetModule→.spreadsheets—Spreadsheet,Sheet,Cell,Range,Chart,PivotTable,SheetSelection.PresentationModule→.presentations—Presentation,Slide,Shape,TextFrame,PresentationSelection.
Learn one and you know all three: opening, reading, editing, selection and clipboard, undo/redo, find/replace, the live viewport, and export follow the same verbs everywhere.
Other languages — on the way
The SDK is TypeScript today. C++, C# and other language bindings are on the way: the engine is driven through a small, language-agnostic C ABI over the wasm, so native and .NET wrappers are a thin layer on top of the same surface, not a rewrite. Need one now, or a language that isn't listed?Ask us — we prioritise bindings by request.
Import it
The SDK is authored in erasable-syntax-only TypeScript and resolved through its packageexports. The root entry re-exports the core plus all three engine facades; the./documents, ./spreadsheets and ./presentationssubpaths tree-shake to a single engine.
import {
DocumentModule, SpreadsheetModule, PresentationModule,
TextFormat, ParagraphStyle,
} from "@squareys/webdecks-sdk";The SDK drives an already-loaded wasm module — it never fetches or instantiates the engine itself. Loading the engine build (documents.js /documents.wasm and the UI font) is the host's job, exactly the way a bundler or a <script> would. Thelive examples show a working loader in a running editor.
A tiny editor
Attach the SDK, open a document, present it live on a canvas, edit it, and export — the whole loop, using nothing but the SDK. The page is drawn by the engine straight to the WebGPU swapchain; there is no pixel-readback preview.
import createDocumentsEngine from "@squareys/webdecks-org/documents.js"; // host's Emscripten factory
import { DocumentModule, TextFormat, ParagraphStyle } from "@squareys/webdecks-sdk";
async function main(canvas: HTMLCanvasElement) {
// 1. Load the engine wasm (the host owns this step) and attach the SDK.
const wasm = await createDocumentsEngine();
const engine = await DocumentModule.attach(wasm); // asserts the ABI version
engine.device.addEventListener("lost", () => document.body.classList.add("gpu-lost"));
if (engine.device.isSlowBackend) console.warn("WebGPU is on a slow backend.");
// 2. Open a document and read the model straight off the engine.
const doc = await engine.documents.open();
console.log(`${doc.paragraphCount} paragraphs, ${doc.wordCount} words`);
// 3. Attach the LIVE view to a real <canvas> — presents through the WebGPU swapchain.
const viewport = (await engine.createViewport(canvas)).start(); // resize + dpr automatic
// 4. Edit. Facade mutators chain and repaint the viewport automatically.
doc.selection.documentStart();
doc.insertText("Hello, WebGPU.\n");
const heading = doc.paragraph(0)!; // cached wrapper: === doc.paragraph(0)
heading.setStyle(ParagraphStyle.heading1).select();
doc.toggleFormat(TextFormat.bold); // bold the selected paragraph
// 5. Export. The bytes come from the real serializer; PNG is thumbnail-only.
const docx = await doc.export("docx"); // Uint8Array -> Blob download
const thumbnail = await doc.exportThumbnail(0, 320, 414); // PNG Blob, off the live path
// 6. Lifetime is explicit — nothing leans on the garbage collector.
viewport.stop();
doc.close();
engine.detach();
}Reading and editing the model
Reads are property getters that round-trip through a numeric handle into the engine, so a value is always current and nothing is duplicated in JavaScript. Navigation returns cached wrappers — the same handle always yields the same object, so === is the identity test. Editing mutators chain and repaint any attached viewport.
// Cheap state is a property getter — read straight from the engine, no record to fill.
const para = doc.paragraph(0)!;
console.log(para.style, para.alignment, para.textLength);
// Navigation returns cached wrappers, so identity is a === test.
for (const run of para.runs) {
const isBold = (run.format & TextFormat.bold) !== 0;
console.log(run.fontFamily, run.fontSizePixels, isBold);
}
// Hot loops: fill a buffer you own instead of allocating a string per read.
const buffer = new Uint8Array(para.textLength);
const byteLength = para.getText(buffer);The device lifecycle
A WebGPU device comes up asynchronously and can be lost (a context loss, a driver reset) or come up on a slow software backend. engine.device surfaces all of that as typed events, per module, so the host reacts honestly instead of freezing:
engine.device.addEventListener("ready", () => begin());
engine.device.addEventListener("lost", (event) =>
showReloadBanner(event.detail.reason)); // context loss / driver reset
engine.device.addEventListener("slowbackend", (event) =>
console.warn(event.detail.message)); // software / GLES-compat adapter
// Once ready, the device, adapter, and queue are real JavaScript objects.
const device = engine.device.device; // GPUDevice | nullConcepts worth knowing
- Three parallel modules. The engines are separate wasm builds; a page that needs all three attaches three modules. The uniform surface makes them look identical, so there is one
Selectionbase, oneViewport, oneGpuDevice, and uniquely-named facade classes with no collisions. - ABI-version gate.
attach()reads the engine's ABI version and hard-fails on a mismatch — a wrong or stale build is rejected at startup instead of corrupting reads. There is no silent downgrade. - Numeric handles, explicit lifetime. Every wrapper holds only a
u32handle into the engine and a back-pointer to its module;0xFFFFFFFFis the null. You open (documents.open()) and close (doc.close(),engine.detach()) explicitly — nothing relies on the garbage collector to free wasm memory. - Cached wrappers. A wrapper is built once per handle and reused forever, so
doc.paragraph(3) === doc.paragraph(3). It stores no document data; every getter reads live. - Zero-garbage getters. Single-value state is a plain property getter — no record to allocate or fill. Where a read is bulk (text, a rect, an id list), an overload fills a buffer you own (
getText(out),getRect(out)) so a live editor can poll every frame without churning the GC. - The WebGPU viewport.
engine.createViewport(canvas).start()presents the engine's live render directly to your canvas. It is damage-driven by default — facade mutators request a frame after each edit, so editing repaints on its own — with automatic resize and device-pixel-ratio, and an explicitzoom. PNG output is thumbnail/export only; the live view never touches a 2D canvas.
Next
Browse the API reference for every class and method, or open the live examples — a full React editor built on the SDK, plus copy-paste snippets for each engine.
The SDK is free to try. Production use, or shipping on any origin other than your development one, needs a commercial license with an unlocked, origin-bound build.