
Security News
pnpm 11.5 Adds Support for Recognizing npm Staged Publishes
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.
@zumer/snapdom-plugins
Advanced tools
Official plugins for SnapDOM — extend and transform DOM captures with zero core changes.
npm install @zumer/snapdom-plugins
Import plugins individually (recommended for tree-shaking):
import { snapdom } from '@zumer/snapdom';
import { filter } from '@zumer/snapdom-plugins/filter';
import { timestampOverlay } from '@zumer/snapdom-plugins/timestamp-overlay';
const result = await snapdom(element, {
plugins: [filter({ preset: 'grayscale' }), timestampOverlay()]
});
const png = await result.toPng();
Or import everything at once:
import { filter, timestampOverlay, replaceText } from '@zumer/snapdom-plugins';
CDN (no install):
import { snapdom } from 'https://esm.sh/@zumer/snapdom';
import { filter } from 'https://esm.sh/@zumer/snapdom-plugins/filter';
filterApplies CSS filter effects to the captured clone.
import { filter } from '@zumer/snapdom-plugins/filter';
snapdom(el, { plugins: [filter({ preset: 'grayscale' })] });
| Option | Type | Default | Description |
|---|---|---|---|
preset | string | — | 'grayscale' | 'sepia' | 'blur' | 'invert' | 'vintage' | 'dramatic' |
filter | string | '' | Raw CSS filter string, e.g. 'blur(2px) contrast(1.2)' |
timestamp-overlayAdds a translucent timestamp label to the captured clone.
import { timestampOverlay } from '@zumer/snapdom-plugins/timestamp-overlay';
snapdom(el, { plugins: [timestampOverlay({ position: 'top-right', format: 'date' })] });
| Option | Type | Default | Description |
|---|---|---|---|
format | string | function | 'datetime' | 'datetime' | 'date' | 'time' | 'iso' | custom (Date) => string |
position | string | 'bottom-right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' |
background | string | 'rgba(0,0,0,0.6)' | Label background color |
color | string | '#fff' | Label text color |
fontSize | number | 11 | Font size in px |
replace-textFind-and-replace text in the captured clone. Supports strings and regex.
import { replaceText } from '@zumer/snapdom-plugins/replace-text';
snapdom(el, {
plugins: [replaceText({
replacements: [
{ find: 'DRAFT', replace: 'APPROVED' },
{ find: /\d{4}-\d{2}-\d{2}/, replace: '[REDACTED]' }
]
})]
});
| Option | Type | Default | Description |
|---|---|---|---|
replacements | Array<{ find: string|RegExp, replace: string }> | [] | List of find/replace pairs |
color-tintTints the entire capture to a specified color using a mix-blend-mode overlay.
import { colorTint } from '@zumer/snapdom-plugins/color-tint';
snapdom(el, { plugins: [colorTint({ color: 'royalblue', opacity: 0.4 })] });
| Option | Type | Default | Description |
|---|---|---|---|
color | string | 'red' | Any CSS color value |
opacity | number | 1 | Overlay opacity (0–1) |
ascii-exportAdds a toAscii() export method that converts captures to ASCII art.
import { asciiExport } from '@zumer/snapdom-plugins/ascii-export';
const result = await snapdom(el, { plugins: [asciiExport({ width: 100 })] });
const art = await result.toAscii();
console.log(art);
| Option | Type | Default | Description |
|---|---|---|---|
width | number | 80 | Character width of output |
charset | string | ' .:-=+*#%@' | Characters from lightest to darkest |
invert | boolean | false | Invert luminance mapping |
pdf-imageExports the capture as a PNG embedded in a downloadable PDF (A4). Adds a toPdfImage() method.
import { pdfImage } from '@zumer/snapdom-plugins/pdf-image';
const result = await snapdom(el, { plugins: [pdfImage({ orientation: 'landscape' })] });
await result.toPdfImage(); // triggers download
| Option | Type | Default | Description |
|---|---|---|---|
orientation | string | 'portrait' | 'portrait' | 'landscape' |
quality | number | 0.92 | JPEG quality (0–1) |
filename | string | 'capture.pdf' | Download filename |
html-in-canvasUses the experimental WICG drawElementImage API for direct DOM-to-canvas rendering where supported. Falls back gracefully.
import { htmlInCanvas } from '@zumer/snapdom-plugins/html-in-canvas';
snapdom(el, { plugins: [htmlInCanvas()] });
This plugin uses an experimental browser API and may not work in all environments.
agent-mapProduces a Set-of-Mark package for visual agents: an annotated screenshot with numbered badges on interactive elements, plus a compact JSON map from badge index → role / accessible name / bbox / state. One call, fully client-side.
import { agentMap } from '@zumer/snapdom-plugins/agent-map';
const result = await snapdom(el, { plugins: [agentMap()] });
const { image, map, dimensions } = await result.toAgentMap();
// image: data URL of the screenshot with numbered red badges overlaid
// map: [{ i, n, r, b, s? }, …] — index, name, role, bbox, state
// Agent says "click element 2" → map[2].b gives [x, y, w, h]
Map entry shape (default fields: 'minimal'):
| Key | Type | Description |
|---|---|---|
i | number | Index matching the badge drawn on the image |
n | string | Accessible name (aria-label → labelledby → alt → title → labels → textContent, truncated to 60 chars) |
r | string | ARIA-style role (button, link, checkbox, radio, textbox, combobox, slider, heading, …) — derived from role attribute or implicit role of the element |
b | [x, y, w, h] | Bounding box in pixels, scaled against maxImageWidth |
s | object? | State: included only when at least one key is meaningful — checked, disabled, focus, expanded, pressed, selected, value, open, selectedText, covered |
Example map for a checkout form:
[
{ i: 0, n: 'Email', r: 'textbox', b: [28, 80, 280, 34], s: { value: 'ada@example.com' } },
{ i: 1, n: 'Send product updates', r: 'checkbox', b: [28, 134, 13, 13], s: { checked: true } },
{ i: 2, n: 'Apply coupon', r: 'button', b: [28, 176, 114, 38], s: { expanded: false } },
{ i: 3, n: 'Remove coupon', r: 'button', b: [150, 176, 140, 38], s: { disabled: true } },
{ i: 4, n: 'Pay $53.90', r: 'button', b: [28, 220, 97, 38] }
]
| Option | Type | Default | Description |
|---|---|---|---|
image | 'annotated' | 'raw' | false | 'annotated' | 'annotated' overlays numbered badges; 'raw' skips badges; false skips image generation entirely (no canvas draw, no toDataURL — cheapest path). |
fields | 'minimal' | 'full' | 'minimal' | 'full' adds t (raw text content) and a (meaningful attributes) per entry. |
semantic | boolean | false | Include non-interactive structural elements (headings, paragraphs, landmarks). Off by default — agents act on interactive. |
maxImageWidth | number | 1024 | Downscale target for the image; bboxes rescale to match. |
imageFormat | 'png' | 'jpg' | 'webp' | 'png' | Image format (only used when image is rendered). |
imageQuality | number | 0.8 | Quality for lossy formats. |
interactiveSelector | string | see below | CSS selector for interactive elements. |
semanticSelector | string | see below | CSS selector for semantic elements (used when semantic: true). |
labelStyle | object | {} | Override badge styles. |
Defaults:
a[href], button, input, select, textarea, [role="button"|"link"|"tab"|"menuitem"|"checkbox"|"radio"|"switch"|"slider"|"combobox"|"textbox"], [tabindex]:not([tabindex="-1"]), summary, [contenteditable="true"]h1–h6, nav, main, article, section, header, footer, figcaption, blockquote, legend, pPer-call options override constructor options (e.g. result.toAgentMap({ image: false })).
Because it runs entirely in the browser, it works in contexts where Playwright / Puppeteer can't: Chrome extensions, SaaS web apps capturing the user's own page, Electron apps capturing their own window.
html-exportAdds a toHtml() export that returns the capture as a self-contained, re-renderable HTML document (clone + inlined styles/fonts) instead of pixels. It unwraps the SVG <foreignObject> SnapDOM already produced, so the markup and CSS match the capture byte-for-byte — nothing is rasterized.
import { htmlExport } from '@zumer/snapdom-plugins/html-export';
const result = await snapdom(el, { plugins: [htmlExport()] });
const html = await result.toHtml(); // full <!DOCTYPE html> string
// Or download a .html file:
await result.toHtml({ download: 'snapshot.html' });
| Option | Type | Default | Description |
|---|---|---|---|
fullDocument | boolean | true | Wrap output in <!DOCTYPE html>…; if false, return just <style> + the fragment |
filename | string | 'capture.html' | Download filename when opts.download is true |
Per-call opts: download (boolean \| string — true triggers download, a string sets the filename), plus fullDocument / filename overrides. Returns the HTML string.
gif-exportAdds a toGif() export that records an animated GIF by re-capturing the live element over time and encoding the frames. The GIF89a encoder (median-cut quantization + LZW) is built in — no dependencies. Returns a Blob (image/gif).
import { gifExport } from '@zumer/snapdom-plugins/gif-export';
const result = await snapdom(el, { plugins: [gifExport({ fps: 12, duration: 3000 })] });
const blob = await result.toGif();
// Or download directly:
await result.toGif({ download: 'animation.gif' });
| Option | Type | Default | Description |
|---|---|---|---|
fps | number | 10 | Frames per second |
duration | number | 2000 | Total duration in ms (ignored if frames is set) |
frames | number | — | Explicit frame count (overrides duration) |
maxColors | number | 256 | Palette size per frame (2–256) |
background | string | '#ffffff' | Color composited under transparent pixels |
scale | number | 1 | Capture scale |
repeat | number | 0 | Loop count (0 = forever, -1 = play once) |
filename | string | 'capture.gif' | Download filename |
Per-call opts override any constructor option, plus download (boolean \| string). Each frame is captured live, so CSS animations / dynamic content are recorded as they play.
video-exportAdds a toMp4() export that records a video by re-capturing the live element over time and encoding the frames with the native MediaRecorder. Returns a Blob whose type reflects what was actually produced.
import { videoExport } from '@zumer/snapdom-plugins/video-export';
const result = await snapdom(el, { plugins: [videoExport({ fps: 30, duration: 4000 })] });
const blob = await result.toMp4();
// Or download directly:
await result.toMp4({ download: true });
Codec reality:
MediaRecorderoutput depends on the browser. Safari produces MP4 (H.264); Chromium typically produces WebM (VP8/VP9). When MP4 isn't supported the plugin falls back to WebM, warns in the console, and the downloaded file extension is set to.mp4/.webmaccordingly.
| Option | Type | Default | Description |
|---|---|---|---|
fps | number | 10 | Frames per second |
duration | number | 2000 | Total duration in ms (ignored if frames is set) |
frames | number | — | Explicit frame count (overrides duration) |
background | string | '#ffffff' | Color composited under transparent pixels |
scale | number | 1 | Capture scale |
bitrate | number | — | videoBitsPerSecond passed to MediaRecorder |
filename | string | — | Download filename (extension auto-set to .mp4 / .webm) |
Requires MediaRecorder (unavailable in some headless environments). Per-call opts override any constructor option, plus download (boolean \| string).
Global (applies to all captures):
import { snapdom } from '@zumer/snapdom';
import { filter } from '@zumer/snapdom-plugins/filter';
snapdom.plugins(filter({ preset: 'sepia' }));
Per-capture (overrides global for that call):
const result = await snapdom(element, {
plugins: [filter({ preset: 'dramatic' })]
});
Per-capture plugins run before global ones. Duplicate plugin names are skipped automatically.
Use the template to scaffold a new plugin in seconds:
npx degit zumerlab/snapdom/packages/plugin-template my-plugin
See PLUGIN_SPEC.md for the full hook specification and CONTRIBUTING_PLUGINS.md to get your plugin listed on the community page.
MIT
FAQs
Official plugins for SnapDOM
We found that @zumer/snapdom-plugins demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.

Security News
Federal audit finds NIST lacked a plan to clear the NVD backlog, wasted funds on duplicate work, and delayed use of CISA data.

Research
/Security News
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.