Package Information
Available Nodes
Documentation generating…
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-workflowpeer dependency pulls inisolated-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:
- the node's Chromium Executable Path option,
N8N_CHROMIUM_PATH, thenPUPPETEER_EXECUTABLE_PATH, thenCHROME_PATH,- well-known paths (
/usr/bin/chromium,/usr/bin/google-chrome, the macOS app bundles, …), - 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 frompackage.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
- create
nodes/<Name>/with<Name>.node.ts,descriptions.ts, the logic in its own module, two
SVG icons and aREADME.md, - add the compiled path to
n8n.nodesinpackage.json, - keep the logic free of n8n types and throw
OperationErrorfrom it, so it is unit-testable and
errors reach the user with a useful description, - add
test/<name>.test.ts, - 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-coreis pinned to the 24.x line; 25.x is ESM-only and cannot berequire()d on
Node 20.pdfjs-distis ESM-only from v4 on ("legacy" there means an older JS target, not CommonJS), so it
is loaded through a real dynamicimport(). TypeScript compiling to CommonJS would rewriteimport()intorequire()and break that, soconverters/pdf/pdfjs.tsbuilds the import vianew Function. The version is pinned to~5.6.x: 5.7 and 6.x require Node 22.13+.- Jest sandboxes dynamic
import(), which is whynpm testruns 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,yargsandzlibjs— these packages publish bundled, minifieddistfiles, andtesseract.js-coreis
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)')inconverters/pdf/pdfjs.ts. It runs a fixed string with no external input,
and exists only because TypeScript compiling to CommonJS would otherwise rewrite the dynamicimport()that ESM-onlypdfjs-distrequires. 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)