CLI reference
Complete reference for the jto and jto-cli command-line tools: every command, flag, config file, environment variable, exit code, and dev-server endpoint. For a task-oriented walkthrough, see the CLI guide.
Packages
| Package | Bin | Includes | Intended for |
|---|---|---|---|
@json-to-office/jto | jto | All core commands plus the dev web playground (React, Monaco, Vite, AI assistant) | Local development |
@json-to-office/jto-cli | jto-cli | Core commands only — no React/Monaco/Vite/AI dependencies | CI, serverless, containers |
Both are ESM packages requiring Node >= 20, built on Commander. jto depends on jto-cli and re-mounts its command set, so flags and behavior are identical for the shared commands. jto-cli registers a hidden dev placeholder per format that prints a pointer to install @json-to-office/jto and exits 1 (instead of Commander's "unknown command").
Command tree
Every command is mounted under a format parent: docx or pptx.
jto <docx|pptx> generate <input>
jto docx diff <old> <new> # DOCX only
jto <docx|pptx> validate <file-or-dir>
jto <docx|pptx> schemas
jto <docx|pptx> discover
jto <docx|pptx> init [name]
jto <docx|pptx> fonts <list|inspect|install>
jto <docx|pptx> dev # jto onlyThe format parent selects an adapter that fixes the output extension (.docx / .pptx), the label, and the default dev-server port (3003 for docx, 3004 for pptx).
generate
jto <docx|pptx> generate <input> [options]<input> (required) is the path to the input JSON file. There is no stdin/stdout mode — input is read from a file and output is written to a file.
| Flag | Type | Default | Description |
|---|---|---|---|
-o, --output <path> | string | <input basename> + .docx/.pptx in cwd | Output file path |
-t, --template <name> | string | — | Template name. Reserved — accepted but currently has no effect |
--plugins [names-or-paths] | string | boolean | — | Comma-separated plugin names/paths; bare flag enables auto-discovery |
--plugin-dir <dir> | string | — | Directory to search for plugins |
--theme <name-or-path> | string | — | Theme name or path. Applied on both the standard and plugin-loaded paths, and overrides the document's props.theme |
--theme-path <path> | string | — | Path to a theme file (tried before --theme); .json files are parsed, other extensions are dynamically imported (uses the default or theme export) |
--strict | boolean | false | Accepted no-op kept for compatibility — validation always runs; see below |
--strict-fonts | boolean | false | Fail generation on unresolved fontRegistry references |
--no-google-fonts | boolean | — | Sets fonts.googleFonts.enabled: false on the generator. Has no effect on generate output because generate never fetches (fetching happens only in the dev-server preview pipeline) |
--font-cache-dir <path> | string | — | Sets fonts.googleFonts.cacheDir on the generator. Has no effect on generate output for the same reason |
--font <name=path> | string, repeatable | [] | Register a font file: <family>=<path to .ttf/.otf> |
--fonts-dir <path> | string | — | Scan a directory for .ttf/.otf files and auto-register them by filename |
--font-mode <mode> | substitute | custom | custom | custom keeps font references as-is; substitute rewrites non-safe fonts to safe ones. Any other value errors |
--font-substitute <family=safe> | string, repeatable | [] | Map a non-safe family to a specific safe font; the target must be in the safe-fonts list or the command errors |
--dry-run | boolean | false | Print a summary — input, output, format, theme, plugins (when any loaded), and Validation: passed — without writing files |
--renderer <id> | string | format default | Backend that writes the file — see Choosing a backend |
--quality-profile <path> | JSON file | default format profile | Custom profile or shipped-profile override: document class, rule parameters, and intended renderer targets |
--quality-policy <path> | JSON file | advisory | Per-run rule overrides, suppressions, diagnostic budget, and gate |
--quality-gate <severity> | none | error | warning | info | none | Fail before rendering when a diagnostic at or above the threshold exists |
See Design quality for the built-in rules, shipped profiles, policy format, gate semantics and known limits.
Choosing a backend
The root JSON renderer field picks which library turns the compiled document into bytes. When omitted, DOCX uses docxjs and PPTX uses pptxgenjs. --renderer overrides the document field for that invocation. office-open is experimental and opt-in.
jto docx generate report.docx.json --renderer office-open -o report.docx{ "name": "docx", "renderer": "office-open", "children": [] }The two backends do not support the same set of features. A document needing one the chosen backend does not have is refused before any bytes exist, naming the feature and the path that needed it — a comment thread on office-open, for instance. That is deliberate: a backend silently dropping content would be worse than a failed command.
The plugin/generation config file's theme and themePath keys are applied by generate (either CLI flag replaces both — see Theme selection below), as is validation.allowUnknownFields. Only validation.strict is inert. Plugin keys (plugins, pluginDirs, autoDiscover) take effect as documented there.
Font-directory filename parsing: names like Inter-BoldItalic.ttf or Roboto_500.otf are parsed into family, weight, and italic; weight names map thin=100 through black/heavy=900; only .ttf/.otf files are scanned, and multiple variant files coalesce into one registry entry per family.
Theme selection
--theme and --theme-path apply on both paths — with plugins loaded and without. The requested theme is registered under a reserved key and the document's props.theme is rewritten to point at it, so an explicitly requested theme beats the one the document names for itself. Precedence resolves in two steps:
- Origin.
--themeand--theme-pathare merged against the config file as one group, not key by key. If you pass either flag, both config-file keys (theme,themePath) are discarded and only the flags you typed survive. If you pass neither, both config-file keys survive. - Order within the surviving origin.
themePathis tried first, andthemeonly if it yielded nothing.
Because step 1 settles the origin first, step 2 never mixes the two sources. The four combinations:
--theme | --theme-path | Theme used |
|---|---|---|
| — | — | Config-file themePath, else config-file theme |
| set | — | The --theme value. A config-file themePath is ignored |
| — | set | The --theme-path file. A config-file theme is ignored |
| set | set | The --theme-path file; the --theme value is used only if reading that file throws |
With no theme requested from either source, the document's props.theme is used unchanged.
--theme resolves in order: a key of the supplied customThemes map → a built-in theme name → an existing .json path; for DOCX the value is additionally tried as inline theme JSON. --theme-path takes a .json file (parsed) or any other extension (dynamically imported, using the default or theme export), and also registers the theme under its own name.
A requested theme that resolves to nothing leaves props.theme in charge; nothing is silently swapped to a default and generation proceeds. The message depends on which input failed: a --theme value that names no theme prints Unknown theme "X"; keeping the document's own theme, while a --theme-path that cannot be read, parsed, or imported prints Failed to load theme from <path>: <reason>. With both flags set, a failing path falls through to --theme and an unknown name there adds its own line, so a doubly-bad invocation prints both.
A rejected request is not echoed on the --dry-run/summary Theme: line: that line reports the theme generation actually used, so it shows the document's own props.theme (or default when it names none). A resolved request is reported as the adapter resolved it — the file path for --theme-path, the name for --theme. See Theme schema.
--strict on generate is an accepted no-op: it affects nothing — not the output, not the dry-run summary. Validation always runs, and how permissive it is depends on validation.allowUnknownFields; core-docx marks validation.strict @deprecated No longer consulted. The config file's validation.strict key is inert for the same reason. (--strict on validate is a different flag and does still work.)
diff (DOCX only)
jto docx diff <old> <new> [options]Diffs two JSON documents into a redline .docx with native Word tracked changes. Mounted only under docx; both inputs are validated before diffing.
| Flag | Type | Default | Description |
|---|---|---|---|
-o, --output <path> | string | redline.docx | Output redline path |
--author <name> | string | json-to-office | Revision author shown in Word |
--date <iso> | string | now | Revision timestamp (ISO 8601); an invalid value errors |
--json-out <path> | string | — | Also write the redline as a JSON document definition |
-f, --format <format> | pretty | json | pretty | Summary output format; json emits { output, jsonOut, summary } |
--dry-run | boolean | false | Compute the diff and summary without writing files |
The summary reports inserted/deleted/modified tracked changes and unchanged blocks, and lists changes that are not expressible as tracked changes. Exits 0 on success, 1 on failure.
validate
jto <docx|pptx> validate <file-or-directory> [options]The argument accepts a JSON file, a directory, or a glob pattern (node_modules, dist, and build are ignored).
| Flag | Type | Default | Description |
|---|---|---|---|
-t, --type <type> | document | theme | auto | auto | Auto-detection by shape: name: 'docx'/'pptx', children[], slides[], or props.metadata.title ⇒ document; colors/fonts/styles/pageSetup/componentDefaults ⇒ theme |
-s, --schema <path> | string | — | Validate against a custom JSON Schema (Ajv with allErrors + formats); a missing schema file errors up front |
--strict | boolean | false | Use strict validation — no cleaning or defaults applied |
-q, --quiet | boolean | false | Only output errors |
-f, --format <format> | pretty | json | pretty | json prints a machine-readable result array per file |
-r, --recursive | boolean | false | Recurse into directories (**/*.json) |
--quality-profile <path> | JSON file | default format profile | Custom profile or shipped-profile override used by quality rules |
--quality-policy <path> | JSON file | advisory | Rule overrides, suppressions, budget, and gate |
--quality-gate <severity> | none | error | warning | info | none | Mark files invalid at or above the selected quality severity |
Quality remains advisory unless the profile/policy or --quality-gate supplies a gate. See Design quality.
Multi-file runs print a File/Status/Errors table. Exit code 1 if any file is invalid, else 0. Zero matched files warns and exits 0.
schemas
jto <docx|pptx> schemas [options]Generates JSON Schemas for documents and themes — for IDE validation, tooling, and LLM workflows.
| Flag | Type | Default | Description |
|---|---|---|---|
-o, --output-dir <path> | string | ./schemas | Output directory |
--plugins [names-or-paths] | string | boolean | — | Include plugin schemas |
--plugin-dir <dir> | string | — | Plugin search directory |
-f, --format <type> | json | typebox | json | typebox emits TypeBox TypeScript files |
--theme-only | boolean | false | Only theme schemas (also skips plugin loading) |
--document-only | boolean | false | Only document schemas |
--split | boolean | false | Separate schema file per component type |
Prints a table of generated paths plus any included plugins. See JSON schemas for the output layout.
discover
jto <docx|pptx> discover [options]Discovers json-to-office plugins, document JSON files, and theme files across the project.
| Flag | Type | Default | Description |
|---|---|---|---|
-j, --json | boolean | false | JSON output |
-s, --schema | boolean | false | Include full schemas in output (plugins only) |
-e, --examples | boolean | false | Include usage examples (plugins only) |
-t, --type <type> | plugin | document | theme | all | all | What to discover; an invalid value exits 1 |
-s, --scope <path> | string | — | Limit discovery to a directory |
--max-depth <depth> | integer | 10 | Maximum search depth |
--include-node-modules | boolean | false | Also search node_modules |
-v, --verbose | boolean | false | Debug output |
--grouped | boolean | false | Group results by location |
Known quirk: -s is declared twice
Both --schema and --scope register the short flag -s, so -s is ambiguous. Always use the long forms --schema and --scope.
init
jto <docx|pptx> init [name] [options]Scaffolds a new project. Without a name it prompts interactively (initial value my-json-to-<format>-project). Fails if the target directory already exists.
| Flag | Type | Default | Description |
|---|---|---|---|
-t, --template <type> | string | basic | Project template (currently only basic — the scaffold content is fixed) |
--skip-install | boolean | false | Skip running npm install after scaffolding |
The scaffold contains:
package.json— scriptsdev,generate,validate,schemascallingjto <format> …; dependency@json-to-office/json-to-docx(or-pptx); devDependencies@json-to-office/jtoand TypeScriptexample.json— a minimal working document (heading + paragraph for docx; one slide with text for pptx).gitignore
fonts
Three subcommands. See Fonts for the concepts behind safe fonts, Google Fonts, and embedding.
fonts list
jto <docx|pptx> fonts list [document] [options]Prints the safe (Office-bundled) fonts, local fonts found in the fonts directory, and — when a JSON document argument is given — every font the document references, tagged [safe], [google] (in the popular Google Fonts list), or [unresolved].
| Flag | Type | Default | Description |
|---|---|---|---|
--fonts-dir <path> | string | ./fonts | Directory of local .ttf/.otf files to list |
fonts inspect
jto <docx|pptx> fonts inspect <file>Prints family, weight, italic, format, and size for a font file. Warns for non-TTF/OTF files: embedding requires TTF or OTF — WOFF/WOFF2 will not embed in .docx. No flags.
fonts install
jto <docx|pptx> fonts install <family> [options]Downloads a Google Fonts family as local TTFs.
| Flag | Type | Default | Description |
|---|---|---|---|
-w, --weights <list> | comma/space list | 400,700 | Weights as 100-step integers 100–900; an invalid token errors |
--italics | boolean | false | Also download italic variants |
-d, --dir <path> | string | ./fonts | Output directory |
Files are written atomically (.tmp then rename) and named <Family>-<Weight>[Italic].ttf (for example Inter-Regular.ttf, Inter-Bold.ttf), compatible with --fonts-dir auto-discovery in generate. Any failed file ⇒ exit 1.
dev (jto only)
jto <docx|pptx> dev [options]Starts the development server with the web playground UI (see Playground).
| Flag | Type | Default | Description |
|---|---|---|---|
-p, --port <port> | integer | 3003 (docx) / 3004 (pptx) | Server port. Precedence: CLI flag > config-file server.port > PORT env var > format default |
-H, --host <host> | string | localhost | Bind host |
-o, --open | boolean | false | Open the browser on start |
-c, --config <path> | string | — | Config file path |
On start the CLI prints the local URL, the API URL (http://<host>:<port>/api/<format>/generate), and the health URL (http://<host>:<port>/health). Shuts down gracefully on SIGINT/SIGTERM.
Client serving in dev mode resolves in order: JTO_CLIENT_PATH env override → the bundled client when running from the published package → the source client through a Vite dev server in middleware mode with HMR (HMR port from development.hmrPort, default 5173). In production mode the pre-built SPA is served with an SPA fallback.
The AI assistant (/api/ai/chat) is mounted unless AI_ENABLED=false. It streams via the Vercel AI SDK using the local Claude Code auth (no raw API key), restricted to the opus / sonnet / haiku models (default opus), with file tools disallowed and no session persistence.
Config files
Two independent config systems exist.
Plugin / generation config
Loaded via cosmiconfig by generate and schemas. Search order:
.json-to-office.config.json/.jsjson-to-office.config.json/.js.json-to-officerc,.json-to-officerc.json/.js- legacy
json-to-docx/json-to-pptxvariants of the above package.jsonkeysjson-to-office,json-to-docx,json-to-pptx
Shape:
{
"plugins": ["weather"],
"pluginDirs": ["./plugins"],
"autoDiscover": false,
"aliases": {},
"theme": "minimal",
"themePath": "./brand-theme.json",
"discovery": { "maxDepth": 10, "includeNodeModules": false },
"validation": { "strict": false, "allowUnknownFields": false }
}CLI flags win over config values; an absent flag leaves the corresponding key alone. The theme and themePath keys are applied by generate (see Theme selection above); validation.allowUnknownFields is forwarded to the generator; validation.strict is inert. Plugin load order: --plugins flag (bare = auto-discover, string = named list) → autoDiscover → plugins → --plugin-dir → pluginDirs.
Dev-server config
Loaded by dev (also via -c/--config). Files: json-to-office.config.ts / .js / .mjs / .json, plus legacy docx/pptx variants. The file is deep-merged over the defaults and TypeBox-validated; an invalid config warns and falls back to defaults. NODE_ENV=production forces mode: 'production'.
These four keys are the whole schema — the dev server reads every one of them:
| Key | Default | Description |
|---|---|---|
mode | development | development or production |
server.port | format default | Server port. Setting this key wins over PORT and the format default; only -p outranks it. Omit it and you get PORT, else 3003 (docx) / 3004 (pptx) |
server.host | localhost | Bind host |
development.hmrPort | 5173 | Vite HMR port when the client is served from source |
Keys the dev server never read — server.cors.*, api.*, playground.*, paths.*, development.hmr, development.sourceMap, development.verbose — were removed from the schema rather than left to imply an effect. Unknown keys still validate, so a config file that still carries them keeps loading; they are ignored, as they always were. CORS is configured through the CORS_ORIGIN environment variable and the upload size cap through MAX_FILE_SIZE.
Environment variables
| Variable | Used by | Default | Effect |
|---|---|---|---|
HIGHCHARTS_SERVER_URL | generate (both CLIs), dev | — | Highcharts export server URL for chart rendering |
HIGHCHARTS_API_KEY | generate, dev | — | API key sent to the Highcharts server |
HIGHCHARTS_API_KEY_HEADER | generate, dev | x-api-key | Header name for the Highcharts API key |
JTO_PPTX_RASTERIZER_URL | docx generate | — | Remote rasterizer for visual components; unset ⇒ in-process LibreOffice rasterizer |
JTO_PPTX_RASTERIZER_API_KEY | docx generate, dev | — | API key sent to the remote rasterizer |
LIBREOFFICE_PATH | rasterizer, previews | auto-detected | Path to the LibreOffice binary |
PDFTOPPM_PATH | rasterizer | auto-detected | Path to the pdftoppm binary |
DEBUG | generate | — | true enables generator debug mode |
PORT | jto dev | — | Listen port, used when the config file does not set server.port and -p is not given |
JTO_CLIENT_PATH | jto dev | — | Override the playground client directory |
AI_ENABLED | jto dev | enabled | false disables the /api/ai routes |
API_AUTH_MODE | jto dev | see below | required | auto | disabled. Defaults to required in production, auto locally |
PLUGIN_AUTOLOAD | jto dev | on in development and test | true lets the server load the plugins it discovers on disk, once at startup. Locally it also allows the two request-time loads — on-demand schema generation, and a keyless POST /discovery/load-plugins — which stay refused anywhere else. Off, plugins are listed but cannot be switched on |
API_KEY | jto dev | — | Credential required on /api/* when auth mode requires it |
API_KEY_HEADER | jto dev | x-api-key | Header carrying the API key |
RATE_LIMIT_WINDOW_MS | jto dev server | 900000 | Rate-limit window applied across /api/* |
RATE_LIMIT_MAX | jto dev server | 100 prod / 1000 dev | Requests per window per client, per method+path |
TRUST_PROXY_HEADERS | jto dev server | false | Trust X-Real-IP / X-Forwarded-For for rate-limit identity. Enable only behind a proxy that overwrites them |
MAX_REQUEST_BODY_SIZE | jto dev server | 33554432 | Body cap across /api/* (32 MiB) |
MAX_CONCURRENT_REQUESTS | jto dev server | 8 prod / 64 dev | In-flight /api/* requests before 503 |
MAX_FILE_SIZE | jto dev server | 10485760 | Upload size cap (10 MiB) |
OUTBOUND_SOURCE_MODE | jto dev server | safe prod / development otherwise | safe restricts remote assets to allowlisted HTTPS hosts and blocks local paths |
OUTBOUND_HOST_ALLOWLIST | jto dev server | empty | Comma-separated hosts allowed in safe mode; *.example.com wildcards supported |
CORS_ORIGIN | jto dev server | * | Allowed origin(s), comma-separated |
LIBREOFFICE_TIMEOUT_MS | jto dev server | 30000 | LibreOffice conversion timeout |
LOG_LEVEL | jto dev server | info | error | warn | info | debug |
CACHE_ENABLED | jto dev server | true | false disables the generation cache |
CACHE_MAX_SIZE_MB | jto dev server | 100 | Cache size cap |
CACHE_TTL_SECONDS | jto dev server | 3600 | Cache TTL |
CACHE_MAX_ITEMS | jto dev server | 1000 | Cache item cap |
NODE_ENV | both | development | Anything other than development or test selects hardened defaults |
The dev server loads a .env file via dotenv. NODE_ENV is normalized before use: only development and test keep permissive defaults, so a mislabelled deployment (staging, a typo) gets production-grade auth, rate limits, and outbound-source policy rather than silently opening up.
The values above are applied process-wide across /api/*; individual expensive routes keep their own tighter per-route limits (10/20/30 requests per 15 minutes in production) below that ceiling. The standalone render server reads its own separate set of variables.
PORT is read by jto dev as the deployment convention, but only below anything more specific: -p > config-file server.port > PORT > the format default (3003 docx / 3004 pptx). Each step is decided by whether the value was actually supplied, not by what it happens to equal, so PORT=3003 jto pptx dev binds 3003 rather than being mistaken for the unset default and bumped to 3004. A PORT that is not an integer in 0–65535 is ignored and the format default applies. The same order is used when the dev-server config file fails schema validation and the packaged defaults are substituted.
Exit codes
| Code | Meaning |
|---|---|
0 | Success (including --help / --version) |
1 | Any failure: validation errors, generation errors, invalid flag values, failed font downloads, jto-cli <format> dev placeholder |
These are the only two codes. Error output special-cases missing files ("File not found") and malformed JSON ("Invalid JSON in input file"), and prints per-path validation errors with suggestions.
Output streams and piping
The CLI renders with Ink, so an interactive terminal gets spinners, colour, and live status. Redirected output is handled differently on purpose:
- Error detail goes to stderr. Command results — a validation report, a generated-file summary — go to stdout, so
jto pptx validate deck.json 2>/dev/nullstill shows the report while1>/dev/nullisolates the failure reason. - Non-TTY output is plain. When stdout (or stderr) is not a terminal, output bypasses Ink entirely: no cursor-control escape sequences, and no wrapping to the terminal width. Long file paths stay on one line, so
jto pptx validate deck.json | grep FAILbehaves.
For anything you intend to parse, prefer the machine-readable mode rather than scraping the human output:
jto pptx validate deck.json -f json > results.json-f json — available on validate and on diff (DOCX only) — writes structured JSON straight to stdout, bypassing the renderer entirely. Note that -f on schemas means something different: it selects json or typebox as the schema output format.
Dev-server HTTP API
Routes are mounted at /api/<format> (with legacy aliases /api/documents for docx and /api/presentations for pptx). Rate limits below apply in production mode; development mode is effectively unlimited. See also the API reference.
Authentication depends on API_AUTH_MODE
Auth is controlled by API_AUTH_MODE, which defaults to required in production and auto locally.
required— every/api/*request needs the key. If noAPI_KEYis configured the server answers503, so a production deployment fails closed rather than open.auto— enforced only whenAPI_KEYis set; with no key the API is open. This keeps local playgrounds zero-config.disabled— no check at all. Only for an intentionally public demo.
The key is read from x-api-key (or API_KEY_HEADER), and Authorization: Bearer <key> is also accepted. Comparison is timing-safe. Running auto or disabled on a reachable host means keeping the server off the public internet.
| Method | Route | Description |
|---|---|---|
POST | /api/<format>/generate | Generate a document; returns a base64 envelope with cache HIT/MISS. Client-supplied fonts.strict is stripped. 10 req / 15 min in production |
POST | /api/<format>/validate | Validate a document JSON |
POST | /api/docx/diff | Diff two documents into a redline (DOCX only). 32 MB body cap; 30 req / 15 min in production |
POST | /api/<format>/preview/libreoffice | Convert an uploaded file to PDF via LibreOffice; 503 if LibreOffice is missing |
POST | /api/<format>/preview/libreoffice-from-json | JSON → generate → PDF in one step; 16 MB cap |
POST | /api/<format>/standard-components | Resolve plugin components to standard definitions |
POST | /api/<format>/rasterize | Rasterize a single-slide pptx to PNG; 32 MB cap, DPI clamped |
GET | /api/<format>/renderers | Backends this format registers, default first — what the playground's backend picker offers |
GET | /api/<format>/cache-stats | Cache statistics |
DELETE | /api/<format>/cache | Clear the cache |
GET | /api/discovery/all | Discover plugins, documents, and themes |
GET | /api/discovery/plugins | /documents | /themes | Discover one kind |
GET | /api/discovery/plugin/:name | One plugin's metadata |
POST | /api/discovery/load-plugins | Load plugins into the server |
GET | /api/discovery/documents/:name/content | A discovered document's JSON |
GET | /api/discovery/themes/:name/content | A discovered theme's JSON |
GET | /api/discovery/schemas/document | /schemas/theme | Generated schemas |
GET | /api/fonts/catalog | Google Fonts catalog |
POST | /api/fonts | Materialize Google Fonts |
POST | /api/ai/chat | AI assistant chat stream (unless AI_ENABLED=false) |
GET | /health, /health/ready, /health/live | Health probes (outside /api) |
Programmatic use
@json-to-office/jto-cli also exports its building blocks for embedding — DocxFormatAdapter, PptxFormatAdapter, createAdapter, GeneratorFactory, SchemaGenerator, JsonValidator, the plugin services (PluginRegistry, PluginLoader, PluginDiscoveryService, PluginConfigService), loadConfig, registerCoreCommands (the composition point the full jto uses to add dev), and UI helpers. See the API reference.