[]
DsPdfJS is a PDF library for JavaScript and TypeScript providing fast, memory-efficient PDF and image processing in browsers and on the server.
Powered by WebAssembly, it runs in modern browsers and in Node.js, Deno, and Bun, with no runtime dependencies. Its object model follows the PDF specification, giving programmatic access to document properties, pages, fonts, annotations, and forms, alongside high-level APIs for building documents with formatted text, graphics, and images.
npm install @mescius/ds-pdf
In Node.js, this is the whole thing - the Wasm module is located automatically:
import { connectDsPdf, PdfDocument, pushObjectManager, popObjectManager } from "@mescius/ds-pdf";
import { writeFileSync } from "node:fs";
if (!(await connectDsPdf())) throw new Error("Failed to initialize DsPdfJS.");
pushObjectManager();
try {
const doc = new PdfDocument();
const page = doc.pages.addNew();
page.context.drawText({ text: "Hello, World!", fontSize: 20 }, 72, 72);
writeFileSync("hello.pdf", doc.savePdf());
} finally {
popObjectManager();
}
In the browser, add one line telling the library where the Wasm module lives - see Loading the WebAssembly module for the options.
DsPdfJS runs without a license key in Node.js, Deno, and Bun, and in a browser served
from a local host name - localhost, 127.0.0.1, ::1, a *.local or other dot-less
host name, or file://. You do not need to contact sales to try it: install the package,
run the code above, and you will get a working PDF.
Keyless builds:
A browser page served from any other host name requires a key. There is no watermarked
fallback there: connectDsPdf() rejects with License Not Found.
To remove the watermark and the page limit, and to run in a browser off localhost, request a free 30-day key (see How to Get Trial Keys) and apply it before connecting:
import { DsPdfConfig } from "@mescius/ds-pdf";
await DsPdfConfig.setLicenseKey("YOUR_LICENSE_KEY");
A commercial license is required for production use.
DsPdfJS ships the Wasm module in several forms so it can fit different build setups. Pick one of the following.
The Wasm binary is inlined into a JavaScript module, so there is no separate asset to copy, host, or resolve. This is usually the right choice for Vite, webpack, Rollup, Next.js, and edge runtimes, where asset URLs are the most common source of friction.
import { DsPdfConfig, connectDsPdf } from "@mescius/ds-pdf";
import { embeddedWasmFactory } from "@mescius/ds-pdf/wasm-embedded";
DsPdfConfig.wasmFactory = embeddedWasmFactory;
await connectDsPdf();
No wasmUrl needed. The trade-off is a larger JavaScript bundle, since the binary
travels base64-encoded inside it.
Importing the module registers the factory on its own too, but assign it explicitly as
shown above: the package is marked side-effect-free, so a bare
import "@mescius/ds-pdf/wasm-embedded"; may be dropped by tree-shaking and leave no
module registered.
Serve DsPdf.wasm as a static asset and point the library at it. This keeps your
JavaScript bundle small and lets the browser cache the binary separately.
import { DsPdfConfig, connectDsPdf } from "@mescius/ds-pdf";
DsPdfConfig.wasmUrl = "/assets/DsPdf.wasm";
await connectDsPdf();
Copy the file into your served output as part of your build, for example from
node_modules/@mescius/ds-pdf/assets/DsPdf.wasm. A CDN or versioned URL works too:
DsPdfConfig.wasmUrl = "https://cdn.example.com/v9.2/DsPdf.wasm";
If neither wasmUrl nor wasmFactory is set, DsPdfJS looks for the module under
node_modules/@mescius/ds-pdf/assets/ relative to the current working directory.
Convenient for scripts and tests; set the path explicitly for production, since the
lookup depends on the working directory. Auto-detection also prints a console.info
notice recommending an explicit path - set wasmUrl to silence it.
For full control over how the binary is fetched - a private CDN, an authenticated endpoint, a preloaded buffer:
import { DsPdfConfig } from "@mescius/ds-pdf";
import { externalWasmFactory } from "@mescius/ds-pdf/wasm-external";
DsPdfConfig.wasmFactory = async () => {
const wasmBinary = await (await fetch("https://cdn.example.com/DsPdf.wasm")).arrayBuffer();
return externalWasmFactory({ wasmBinary });
};
externalWasmFactory takes either { wasmBinary } or { wasmUrl }. DsPdfJS calls
wasmFactory() without arguments, so fetch the binary inside your factory as shown.
One factory wins.
wasmFactorytakes precedence overwasmUrl, and importing either@mescius/ds-pdf/wasm-embeddedor@mescius/ds-pdf/wasm-externalsetswasmFactoryas a side effect of the import itself, not when the factory is first called. A stray import anywhere in the app therefore overrides an Option BwasmUrlset elsewhere:wasm-embeddedquietly loads the embedded binary and your URL is ignored, whilewasm-externalmakesconnectDsPdf()returnfalse(it is invoked with no arguments, soexternalWasmFactorythrows).
Set
DsPdfConfig.verbose = trueto log which module was resolved and from where.
import {
DsPdfConfig, connectDsPdf, PdfDocument, pushObjectManager, popObjectManager
} from "@mescius/ds-pdf";
DsPdfConfig.wasmUrl = "/assets/DsPdf.wasm";
// required off localhost, see "Evaluating without a license key":
// await DsPdfConfig.setLicenseKey("...");
export async function createDocument() {
if (!(await connectDsPdf())) throw new Error("Failed to initialize DsPdfJS.");
pushObjectManager();
try {
const doc = new PdfDocument();
const page = doc.pages.addNew();
page.context.drawText({ text: "Hello, World!", fontSize: 20 }, 72, 72);
return doc.savePdf(); // Uint8Array
} finally {
popObjectManager();
}
}
<script src="node_modules/@mescius/ds-pdf/umd/ds-pdf.js"></script>
<script>
async function createDocument() {
// The UMD bundle exposes the global 'DocSol'
DocSol.DsPdfConfig.wasmUrl = "/assets/DsPdf.wasm";
// await DocSol.DsPdfConfig.setLicenseKey("..."); // required off localhost
if (!(await DocSol.connectDsPdf())) throw new Error("Failed to initialize DsPdfJS.");
const om = new DocSol.ObjectManager();
try {
const doc = new DocSol.PdfDocument(om);
const page = doc.pages.addNew();
page.context.drawText({ text: "Hello, World!", fontSize: 20 }, 72, 72);
return doc.savePdf();
} finally {
om.dispose();
}
}
createDocument()
.then(pdfData => console.log("PDF created:", pdfData.length, "bytes"))
.catch(err => console.error(err));
</script>
const { connectDsPdf, PdfDocument, ObjectManager } = require("@mescius/ds-pdf");
const { writeFileSync } = require("node:fs");
(async () => {
if (!(await connectDsPdf())) throw new Error("Failed to initialize DsPdfJS.");
const om = new ObjectManager();
try {
const doc = new PdfDocument(om);
const page = doc.pages.addNew();
page.context.drawText({ text: "Hello, World!", fontSize: 20 }, 72, 72);
writeFileSync("hello.pdf", doc.savePdf());
} finally {
om.dispose();
}
})();
Many objects are backed by Wasm memory, which is not reclaimed by the JavaScript garbage collector. Scope them with one of these patterns so intermediates are released:
// Push / pop - implicit, scoped
pushObjectManager();
try { /* ... */ } finally { popObjectManager(); }
// Explicit instance
const om = new ObjectManager();
try { /* ... */ } finally { om.dispose(); }
// Decorator
@withObjectManager
async buildReport() { /* ... */ }
Objects created inside a scope are disposed when it ends. Do not hold references to them past that point.
Pkcs7Signer and Pkcs7SignerBase classes). (DOC-7384)DocumentSecurityStore class). (DOC-7469)Signature class, representing a signature already present in a document. (DOC-7470)StructTreeRoot and StructElement classes. (DOC-7575)PdfAConformanceLevel.PdfA4, PdfA4e, PdfA4f) and for PDF/UA-2 (the Metadata.pdfUa and Metadata.pdfUaRev properties). (DOC-7682)PdfDocument.outputIntents property and the relevant classes. (DOC-7512)PdfDocument.viewerPreferences property and the ViewerPreferences class. (DOC-7471)PdfDocument.pageMode and PdfDocument.pageLayout properties. (DOC-7455)PdfDocument.pageLabelingRanges property and the related page labeling classes. (DOC-7624)PdfDocument.getFonts() method, which enumerates the fonts used in a document. (DOC-7729)PdfPage.annotationsTabsOrder property. (DOC-7511)SaveMode.Linearized member. (DOC-7655)ActionBase, such as ActionLaunch, ActionHide, ActionImportData and ActionSound. (DOC-7654)CheckBoxField members: hasRadioButtonBehavior, getCheckedAppearanceStreamName(), getCheckedAppearanceStreamNames(), setCheckedAppearanceStreamName() and setCheckedAppearanceStreamNames(). (DOC-7628)IccProfile members: the n setter, the range property and the metadata property. (DOC-7675, DOC-7676)LineBreakingRules.WhiteSpace mode, which breaks lines only at white spaces or mandatory break characters. (DOC-7651)See CHANGELOG.md inside the package for the full change history.
Free to evaluate, see Evaluating without a license key.
A commercial license is required for production use, and each purchase includes one year of updates and support. Contact MESCIUS Sales for pricing and licensing options.
JavaScript viewers: PDF Viewer and Editor · Data Viewer · Image Viewer and Editor