How to batch svg viewbox standardization with automation scripts
- Step 1Install and pair the runner — Install
@jadapps/runnerand pair it with your account once (see the runner docs linked from the API response'sinstallRunnerfield). The runner listens onhttp://127.0.0.1:9789. For viewBox-fixer it launches a headless browser per job because the bounding-box computation depends onDOMParser. - Step 2Fetch the tool schema — Call
GET /api/v1/tools/svg-viewbox-fixerwith your API key. The response includes the options array — for this tool justaddPadding(type number, default 0, min 0, max 50, step 1) — plusacceptsMultiple:false,minTier, and anexecution.runnerModeofheadless-browser. Build your payload from this, don't hard-code assumptions. - Step 3Walk your icon directory — Use
fs.readdirSync/globto collect*.svgfiles. Respect your tier's batch and per-file limits: free is 1 file and 5 MB; Pro is 20 files per browser batch and 50 MB each; Developer is unlimited batch and 2 GB each. For very large folders, chunk the list. - Step 4POST each file to the local runner — Send each SVG's content to
http://127.0.0.1:9789/v1/tools/svg-viewbox-fixer/runwith{ slug, files, options:{ addPadding } }. The runner returns the fixed SVG markup plus the metrics object. KeepaddPaddingconstant across the run if you want a uniform margin on every icon. - Step 5Write outputs and capture metrics — Save each result as
<stem>-viewbox-fixed.svg(or overwrite in place if you prefer, after a git commit). Log theComputed bboxflag — anyno (fallback)means the file had no measurable geometry and used declared dimensions instead; collect those for a manual pass. - Step 6Verify and integrate into CI — Spot-check a few fixed icons in a browser at
width:100%. Once happy, wire the script into a pre-commit hook or CI step so new icons get a correct viewBox automatically. Because the runner is local, the same script runs on a developer laptop or a self-hosted CI agent without leaking files.
Runner endpoints and what they return
The cloud API is a schema/dispatch layer only. File processing happens on localhost via the paired runner.
| Call | Purpose | Returns |
|---|---|---|
GET /api/v1/tools/svg-viewbox-fixer | Discover the option schema before building payloads | Options (addPadding), acceptsMultiple:false, minTier, execution.runnerMode:headless-browser |
POST /api/v1/tools/svg-viewbox-fixer/run (cloud) | Intentionally upload-free dispatch | 400 Runner required with runnerEndpoint, schemaEndpoint, installRunner fields |
POST http://127.0.0.1:9789/v1/tools/svg-viewbox-fixer/run | Actual execution on your machine | Fixed SVG markup + metrics { New viewBox, Computed bbox } |
Tier limits that govern a batch run
SVG-family limits from the central tier table. Batch counts are per browser-tool batch; a script can loop indefinitely within per-file size limits.
| Tier | Max file size | Batch files | Notes |
|---|---|---|---|
| Free | 5 MB | 1 | Single file at a time — use the browser tool, not a batch script |
| Pro | 50 MB | 20 | Typical icon-library tier; batch parallelism is 2 |
| Pro + Media | 200 MB | 100 | Rarely needed for SVG (text files); parallelism 4 |
| Developer | 2 GB | Unlimited | Best for large monorepo icon sets; parallelism 8 |
| Enterprise | Unlimited | Unlimited | No size or count ceiling |
The only payload option
viewBox-fixer takes exactly one option. Do not send crop/align/format options — they don't exist for this tool and will be ignored.
| Field | Type | Default | Valid range (API) | Effect |
|---|---|---|---|---|
addPadding | number | 0 | 0–50 (server-validated; >50 → 400) | Adds N user units on every side of the computed box |
Cookbook
Copy-paste scripts. Replace JAD_API_KEY and the directory path. All file content stays on your machine — the cloud key is only used to discover the schema.
Discover the schema (curl)
Before scripting, confirm the option shape and runner mode. This is the only call that touches JAD's cloud; no file content is sent.
curl -s https://jadapps.com/api/v1/tools/svg-viewbox-fixer \
-H "Authorization: Bearer $JAD_API_KEY" | jq '.options, .execution'
# → options: [ { name:"addPadding", type:"number",
# default:0, min:0, max:50, step:1 } ]
# → execution: { runnerMode:"headless-browser" }Confirm the cloud /run is upload-free
Hitting the cloud run endpoint returns pairing instructions, never a result. This is by design — proof that file content is processed locally.
curl -s -X POST https://jadapps.com/api/v1/tools/svg-viewbox-fixer/run \
-H "Authorization: Bearer $JAD_API_KEY" -d '{}'
# 400 {
# "error": "Runner required",
# "runnerEndpoint": "http://127.0.0.1:9789/v1/tools/svg-viewbox-fixer/run",
# "schemaEndpoint": ".../api/v1/tools/svg-viewbox-fixer",
# "installRunner": ".../docs/runner"
# }Node: walk a folder and fix every icon
Reads each .svg in ./icons, POSTs to the local runner with a fixed padding, and writes the result next to the source. Logs any file that fell back to declared dimensions.
import fs from 'node:fs';
import path from 'node:path';
const DIR = './icons';
const RUNNER = 'http://127.0.0.1:9789/v1/tools/svg-viewbox-fixer/run';
const addPadding = 0; // 0..50
const files = fs.readdirSync(DIR).filter(f => f.endsWith('.svg'));
for (const f of files) {
const svg = fs.readFileSync(path.join(DIR, f), 'utf8');
const res = await fetch(RUNNER, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
slug: 'svg-viewbox-fixer',
files: [{ name: f, content: svg }],
options: { addPadding },
}),
});
const out = await res.json();
const stem = f.replace(/\.svg$/, '');
fs.writeFileSync(path.join(DIR, `${stem}-viewbox-fixed.svg`), out.content);
if (out.metrics?.['Computed bbox'] !== 'yes')
console.warn(`fallback: ${f} → ${out.metrics?.['New viewBox']}`);
}
console.log(`Fixed ${files.length} icons`);Audit-only: list files that lack a viewBox
Before mutating anything, find which icons actually need fixing. This is a local read — no runner or API needed.
import fs from 'node:fs';
for (const f of fs.readdirSync('./icons').filter(x => x.endsWith('.svg'))) {
const s = fs.readFileSync(`./icons/${f}`, 'utf8');
const hasVB = /viewBox="[^"]+"/.test(s);
const hasPx = /\b(width|height)="[\d.]+(px)?"/.test(s);
if (!hasVB) console.log(`NO VIEWBOX: ${f}`);
else if (hasPx) console.log(`px dims (will be stripped): ${f}`);
}Pre-commit hook idea
Run the fixer only on staged SVGs so new icons land with a correct viewBox. Skeleton — adapt the staged-file detection to your VCS.
# .git/hooks/pre-commit (excerpt) staged=$(git diff --cached --name-only --diff-filter=ACM -- '*.svg') [ -z "$staged" ] && exit 0 node scripts/fix-viewbox.mjs $staged # POSTs each to localhost:9789 git add $staged # re-stage the fixed files # Files are processed by your local runner; nothing is uploaded.
Edge cases and what actually happens
POSTing file content to the cloud /run endpoint
400 Runner requiredThe cloud /run route never accepts uploads — it validates your key, checks rate limits, then returns a 400 with runnerEndpoint, schemaEndpoint and installRunner. Don't try to base64 your SVG into the cloud call; route file content to http://127.0.0.1:9789 instead. This is the privacy guarantee, not a bug.
Runner not installed or not paired
Connection refusedIf nothing is listening on 127.0.0.1:9789, your fetch to the runner throws ECONNREFUSED. Install and pair @jadapps/runner first (the cloud response's installRunner link points to the docs). Wrap runner calls in a try/catch and surface a clear 'start the runner' message in CI logs.
addPadding above 50
400 validation errorThe API/runner validates numeric options against the schema. Sending addPadding: 80 fails with Option "addPadding" must be ≤ 50. The browser slider caps at 20 so it never trips this, but a script can — clamp your value to 0–50 before sending.
addPadding sent as a non-number
400 validation errorPassing addPadding: "large" or addPadding: null is rejected: a number-typed option that isn't finite throws must be a number. Omitting the field entirely is fine — the engine applies the default of 0. Send a real number or nothing.
File exceeds the tier size limit
RejectedSVG-family caps apply: 5 MB (free), 50 MB (Pro), 200 MB (Pro+Media), 2 GB (Developer). An oversized file is rejected. SVGs are text, so this almost always means embedded base64 rasters — which the viewBox computation can't measure anyway. Strip embedded images before processing.
Icon has no measurable geometry
FallbackFiles whose only content is <text>, <image>, or empty defs return Computed bbox: no (fallback) and use declared viewBox/width/height (24×24 if absent). Your script should treat any non-yes flag as 'needs human review' rather than trusting the box blindly.
Curve/arc/relative-path icons get an approximate box
ApproximateThe box is computed from raw path numbers, so icons built from C/Q/A curves or lowercase relative commands can produce a box that's slightly too large or offset (control points and arc params are counted as coordinates). For a CI gate, accept these as safe over-estimates, or pre-normalize paths to absolute coordinates upstream.
Transforms in the source SVG
IgnoredThe runner uses the same logic as the browser tool and ignores transform attributes. Icons that position content via group transforms will get a box that doesn't match the rendered result. Flatten transforms in a build step (or with your vector toolchain) before the viewBox pass.
Rate limit hit on schema discovery
429The cloud schema/dispatch layer is rate-limited per key (X-RateLimit-* headers report the budget). Discovery is one call per tool — cache the schema and reuse it across your whole batch rather than re-fetching per file. The local runner calls are not subject to the cloud rate limit.
Overwriting source files without a checkpoint
RiskyWriting the fixed SVG over the original loses the ability to diff. Commit to git first (or write to <stem>-viewbox-fixed.svg as the script above does), then review the diff — only the root <svg> attributes should change. Because the fixer strips px width/height, a noisy diff there is expected and correct.
Frequently asked questions
Can I batch through the cloud API directly?
No — the cloud /run endpoint is intentionally upload-free and returns a 400 with pairing instructions. All file processing goes through a paired @jadapps/runner on your own machine. The cloud is used only to discover the schema (GET /api/v1/tools/svg-viewbox-fixer) and to authenticate; your SVG content never leaves your network.
Why does the runner launch a headless browser for this tool?
The viewBox computation parses the SVG with DOMParser and walks the DOM to read element coordinates — that's a browser API, not something the lightweight edge engine replicates. So the runner runs viewBox-fixer in a short-lived headless browser session (execution.runnerMode: headless-browser). Pure-text tools like the minifier run in the engine instead.
What options can I send?
Exactly one: addPadding, a number from 0 to 50 (server-validated). There is no crop mode, no alignment option, no output-format toggle for this tool. Sending unknown options is harmless but ignored. To set a uniform margin across a library, pass the same addPadding for every file.
How do I keep all my icons on the same grid (e.g. 0 0 24 24)?
The fixer crops to real content bounds, so it won't force a uniform 24-unit grid. If grid consistency matters more than tight cropping, don't auto-fit — instead validate that every file already has viewBox="0 0 24 24" (the audit script in the cookbook does this) and only fix the outliers. The icon-consistency-standards guide covers when to choose grid uniformity over tight bounds.
How many files can I process per run?
The browser tool's batch is tier-limited (1 free, 20 Pro, 100 Pro+Media, unlimited Developer), but a script looping over the local runner one file at a time is bounded only by per-file size limits and your patience. For thousands of icons on Developer/Enterprise, loop sequentially or with modest concurrency (the runner serializes headless-browser jobs).
How do I detect which icons actually need fixing?
Run the audit script: flag files with no viewBox="…", and note files with absolute width/height px attributes (those will be stripped). You can also process everything and compare the New viewBox metric to the original — unchanged boxes mean the file was already correct. Only commit the ones that changed.
What's in the runner's response?
The fixed SVG markup (content), the output filename (<stem>-viewbox-fixed.svg), original/output sizes, and a metrics object with New viewBox (the four-number string written) and Computed bbox (yes if measured from geometry, no (fallback) if it used declared dimensions). Log the metrics to triage your batch.
Will a script handle curve-heavy icons correctly?
It runs the same algorithm as the browser — the box is computed from raw path numbers, so curves/arcs/relative paths produce an approximate (usually slightly large) box. For a strict CI gate, either accept safe over-estimates or pre-convert paths to absolute straight-line approximations upstream. The output is always valid SVG; the question is only crop tightness.
Is there a rate limit I'll hit?
The cloud schema/dispatch layer is rate-limited per API key (watch X-RateLimit-Remaining). But schema discovery is a single call you can cache for the whole batch, and the actual per-file processing happens on the local runner, which the cloud rate limit doesn't touch. So a big batch won't burn your cloud budget.
Can I run this in CI without leaking private icons?
Yes — that's the point of the runner architecture. Install and pair @jadapps/runner on your CI agent; the script POSTs file content to 127.0.0.1:9789, which processes locally. No icon bytes go to JAD's cloud. This is suitable for proprietary brand assets and air-gapped-ish pipelines (the agent still needs the one-time pairing handshake).
Should I overwrite originals or write new files?
Either, but checkpoint first. The cookbook script writes <stem>-viewbox-fixed.svg so you can diff before replacing. If you overwrite in place, commit to git beforehand and review the diff — expect changes only on the root <svg> tag (viewBox set, px width/height stripped and re-added, preserveAspectRatio added).
How does this fit with building a sprite sheet?
Fix the viewBox on individual icons first, then assemble them with the sprite builder — don't re-fix the finished sprite, because <symbol> viewBoxes are deliberate. For per-icon cleanup before spriting, you might also run the minifier and metadata scrubber in the same loop.
Privacy first
Every JAD SVG tool runs entirely in your browser using the DOM API and Canvas. Your SVG files never leave your device — verified by zero outbound network requests during processing.