NamahaPDF SDK
A complete, in-browser PDF editor you can drop into your own app: view, edit, annotate, sign, redact, and fill forms, fully on-device. Documents never leave the browser; there’s no upload step and no cloud dependency for editing.
The SDK ships as a family of packages — the React UI plus one headless engine per document format:
| Package | What it is | Install |
|---|---|---|
@namahapdf/react | The ready-to-use React editor component, <NamahaEditor>. | npm i @namahapdf/react |
@namahapdf/core | The headless PDF engine + license gate. No UI. | npm i @namahapdf/core |
@namahapdf/pptx | The headless PowerPoint engine — parse / render / edit / export PDF. | npm i @namahapdf/pptx |
@namahapdf/sheets | The headless Excel engine — parse / render / edit .xlsx. | npm i @namahapdf/sheets |
Most apps want @namahapdf/react. Reach for a headless engine package only when building your own UI or doing headless processing. Each engine carries its own license gate — call configureLicense() from whichever package you use.
Install
npm i @namahapdf/reactreact and react-dom (≥ 18) are peer dependencies you almost certainly already have. The package’s own runtime deps install automatically.
Quickstart: React
Render the full editor in three lines:
import { NamahaEditor } from '@namahapdf/react';
import '@namahapdf/react/styles.css'; // ← required
export default function App() {
return (
<div style={{ height: '100vh' }}>
<NamahaEditor licenseKey="YOUR_KEY" />
</div>
);
}import '@namahapdf/react/styles.css' the editor renders unstyled. The CSS is scoped under .namahapdf-root and ships without a global reset, so it won’t affect the rest of your page.Component props:
| Prop | Type | Description |
|---|---|---|
licenseKey | string | Your SDK key. Without it the editor runs watermarked with premium features gated. |
activationUrl | string | Override the activation endpoint (default: same-origin /api/license/activate). |
decryptUrl | string | Endpoint to decrypt password-protected PDFs (default: same-origin /api/decrypt-pdf). Unencrypted PDFs open in-browser with no network call. |
signUrl | string | Endpoint for secure Sign & certify (cryptographic PAdES; default: same-origin /api/sign). Quick visual sign runs in-browser with no network call. |
theme | NamahaTheme | Brand colors: pass primary/surface/text (hex) to match the editor to your brand. Applied client-side; the tonal scale is derived for you. |
className | string | Extra class on the .namahapdf-root wrapper. |
style | CSSProperties | Inline style, use it to size the editor. |
Match your brand. Pass a theme with one or more hex colors, and the tonal scale is derived for you, all client-side:
<NamahaEditor
theme={{
primary: '#2563EB', // accent: buttons, active states, focus rings
surface: '#FFFFFF', // panels & canvas
text: '#0F172A', // body text
}}
/>Frameworks
Vite / CRA / Remix: no special handling, render it on the client and import the stylesheet once.
Next.js App Router: works directly; the component is "use client".
Next.js Pages Router / strict SSR: load it client-only:
import dynamic from 'next/dynamic';
const NamahaEditor = dynamic(
() => import('@namahapdf/react').then((m) => m.NamahaEditor),
{ ssr: false },
);Headless engine: @namahapdf/core
For building your own UI or programmatic processing. Configure the license once, then parse / render / edit.
import { configureLicense, PDFParser, avniEngine, PDFEditProcessor } from '@namahapdf/core';
configureLicense({ licenseKey: 'YOUR_KEY' });
const parser = new PDFParser(new Uint8Array(await file.arrayBuffer()));
const doc = parser.parse();
if (!avniEngine.getAvailableProcessors().includes('pdf-editor-core')) {
avniEngine.registerProcessor(new PDFEditProcessor());
}
await avniEngine.runPipeline(doc, {
tasks: [{ processorName: 'pdf-editor-core', params: { operations: [
{ type: 'insert-text', pageIndex: 0, text: 'Hello', x: 72, y: 700, size: 12 },
] } }],
});Editing operations are license-gated inside the engine, so a gated op throws before any mutation if its feature isn’t licensed.
Bundled fonts: edits substitute a missing embedded font with a matching bundled OFL face (e.g. Reddit Sans, or Arimo for Arial) instead of a standard-14 look-alike. Serve the font files at /fonts/<file> or inject your own loader with setBundledFontBytesProvider(); without either, the engine falls back to the standard-14 faces.
PowerPoint & Excel engines
The same architecture powers @namahapdf/pptx and @namahapdf/sheets: parse, render, and edit .pptx / .xlsx fully on-device, then save a faithful file. Edits are small JSON EditPlan intents applied as surgical XML splices, so untouched content is preserved byte-for-byte — and the same plan an LLM emits (validate it with validateEditPlan) is the plan the UI emits.
Edit a PowerPoint deck (intent-sourced session with undo/redo):
import { PptxEditSession, configureLicense } from '@namahapdf/pptx';
configureLicense({ licenseKey: 'YOUR_KEY' });
const session = await PptxEditSession.load('deck.pptx', bytes);
await session.apply([
{ op: 'replace-text',
anchor: { fmt: 'pptx', slidePart: 'ppt/slides/slide1.xml', shapeId: '2' },
newText: 'Q3 Review' },
]);
const edited = session.bytes; // still a valid .pptxEdit an Excel workbook. Formulas are computed in-browser by a whitelisted evaluator (~50 functions) and cached into the file, so cells show their result immediately; every value edit still sets fullCalcOnLoad, so Excel recalculates authoritatively on open:
import { XlsxEditSession, configureLicense } from '@namahapdf/sheets';
configureLicense({ licenseKey: 'YOUR_KEY' });
const anchor = { fmt: 'xlsx', sheetPart: 'xl/worksheets/sheet1.xml', row: 1, col: 1 } as const;
const session = await XlsxEditSession.load('book.xlsx', bytes);
await session.apply([
{ op: 'set-cell', anchor, row: 1, col: 1, newText: '5000000' },
{ op: 'set-cell', anchor, row: 2, col: 1, newText: '=SUM(B1:B2)*1.2' },
{ op: 'insert-rows', anchor, at: 0, count: 1 },
]);
const edited = session.bytes; // still a valid .xlsxpptx-edit, sheets-view, and sheets-edit.Licensing
Free to install and try; license-gated for production. Without a valid key the editor runs in evaluation mode, fully usable for viewing/annotating, but watermarked with edit/redact/forms gated. A valid key removes the watermark and unlocks your plan’s features.
License states from getLicenseState():
| Status | Meaning | Watermark |
|---|---|---|
UNLICENSED | No key supplied. | Yes |
INVALID | Bad signature, expired, or wrong domain. | Yes |
LICENSED | Valid key + fresh activation token. | No |
GRACE | Valid key, activation stale/offline but tolerated. | No |
DEGRADED | Grace exhausted or explicitly revoked. | Yes |
Feature tiers:
| Feature | Without a license? |
|---|---|
view | Base, always available |
annotate | Base, always available |
edit-text | Licensed |
sign | Licensed |
redact | Licensed |
forms | Licensed |
export-clean | Licensed |
pptx-edit | Licensed — PowerPoint editing |
sheets-view | Licensed — Excel viewing |
sheets-edit | Licensed — Excel editing |
A valid key unlocks immediately offline (verified against an embedded public key). Online activation runs in the background to enable revocation and seat limits, with a generous offline grace window so network blips never break a paying customer.
License API
Read and react to license state from your own UI:
import {
configureLicense, getLicenseState, isFeatureEnabled,
assertFeature, shouldWatermark, onLicenseChange,
} from '@namahapdf/core';
configureLicense({ licenseKey: 'YOUR_KEY' });
getLicenseState().status; // "LICENSED" | "GRACE" | ...
isFeatureEnabled('redact'); // boolean, tailor your UI
assertFeature('redact'); // throws if not licensed
shouldWatermark(); // boolean
const off = onLicenseChange((s) => console.log(s.status)); // activation updatesThe same helpers are re-exported from @namahapdf/react for convenience.