Documentation

n8n-nodes-absysco

A set of n8n community nodes for document work and workflow plumbing.

Each node has its own documentation — click through for parameters, examples and limitations.

Node What it does Extra requirements
Format Convert HTML → PDF, PDF → HTML, PDF → Text Chromium, for HTML → PDF
Schema Guard Validates items against a JSON Schema and routes the ones that do not fit to a second output, with an explanation of what was wrong none
Async Job Poller Polls a job status endpoint until it succeeds or fails, with backoff, Retry-After support and a time budget none
QR Code Generates QR codes for text, links, WiFi networks and contact cards, and reads them back out of images none
OCR Reads text from images with Tesseract, compiled to WebAssembly language data
Template Render Renders Handlebars or Nunjucks templates with item data, including partials and template inheritance none

Requirements

Node.js 22 or newer. Everything works straight after installing, except HTML → PDF.

Node 22 or newer is required, not Node 20. The n8n-workflow peer dependency pulls in
isolated-vm, which publishes prebuilt binaries only for the Node 22 and 24 ABIs and whose sources
do not compile against Node 20's V8 headers — so on Node 20 the install itself fails, before any
node is loaded. This was found by CI, not by reading changelogs.

Works out of the box Schema Guard, Async Job Poller, QR Code, OCR, Template Render, and Format Convert's PDF → HTML and PDF → Text
Needs one extra step Format Convert's HTML to PDF

HTML → PDF renders with a real browser, and npm cannot install a browser. It is therefore the one
opt-in feature — puppeteer-core is an optional peer dependency, which npm does not install
automatically:

cd ~/.n8n/nodes && npm install puppeteer-core@^24

…plus a Chromium binary on the host, see below. Until then the operation
still appears in the UI and fails with a message naming that exact command, so nothing breaks
silently and the other five nodes are unaffected.

OCR needs nothing extra. The engine ships with the package; language data is downloaded on first
use, or provided locally on hosts without internet access.

Installation

Deployment guide — three installation paths, Chromium and OCR setup, a
step-by-step verification checklist, troubleshooting and production notes.

The short version, for self-hosted n8n: Settings → Community nodes → Install, package name:

n8n-nodes-absysco

Or manually:

cd ~/.n8n/nodes           # create it if missing, and `npm init -y` once
npm install n8n-nodes-absysco

Restart n8n afterwards — node definitions are read at startup.

If you need HTML → PDF, the image also needs a Chromium binary, so the Docker-image path in the
deployment guide is the one to follow.

Chromium is not bundled

The package depends on puppeteer-core, which deliberately ships no browser: bundling Chrome
would add ~150 MB to every install, and the official n8n image is Alpine-based where prebuilt Chrome
binaries do not run anyway. Install a browser in your n8n image instead:

FROM n8nio/n8n:latest
USER root
RUN apk add --no-cache chromium nss freetype harfbuzz ttf-freefont
ENV N8N_CHROMIUM_PATH=/usr/bin/chromium
USER node

On Debian-based images: apt-get install -y chromium fonts-liberation.

The browser is located in this order:

  1. the node's Chromium Executable Path option,
  2. N8N_CHROMIUM_PATH, then PUPPETEER_EXECUTABLE_PATH, then CHROME_PATH,
  3. well-known paths (/usr/bin/chromium, /usr/bin/google-chrome, the macOS app bundles, …),
  4. Playwright browser directories ($PLAYWRIGHT_BROWSERS_PATH, /opt/pw-browsers, /ms-playwright).

If none match, the node fails with a message naming the fix rather than a raw spawn error.

Since puppeteer-core is only an optional peer dependency, a default install has no browser
dependency at all. The package does have other runtime dependencies (pdfjs-dist, tesseract.js,
ajv, qrcode, handlebars, …), so it is not eligible to be a verified n8n community node —
verified nodes must be dependency-free. Install it as a regular community node, which on self-hosted
n8n works from Settings → Community nodes.

Development

npm install
npm run build      # tsc -> dist/, then copies node icons
npm run lint
npm test
npm run format

Always go through npm run …. The local typescript 5.9 and eslint 9 are pinned deliberately; a
globally installed newer version produces different diagnostics.

Tests that need external resources self-skip when they are missing, so npm test stays green on a
bare machine. Provide them to run those tests for real:

N8N_CHROMIUM_PATH=/usr/bin/chromium \
N8N_TESSDATA_PATH=/opt/tessdata \
npm test

CI supplies both, so the browser-rendering and OCR tests actually execute there.

Releasing

Bump the version, write its CHANGELOG.md section, merge — then press one button:

npm version 0.2.0 --no-git-tag-version
# add the 0.2.0 section to CHANGELOG.md
git commit -am "release: 0.2.0" && git push

Actions → Publish Package → Run workflow (on the default branch).
.github/workflows/publish.yml reads the version from
package.json, creates and pushes the v0.2.0 tag itself, publishes to npm with provenance and
creates the GitHub Release with that version's CHANGELOG.md section as its notes.
There is no version to type anywhere, so the tag, the package and the notes cannot disagree.

Pushing a v* tag by hand still works and does the same thing, minus the tagging step.

The run refuses to start if the version was already released from a different commit, if a manual run
is not on the default branch, or if a pushed tag disagrees with package.json. It runs the full
suite with a real browser and OCR data, and skips publishing or releasing anything that already
exists — so re-running is safe. Tick dry_run to rehearse without publishing. Needs one
repository secret, NPM_PUBLISH; setup is in the
deployment guide.

To try the nodes in a real n8n UI:

npm run build && npm link
mkdir -p ~/.n8n/custom && cd ~/.n8n/custom && npm init -y
npm link n8n-nodes-absysco
N8N_CHROMIUM_PATH=/usr/bin/chromium npx n8n

Repository layout

nodes/                      one directory per node, each with its own README
  FormatConvert/            HTML <-> PDF <-> text
  SchemaGuard/              JSON Schema validation, two outputs
  AsyncJobPoller/           submit -> poll -> result
  QrCode/                   generate and read
  Ocr/                      Tesseract recognition
  TemplateRender/           Handlebars and Nunjucks
converters/                 conversion logic behind one Converter interface
shared/                     helpers used by every node
test/                       one suite per node, plus shared/ and round-trip suites
scripts/                    build helpers: clean dist, copy icons, extract release notes

Every node is registered in the n8n.nodes array of package.json, and CI fails if a declared node
is missing from dist/ or lacks its icons — the classic reason a node silently fails to appear.

shared/ holds what the nodes genuinely have in common: errors.ts (an OperationError carrying a
description, plus the mapper to NodeOperationError), io.ts (reading binary input, writing the
result to a binary field or a json field), paths.ts (dotted-path get/set) and properties.ts (the
generated "Output To" fields, so four nodes offer that choice identically).

Adding a node

  1. create nodes/<Name>/ with <Name>.node.ts, descriptions.ts, the logic in its own module, two
    SVG icons and a README.md,
  2. add the compiled path to n8n.nodes in package.json,
  3. keep the logic free of n8n types and throw OperationError from it, so it is unit-testable and
    errors reach the user with a useful description,
  4. add test/<name>.test.ts,
  5. link the new README from the table at the top of this file.

To add a conversion to the existing Format Convert node instead, see
its README.

Module format

n8n loads community nodes with CommonJS require(), which constrains the runtime dependencies:

  • puppeteer-core is pinned to the 24.x line; 25.x is ESM-only and cannot be require()d on
    Node 20.
  • pdfjs-dist is ESM-only from v4 on ("legacy" there means an older JS target, not CommonJS), so it
    is loaded through a real dynamic import(). TypeScript compiling to CommonJS would rewrite
    import() into require() and break that, so converters/pdf/pdfjs.ts builds the import via
    new Function. The version is pinned to ~5.6.x: 5.7 and 6.x require Node 22.13+.
  • Jest sandboxes dynamic import(), which is why npm test runs Jest under
    --experimental-vm-modules.

Every other dependency (ajv, qrcode, jsqr, pngjs, jpeg-js, tesseract.js, handlebars,
nunjucks) ships a CommonJS entry point and was checked to load under require() before being
adopted.

Dependency scanner alerts

Supply-chain scanners flag some of these dependencies. Recorded here so nobody has to re-triage it:

  • "Obfuscated code" on puppeteer-core, tesseract.js, tesseract.js-core, yargs and
    zlibjs
    — these packages publish bundled, minified dist files, and tesseract.js-core is
    largely WebAssembly. Heuristics that score identifier entropy read that as obfuscation. All five
    are mainstream packages, pulled in directly or transitively by the browser-rendering and OCR
    features; clearing the alerts would mean dropping those features.
  • Nothing in this repository is minified or generated, and dist/ is not committed.
  • The one construct here that looks like an eval is new Function('specifier', 'return import(specifier)') in converters/pdf/pdfjs.ts. It runs a fixed string with no external input,
    and exists only because TypeScript compiling to CommonJS would otherwise rewrite the dynamic
    import() that ESM-only pdfjs-dist requires. See Module format.

Roadmap

  • OCR for PDFs, by rasterising pages first — scanned PDFs currently yield no text at all
  • Rebuild detected column boundaries into real <table> markup
  • Extract embedded images from PDFs
  • Markdown → HTML / PDF
  • HTML → PNG (screenshot), reusing the existing browser session
  • DOCX / XLSX → PDF via headless LibreOffice
  • Rendering directly from a URL as a third HTML → PDF input source
  • Barcode formats beyond QR (Code128, EAN, DataMatrix)

License

MIT

Discussion