Developer documentation

Build with the NamahaPDF SDK

Drop a complete, on-device PDF editor into your app. Install one package, add a license key, ship.

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:

PackageWhat it isInstall
@namahapdf/reactThe ready-to-use React editor component, <NamahaEditor>.npm i @namahapdf/react
@namahapdf/coreThe headless PDF engine + license gate. No UI.npm i @namahapdf/core
@namahapdf/pptxThe headless PowerPoint engine — parse / render / edit / export PDF.npm i @namahapdf/pptx
@namahapdf/sheetsThe 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

bash
npm i @namahapdf/react

react 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:

tsx
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>
  );
}
Don’t forget the stylesheet. Without 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:

PropTypeDescription
licenseKeystringYour SDK key. Without it the editor runs watermarked with premium features gated.
activationUrlstringOverride the activation endpoint (default: same-origin /api/license/activate).
decryptUrlstringEndpoint to decrypt password-protected PDFs (default: same-origin /api/decrypt-pdf). Unencrypted PDFs open in-browser with no network call.
signUrlstringEndpoint for secure Sign & certify (cryptographic PAdES; default: same-origin /api/sign). Quick visual sign runs in-browser with no network call.
themeNamahaThemeBrand colors: pass primary/surface/text (hex) to match the editor to your brand. Applied client-side; the tonal scale is derived for you.
classNamestringExtra class on the .namahapdf-root wrapper.
styleCSSPropertiesInline 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:

tsx
<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:

tsx
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.

ts
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):

ts
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 .pptx

Edit 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:

ts
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 .xlsx
Each engine bundles its own copy of the license gate, so configure it from the package you’re using. The paid feature flags are pptx-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.

The key is not a secret. It’s a signed, domain-locked token meant to live in client code, so a key copied from your site won’t work on another domain. Your private signing key (vendor-side) is the secret.

License states from getLicenseState():

StatusMeaningWatermark
UNLICENSEDNo key supplied.Yes
INVALIDBad signature, expired, or wrong domain.Yes
LICENSEDValid key + fresh activation token.No
GRACEValid key, activation stale/offline but tolerated.No
DEGRADEDGrace exhausted or explicitly revoked.Yes

Feature tiers:

FeatureWithout a license?
viewBase, always available
annotateBase, always available
edit-textLicensed
signLicensed
redactLicensed
formsLicensed
export-cleanLicensed
pptx-editLicensed — PowerPoint editing
sheets-viewLicensed — Excel viewing
sheets-editLicensed — 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:

ts
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 updates

The same helpers are re-exported from @namahapdf/react for convenience.

Need a license key?

Issue and manage keys from the SDK portal.

Get started