Skip to content

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

PackageBinIncludesIntended for
@json-to-office/jtojtoAll core commands plus the dev web playground (React, Monaco, Vite, AI assistant)Local development
@json-to-office/jto-clijto-cliCore commands only — no React/Monaco/Vite/AI dependenciesCI, 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 only

The 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

bash
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.

FlagTypeDefaultDescription
-o, --output <path>string<input basename> + .docx/.pptx in cwdOutput file path
-t, --template <name>stringTemplate name. Reserved — accepted but currently has no effect
--plugins [names-or-paths]string | booleanComma-separated plugin names/paths; bare flag enables auto-discovery
--plugin-dir <dir>stringDirectory to search for plugins
--theme <name-or-path>stringTheme name or path. Applied on both the standard and plugin-loaded paths, and overrides the document's props.theme
--theme-path <path>stringPath to a theme file (tried before --theme); .json files are parsed, other extensions are dynamically imported (uses the default or theme export)
--strictbooleanfalseAccepted no-op kept for compatibility — validation always runs; see below
--strict-fontsbooleanfalseFail generation on unresolved fontRegistry references
--no-google-fontsbooleanSets 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>stringSets 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>stringScan a directory for .ttf/.otf files and auto-register them by filename
--font-mode <mode>substitute | customcustomcustom 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-runbooleanfalsePrint a summary — input, output, format, theme, plugins (when any loaded), and Validation: passed — without writing files
--renderer <id>stringformat defaultBackend that writes the file — see Choosing a backend
--quality-profile <path>JSON filedefault format profileCustom profile or shipped-profile override: document class, rule parameters, and intended renderer targets
--quality-policy <path>JSON fileadvisoryPer-run rule overrides, suppressions, diagnostic budget, and gate
--quality-gate <severity>none | error | warning | infononeFail 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.

bash
jto docx generate report.docx.json --renderer office-open -o report.docx
json
{ "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:

  1. Origin. --theme and --theme-path are 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.
  2. Order within the surviving origin. themePath is tried first, and theme only if it yielded nothing.

Because step 1 settles the origin first, step 2 never mixes the two sources. The four combinations:

--theme--theme-pathTheme used
Config-file themePath, else config-file theme
setThe --theme value. A config-file themePath is ignored
setThe --theme-path file. A config-file theme is ignored
setsetThe --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)

bash
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.

FlagTypeDefaultDescription
-o, --output <path>stringredline.docxOutput redline path
--author <name>stringjson-to-officeRevision author shown in Word
--date <iso>stringnowRevision timestamp (ISO 8601); an invalid value errors
--json-out <path>stringAlso write the redline as a JSON document definition
-f, --format <format>pretty | jsonprettySummary output format; json emits { output, jsonOut, summary }
--dry-runbooleanfalseCompute 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

bash
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).

FlagTypeDefaultDescription
-t, --type <type>document | theme | autoautoAuto-detection by shape: name: 'docx'/'pptx', children[], slides[], or props.metadata.title ⇒ document; colors/fonts/styles/pageSetup/componentDefaults ⇒ theme
-s, --schema <path>stringValidate against a custom JSON Schema (Ajv with allErrors + formats); a missing schema file errors up front
--strictbooleanfalseUse strict validation — no cleaning or defaults applied
-q, --quietbooleanfalseOnly output errors
-f, --format <format>pretty | jsonprettyjson prints a machine-readable result array per file
-r, --recursivebooleanfalseRecurse into directories (**/*.json)
--quality-profile <path>JSON filedefault format profileCustom profile or shipped-profile override used by quality rules
--quality-policy <path>JSON fileadvisoryRule overrides, suppressions, budget, and gate
--quality-gate <severity>none | error | warning | infononeMark 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

bash
jto <docx|pptx> schemas [options]

Generates JSON Schemas for documents and themes — for IDE validation, tooling, and LLM workflows.

FlagTypeDefaultDescription
-o, --output-dir <path>string./schemasOutput directory
--plugins [names-or-paths]string | booleanInclude plugin schemas
--plugin-dir <dir>stringPlugin search directory
-f, --format <type>json | typeboxjsontypebox emits TypeBox TypeScript files
--theme-onlybooleanfalseOnly theme schemas (also skips plugin loading)
--document-onlybooleanfalseOnly document schemas
--splitbooleanfalseSeparate schema file per component type

Prints a table of generated paths plus any included plugins. See JSON schemas for the output layout.

discover

bash
jto <docx|pptx> discover [options]

Discovers json-to-office plugins, document JSON files, and theme files across the project.

FlagTypeDefaultDescription
-j, --jsonbooleanfalseJSON output
-s, --schemabooleanfalseInclude full schemas in output (plugins only)
-e, --examplesbooleanfalseInclude usage examples (plugins only)
-t, --type <type>plugin | document | theme | allallWhat to discover; an invalid value exits 1
-s, --scope <path>stringLimit discovery to a directory
--max-depth <depth>integer10Maximum search depth
--include-node-modulesbooleanfalseAlso search node_modules
-v, --verbosebooleanfalseDebug output
--groupedbooleanfalseGroup 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

bash
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.

FlagTypeDefaultDescription
-t, --template <type>stringbasicProject template (currently only basic — the scaffold content is fixed)
--skip-installbooleanfalseSkip running npm install after scaffolding

The scaffold contains:

  • package.json — scripts dev, generate, validate, schemas calling jto <format> …; dependency @json-to-office/json-to-docx (or -pptx); devDependencies @json-to-office/jto and TypeScript
  • example.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

bash
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].

FlagTypeDefaultDescription
--fonts-dir <path>string./fontsDirectory of local .ttf/.otf files to list

fonts inspect

bash
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

bash
jto <docx|pptx> fonts install <family> [options]

Downloads a Google Fonts family as local TTFs.

FlagTypeDefaultDescription
-w, --weights <list>comma/space list400,700Weights as 100-step integers 100–900; an invalid token errors
--italicsbooleanfalseAlso download italic variants
-d, --dir <path>string./fontsOutput 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)

bash
jto <docx|pptx> dev [options]

Starts the development server with the web playground UI (see Playground).

FlagTypeDefaultDescription
-p, --port <port>integer3003 (docx) / 3004 (pptx)Server port. Precedence: CLI flag > config-file server.port > PORT env var > format default
-H, --host <host>stringlocalhostBind host
-o, --openbooleanfalseOpen the browser on start
-c, --config <path>stringConfig 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 / .js
  • json-to-office.config.json / .js
  • .json-to-officerc, .json-to-officerc.json / .js
  • legacy json-to-docx / json-to-pptx variants of the above
  • package.json keys json-to-office, json-to-docx, json-to-pptx

Shape:

json
{
  "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) → autoDiscoverplugins--plugin-dirpluginDirs.

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:

KeyDefaultDescription
modedevelopmentdevelopment or production
server.portformat defaultServer 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.hostlocalhostBind host
development.hmrPort5173Vite 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

VariableUsed byDefaultEffect
HIGHCHARTS_SERVER_URLgenerate (both CLIs), devHighcharts export server URL for chart rendering
HIGHCHARTS_API_KEYgenerate, devAPI key sent to the Highcharts server
HIGHCHARTS_API_KEY_HEADERgenerate, devx-api-keyHeader name for the Highcharts API key
JTO_PPTX_RASTERIZER_URLdocx generateRemote rasterizer for visual components; unset ⇒ in-process LibreOffice rasterizer
JTO_PPTX_RASTERIZER_API_KEYdocx generate, devAPI key sent to the remote rasterizer
LIBREOFFICE_PATHrasterizer, previewsauto-detectedPath to the LibreOffice binary
PDFTOPPM_PATHrasterizerauto-detectedPath to the pdftoppm binary
DEBUGgeneratetrue enables generator debug mode
PORTjto devListen port, used when the config file does not set server.port and -p is not given
JTO_CLIENT_PATHjto devOverride the playground client directory
AI_ENABLEDjto devenabledfalse disables the /api/ai routes
API_AUTH_MODEjto devsee belowrequired | auto | disabled. Defaults to required in production, auto locally
PLUGIN_AUTOLOADjto devon in development and testtrue 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_KEYjto devCredential required on /api/* when auth mode requires it
API_KEY_HEADERjto devx-api-keyHeader carrying the API key
RATE_LIMIT_WINDOW_MSjto dev server900000Rate-limit window applied across /api/*
RATE_LIMIT_MAXjto dev server100 prod / 1000 devRequests per window per client, per method+path
TRUST_PROXY_HEADERSjto dev serverfalseTrust X-Real-IP / X-Forwarded-For for rate-limit identity. Enable only behind a proxy that overwrites them
MAX_REQUEST_BODY_SIZEjto dev server33554432Body cap across /api/* (32 MiB)
MAX_CONCURRENT_REQUESTSjto dev server8 prod / 64 devIn-flight /api/* requests before 503
MAX_FILE_SIZEjto dev server10485760Upload size cap (10 MiB)
OUTBOUND_SOURCE_MODEjto dev serversafe prod / development otherwisesafe restricts remote assets to allowlisted HTTPS hosts and blocks local paths
OUTBOUND_HOST_ALLOWLISTjto dev serveremptyComma-separated hosts allowed in safe mode; *.example.com wildcards supported
CORS_ORIGINjto dev server*Allowed origin(s), comma-separated
LIBREOFFICE_TIMEOUT_MSjto dev server30000LibreOffice conversion timeout
LOG_LEVELjto dev serverinfoerror | warn | info | debug
CACHE_ENABLEDjto dev servertruefalse disables the generation cache
CACHE_MAX_SIZE_MBjto dev server100Cache size cap
CACHE_TTL_SECONDSjto dev server3600Cache TTL
CACHE_MAX_ITEMSjto dev server1000Cache item cap
NODE_ENVbothdevelopmentAnything 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

CodeMeaning
0Success (including --help / --version)
1Any 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/null still shows the report while 1>/dev/null isolates 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 FAIL behaves.

For anything you intend to parse, prefer the machine-readable mode rather than scraping the human output:

bash
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 no API_KEY is configured the server answers 503, so a production deployment fails closed rather than open.
  • auto — enforced only when API_KEY is 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.

MethodRouteDescription
POST/api/<format>/generateGenerate 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>/validateValidate a document JSON
POST/api/docx/diffDiff two documents into a redline (DOCX only). 32 MB body cap; 30 req / 15 min in production
POST/api/<format>/preview/libreofficeConvert an uploaded file to PDF via LibreOffice; 503 if LibreOffice is missing
POST/api/<format>/preview/libreoffice-from-jsonJSON → generate → PDF in one step; 16 MB cap
POST/api/<format>/standard-componentsResolve plugin components to standard definitions
POST/api/<format>/rasterizeRasterize a single-slide pptx to PNG; 32 MB cap, DPI clamped
GET/api/<format>/renderersBackends this format registers, default first — what the playground's backend picker offers
GET/api/<format>/cache-statsCache statistics
DELETE/api/<format>/cacheClear the cache
GET/api/discovery/allDiscover plugins, documents, and themes
GET/api/discovery/plugins | /documents | /themesDiscover one kind
GET/api/discovery/plugin/:nameOne plugin's metadata
POST/api/discovery/load-pluginsLoad plugins into the server
GET/api/discovery/documents/:name/contentA discovered document's JSON
GET/api/discovery/themes/:name/contentA discovered theme's JSON
GET/api/discovery/schemas/document | /schemas/themeGenerated schemas
GET/api/fonts/catalogGoogle Fonts catalog
POST/api/fontsMaterialize Google Fonts
POST/api/ai/chatAI assistant chat stream (unless AI_ENABLED=false)
GET/health, /health/ready, /health/liveHealth 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.

Released under the MIT License.