SnapDOM includes a lightweight plugin system that lets you extend or override behavior at any stage of the capture and export process — without touching the core library. A plugin is a plain object with a unique name and one or more lifecycle hooks. Hooks can be synchronous or async, and they all receive a shared context object. Looking for ready-made plugins to install? See the community plugin showcase.
Official plugins
Install the official plugin package:
npm install @zumer/snapdom-plugins
import { filter } from '@zumer/snapdom-plugins/filter';
import { timestampOverlay } from '@zumer/snapdom-plugins/timestamp-overlay';
| Plugin | Category | Description |
|---|---|---|
| picture-resolver | Capture | Resolves lazy-loaded <picture> placeholders. Detects base64 stubs and fetches the real image before capture. |
| timestamp-overlay | Transform | Adds a configurable timestamp label on the captured clone. Supports multiple date formats and positions. |
| filter | Transform | Applies CSS filter effects to captures. Ships with presets: grayscale, sepia, blur, vintage, dramatic. |
| replace-text | Transform | Find-and-replace text in the captured clone. Supports strings and regex patterns. |
| color-tint | Transform | Tints the entire capture to a specified color using an overlay with mix-blend-mode. |
| ascii-export | Export | Adds a toAscii() method that converts captures to ASCII art. Configurable width, charset, and luminance. |
| pdf-image | Export | Exports the capture as a PNG embedded in a downloadable PDF. Supports portrait and landscape orientations. |
| html-in-canvas | Export | Uses the experimental WICG drawElementImage API for direct DOM-to-canvas rendering where supported. |
| prompt-export | Export | LLM-friendly capture: adds a toPrompt() method that returns an annotated screenshot, structured element map with bounding boxes, and a pre-formatted text prompt. |
Community plugins are listed on the plugins showcase page. To submit your plugin, open a PR adding one line to community-plugins.md (see CONTRIBUTING_PLUGINS.md).
Build a plugin in 5 minutes
SnapDOM's hook system gives you full control over every stage of the capture pipeline:
- Clone the template —
npx degit zumerlab/snapdom/packages/plugin-template my-plugin - Write your hook logic —
export function myPlugin() {} - Get listed — open a PR adding one line to
community-plugins.md
See PLUGIN_SPEC.md for the full specification and CONTRIBUTING_PLUGINS.md for submission guidelines.
Registering plugins
Global registration (applies to all captures):
import { snapdom } from '@zumer/snapdom';
// You can register instances, factories, or [factory, options]
snapdom.plugins(
myPluginInstance,
[myPluginFactory, { optionA: true }],
{ plugin: anotherFactory, options: { level: 2 } }
);
Per-capture registration (only for that specific call):
const out = await snapdom(element, {
plugins: [
[overlayFilterPlugin, { color: 'rgba(0,0,0,0.25)' }],
[myFullPlugin, { providePdf: true }]
]
});
- Execution order = registration order (first registered, first executed).
- Per-capture plugins run before global ones.
- Duplicates are automatically skipped by
name; a per-capture plugin with the samenameoverrides its global version.
Lifecycle hooks
Hooks run in capture order (see the capture flow):
| Hook | Stage | Purpose |
|---|---|---|
| beforeSnap | Start | Adjust options before any work. |
| beforeClone | Pre-clone | Before DOM clone (modify live DOM carefully). |
| afterClone | Post-clone | Modify cloned tree safely (e.g. inject overlay). |
| beforeRender | Pre-serialize | Right before SVG → data URL. |
| afterRender | Post-serialize | Inspect context.svgString / context.dataURL. |
| beforeExport | Per export | Before each toPng, toSvg, etc. |
| afterExport | Per export | Transform returned result. |
| afterSnap | Once | After first export; cleanup. |
| defineExports | Setup | Add custom exporters (e.g. toPdf). |
Hook order: beforeSnap → beforeClone → afterClone → beforeRender → afterRender → beforeExport → afterExport (with afterSnap firing once after the first export). Returned values from afterExport are chained to the next plugin (transform pipeline).
Context object
Every hook receives a single context object that contains normalized capture state:
- Input & options:
element,debug,fast,scale,dpr,width,height,backgroundColor,quality,useProxy,cache,outerTransforms,outerShadows,safariWarmupAttempts,compress,embedFonts,localFonts,iconFonts,excludeFonts,exclude,excludeMode,filter,filterMode,fallbackURL. - Intermediate values (depending on stage):
clone,classCSS,styleCache,fontsCSS,baseCSS,svgString,dataURL. - During export:
context.export = { type, options, url }wheretypeis the exporter name ("png","jpeg","svg","blob", etc.), andurlis the serialized SVG base.
You may safely modify context (e.g. override backgroundColor or quality) — but do so early (beforeSnap) for global effects or in beforeExport for single-export changes.
Custom exports via plugins
Plugins can add new exports using defineExports(context). For each export key you return (e.g. "pdf"), SnapDOM automatically exposes a helper method named toPdf() on the capture result.
Register the plugin (global or per capture):
import { snapdom } from '@zumer/snapdom';
// global
snapdom.plugins(pdfExportPlugin());
// or per capture
const out = await snapdom(element, { plugins: [pdfExportPlugin()] });
Call the custom export:
const out = await snapdom(document.querySelector('#report'));
// because the plugin returns { pdf: async (ctx, opts) => ... }
const pdfBlob = await out.toPdf({
// exporter-specific options (width, height, quality, filename, etc.)
});
Example: overlay filter plugin
Adds a translucent overlay or color filter only to the captured clone (not your live DOM). Useful for highlighting or dimming sections before export.
/**
* Ultra-simple overlay filter for SnapDOM (HTML-only).
* Inserts a full-size <div> overlay on the cloned root.
*
* @param {{ color?: string; blur?: number }} [options]
* color: overlay color (rgba/hex/hsl). Default: 'rgba(0,0,0,0.25)'
* blur: optional blur in px (default: 0)
*/
export function overlayFilterPlugin(options = {}) {
const color = options.color ?? 'rgba(0,0,0,0.25)';
const blur = Math.max(0, options.blur ?? 0);
return {
name: 'overlay-filter',
/**
* Add a full-coverage overlay to the cloned HTML root.
* @param {any} context
*/
async afterClone(context) {
const root = context.clone;
if (!(root instanceof HTMLElement)) return; // HTML-only
// Ensure containing block so absolute overlay anchors to the root
if (getComputedStyle(root).position === 'static') {
root.style.position = 'relative';
}
const overlay = document.createElement('div');
overlay.style.position = 'absolute';
overlay.style.left = '0';
overlay.style.top = '0';
overlay.style.right = '0';
overlay.style.bottom = '0';
overlay.style.background = color;
overlay.style.pointerEvents = 'none';
if (blur) overlay.style.filter = `blur(${blur}px)`;
root.appendChild(overlay);
}
};
}
Usage:
import { snapdom } from '@zumer/snapdom';
// Global registration
snapdom.plugins([overlayFilterPlugin, { color: 'rgba(0,0,0,0.3)', blur: 2 }]);
// Per-capture
const out = await snapdom(document.querySelector('#card'), {
plugins: [[overlayFilterPlugin, { color: 'rgba(255,200,0,0.15)' }]]
});
const png = await out.toPng();
document.body.appendChild(png);
The overlay is injected only in the cloned tree, never in your live DOM, ensuring perfect fidelity and zero flicker.
Full plugin template
Use this as a starting point for custom logic or exporters.
export function myPlugin(options = {}) {
return {
/** Unique name used for de-duplication/overrides */
name: 'my-plugin',
/** Early adjustments before any clone/style work. */
async beforeSnap(context) {},
/** Before subtree cloning (use sparingly if touching the live DOM). */
async beforeClone(context) {},
/** After subtree cloning (safe to modify the cloned tree). */
async afterClone(context) {},
/** Right before serialization (SVG/dataURL). */
async beforeRender(context) {},
/** After serialization; inspect context.svgString/context.dataURL if needed. */
async afterRender(context) {},
/** Before EACH export call (toPng/toSvg/toBlob/...). */
async beforeExport(context) {},
/**
* After EACH export call.
* If you return a value, it becomes the result for the next plugin (chaining).
*/
async afterExport(context, result) { return result; },
/**
* Define custom exporters (auto-added as helpers like out.toPdf()).
* Return a map { [key: string]: (ctx:any, opts:any) => Promise<any> }.
*/
async defineExports(context) { return {}; },
/** Runs ONCE after the FIRST export finishes (cleanup). */
async afterSnap(context) {}
};
}
Quick recap:
- Plugins can modify capture behavior (
beforeSnap,afterClone, etc.). - You can inject visuals or transformations safely into the cloned tree.
- New exporters defined in
defineExports()automatically become helpers likeout.toPdf(). - All hooks can be asynchronous, run in order, and share the same
context.
Ready to build?
Clone the plugin template, write a hook, and ship a SnapDOM plugin in minutes.
Browse plugins Install from npm