https://github.com/wiseair-srl/json-to-office
Generate professional .docx and .pptx files from JSON definitions
https://github.com/wiseair-srl/json-to-office
cli document-generation docx json office open-source powerpoint pptx typescript word
Last synced: 6 days ago
JSON representation
Generate professional .docx and .pptx files from JSON definitions
- Host: GitHub
- URL: https://github.com/wiseair-srl/json-to-office
- Owner: Wiseair-srl
- License: mit
- Created: 2026-03-11T20:13:03.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-05-07T09:31:49.000Z (about 1 month ago)
- Last Synced: 2026-05-07T10:36:42.516Z (about 1 month ago)
- Topics: cli, document-generation, docx, json, office, open-source, powerpoint, pptx, typescript, word
- Language: TypeScript
- Homepage: https://github.com/Wiseair-srl/json-to-office
- Size: 4.97 MB
- Stars: 23
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Codeowners: .github/CODEOWNERS
- Security: SECURITY.md
Awesome Lists containing this project
README
# json-to-office
**Documents as data, not code.** Describe `.docx` and `.pptx` files as plain JSON (serializable, portable, language-agnostic) and render them into real Office documents.
[](https://github.com/Wiseair-srl/json-to-office/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/@json-to-office/json-to-docx)
[](https://www.npmjs.com/package/@json-to-office/json-to-pptx)
[](LICENSE)
> **Try the live playgrounds:** [DOCX Playground](https://docx.json-to-office.com) | [PPTX Playground](https://pptx.json-to-office.com)
## Table of contents
- [Quick start](#quick-start)
- [The problem](#the-problem)
- [The solution](#the-solution)
- [Architecture](#architecture)
- [Why not just use X?](#why-not-just-use-x)
- [Features](#features)
- [Full examples](#full-examples)
- [Who it's for](#who-its-for)
- [Use cases](#use-cases)
- [Examples](#examples)
- [Packages](#packages)
- [Development](#development)
## Quick start
```bash
npm install @json-to-office/json-to-docx @json-to-office/json-to-pptx
```
```ts
import { generateAndSaveFromJson as docx } from '@json-to-office/json-to-docx';
import { generateAndSaveFromJson as pptx } from '@json-to-office/json-to-pptx';
// DOCX
await docx(
{
name: 'docx',
props: { theme: 'minimal' },
children: [
{ name: 'heading', props: { text: 'Q1 Report', level: 1 } },
{
name: 'paragraph',
props: { text: 'Revenue grew **32%** quarter-over-quarter.' },
},
],
},
'report.docx'
);
// PPTX
await pptx(
{
name: 'pptx',
props: { theme: 'corporate', grid: { columns: 12, rows: 6 } },
children: [
{
name: 'slide',
props: { background: { color: 'background' } },
children: [
{
name: 'text',
props: {
text: 'Q1 Results',
style: 'title',
grid: { column: 0, row: 0, columnSpan: 12 },
},
},
{
name: 'chart',
props: {
type: 'bar',
data: [
{
name: 'Revenue',
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
values: [1.2, 2.4, 3.1, 4.2],
},
],
grid: { column: 0, row: 1, columnSpan: 8, rowSpan: 5 },
},
},
],
},
],
},
'deck.pptx'
);
```
Or explore interactively with the visual playground (Monaco editor, live preview, AI assistant):
**Try it online:** [DOCX Playground](https://docx.json-to-office.com) | [PPTX Playground](https://pptx.json-to-office.com)
```bash
npm install -g @json-to-office/jto
jto docx dev
jto pptx dev
```

For CI / scripted pipelines that don't need the playground, install the lean [`@json-to-office/jto-cli`](packages/jto-cli) instead — same `generate`/`validate`/`schemas`/`discover`/`init`/`fonts` commands, ~16 deps vs ~70.
```bash
npm install -g @json-to-office/jto-cli
jto-cli docx generate doc.json
```
## The problem
Libraries like [docx](https://github.com/dolanmiu/docx) and [pptxgenjs](https://github.com/gitbrent/PptxGenJS) are imperative, code-first APIs. You build documents by constructing class instances and chaining methods. Powerful, but the document definition _is_ the program. You can't store it in a database, send it over an API, generate it from an LLM, or hand it to a non-developer.
## The solution
json-to-office makes the document definition **data**. You describe a `.docx` or `.pptx` as a JSON tree, and the library renders it into a real Office file. The JSON can live in a DB row, travel over HTTP, come out of GPT-4, or be edited in a visual playground with autocomplete and validation. Definition and rendering are fully decoupled.
```jsonc
// This is a complete document definition. Store it, send it, generate it.
{
"name": "docx",
"props": { "theme": "minimal" },
"children": [
{ "name": "heading", "props": { "text": "Q1 Report", "level": 1 } },
{
"name": "paragraph",
"props": { "text": "Revenue grew **32%** quarter-over-quarter." },
},
{
"name": "table",
"props": {
"columns": [
{
"header": { "content": "Region" },
"cells": [
{ "content": "North America" },
{ "content": "Europe" },
{ "content": "APAC" },
],
},
{
"header": { "content": "Revenue" },
"cells": [
{ "content": "$4.2M" },
{ "content": "$2.8M" },
{ "content": "$1.6M" },
],
},
],
},
},
{
"name": "image",
"props": { "path": "https://example.com/chart.png", "width": "80%" },
},
],
}
```
Your document is just JSON now. Generate it, store it, validate it, version it, and render it anywhere, without touching TypeScript or Office internals.
## Architecture
A JSON document is a tree of **modules**. Each module contains **base components** (heading, paragraph, table, etc.) and optionally **custom components** that you define in your project via a plugin system.

The **processor** walks the tree. When it encounters a custom component, it validates the props against the schema, resolves the requested semver version, calls `render()`, and splices the result back into the tree. If `render()` returns other custom components, the processor re-expands them recursively (up to 20 levels deep). The output is a flat tree of base components only, which the **renderer** converts into native Office objects via docx.js or pptxgenjs.
### Custom components (plugin system)
Define custom components with `createComponent` + `createVersion`. Each version has a [TypeBox](https://github.com/sinclairzx81/typebox) schema for props (chosen over Zod for first-class JSON Schema support) and an async `render()` function that returns an array of standard components (or other custom components, which are expanded recursively):
```ts
import {
createComponent,
createVersion,
} from '@json-to-office/json-to-docx/plugin';
import { Type } from '@sinclair/typebox';
const kpiCard = createComponent({
name: 'kpi-card',
versions: {
'1.0.0': createVersion({
propsSchema: Type.Object({
label: Type.String(),
value: Type.Number(),
unit: Type.Optional(Type.String()),
}),
render: async ({ props }) => [
{ name: 'heading', props: { text: props.label, level: 3 } },
{
name: 'statistic',
props: {
value: `${props.value}${props.unit ? ` ${props.unit}` : ''}`,
},
},
],
}),
},
});
```
Then register it in the generator and use it in JSON like any built-in component:
```ts
const generator = createDocumentGenerator().addComponent(kpiCard).build();
await generator.generateToFile(
{
name: 'docx',
children: [
{ name: 'kpi-card', props: { label: 'Revenue', value: 4.2, unit: 'M$' } },
],
},
'report.docx'
);
```
**Versioning.** Each component supports multiple semver-keyed versions with different props and rendering logic. The document specifies which version to use; if omitted, the latest is resolved automatically. This enables non-breaking evolution of enterprise templates across clients.
**Schema generation.** The enriched schema (standard + custom components) is exportable as JSON Schema, so you get validation and IDE autocomplete even for your custom components.
## Why not just use X?
Generating an Office document from a backend service usually means one of four things. Start from the question:
**What should a document _be_, in a system?**
An artifact rendered once and emailed? Any tool works. An _output of your platform_ — emitted thousands of times, on demand, by services and LLMs, branded, regeneratable, auditable? Then it should behave like data: serializable, validatable, versionable, diffable. None of the four common approaches give you that.
| | json-to-office | Imperative libs
(docx, pptxgenjs, officegen, react-pdf) | Template-driven
(Carbone, docxtemplater) | SaaS / AI doc tools
(Gamma, Tome) | Plain Claude
(direct prompt → .docx/.pptx) |
| ----------------- | ---------------------------- | ----------------------------------------------------------- | -------------------------------------------- | ------------------------------------- | ---------------------------------------------- |
| **Document is** | Declarative JSON | Code | Binary template + data | Hosted artifact | Free-form prompt |
| **Serializable** | Yes | No: trapped in code | Partial: data is JSON, structure isn't | No: locked in platform | No: prompt ≠ output |
| **Reproducible** | Yes: same JSON, same bytes | Yes | Yes | No | No: stochastic |
| **LLM-friendly** | Schema-constrained output | Fragile: no schema | Needs pre-made template | N/A | No structure, no validation |
| **Validation** | Full TypeBox schemas | None | None | N/A | None |
| **Themes** | Built-in, swappable | Manual styling | Baked into template | Built-in | Whatever the model picks |
| **Extensibility** | Plugin architecture + semver | Library APIs | Limited | None | None |
| **Self-hosted** | Yes | Yes | Yes (+ LibreOffice) | No | No (API) |
**vs. imperative libs.** docx and pptxgenjs are json-to-office's own rendering backends. The difference is the layer above: a schema-validated JSON contract, themes, layout pipeline, plugin architecture, and TypeBox schemas that double as TypeScript types and runtime validators. What your code emits stops being code and starts being a value — storable, sendable, replayable.
**vs. template-driven.** Templates work when structure is fixed and only data changes. They break the moment structure becomes dynamic: conditional sections, variable-length tables, data-driven layouts. A `.docx` template is an opaque binary — you cannot diff it, lint it, or compose it. JSON you can.
**vs. SaaS / AI doc tools.** Built for a human assembling one deck in a browser, not a backend emitting thousands of branded documents from structured inputs. No API contract, no self-hosting, no ownership of the pipeline or the artifact.
**vs. Plain Claude.** Claude can emit a `.pptx` directly if you ask. Two problems: (1) output is non-deterministic — same prompt, different document, no validation, no replay; (2) it conflates _content_ with _rendering_. With a JSON layer the LLM emits a small, schema-constrained value, and rendering is deterministic code. Reliable structure, themed output, byte-for-byte regeneratable artifacts.
## Features
### DOCX: 13 components
| Component | Highlights |
| ------------------ | --------------------------------------------------------------- |
| paragraph, heading | Markdown-style bold/italic in text, h1–h6 |
| table | Auto-width columns, merged cells, styled headers |
| image | URL / file / base64, contain / cover / crop, captions, floating |
| list | 57 numbering formats, 9 nesting levels |
| columns | Multi-column layouts |
| text-box | Positioned text regions |
| statistic | KPI cards |
| highcharts | Server-side chart rendering |
| header / footer | Per-section, first-page variant |
| table of contents | Auto-generated from headings |
| section | Independent page size, orientation, margins |
### PPTX: 7 components
| Component | Highlights |
| ---------- | --------------------------------------------------------------------------------- |
| text | Bullets, hyperlinks, style presets |
| image | Rotation, rounded corners, shadows |
| shape | 15 types: rect, ellipse, arrow, star, cloud, etc. |
| table | Auto-pagination with header repeat, colspan/rowspan |
| chart | 8 native PowerPoint types: bar, line, pie, area, doughnut, radar, bubble, scatter |
| highcharts | Server-side chart rendering |
| slide | Grid-based positioning, backgrounds, templates |
### Document diff (DOCX)
Because documents are data, two versions diff like data — and the result opens in Word as native tracked changes:
```bash
jto docx diff contract-v1.json contract-v2.json -o redline.docx
```
Every text edit becomes a real Word revision (accept/reject, author, timestamp) and the file opens in review mode. Word-level diff on paragraphs, headings, and list items; structural changes (tables, images, charts) are replaced and reported in the CLI summary. Programmatic API: `diffDocuments(oldDoc, newDoc)` from `@json-to-office/json-to-docx`; HTTP API: `POST /api/docx/diff`. In the visual playground (`jto docx dev`), use the **Compare** button in the sidebar to diff two documents and open the redline with live preview. Try it with [examples/contract-v1.docx.json](examples/contract-v1.docx.json) and [contract-v2.docx.json](examples/contract-v2.docx.json).
### Cross-format
- **Theme system**: colors, fonts, spacing, component defaults. 3 built-in themes per format (minimal, corporate, vibrant/modern), or define your own.
- **Font system**: curated Office-safe font list plus code-side `fonts.extraEntries` option for embedding Google Fonts and custom TTF/OTF across DOCX and PPTX. Themes name fonts; code registers them. See [docs/fonts.md](docs/fonts.md).
- **Schema validation**: full TypeBox schemas that serve as TypeScript types _and_ runtime validators. Catch errors before rendering.
- **Plugin architecture**: create versioned custom components with `createComponent()`. Full TypeScript support, chainable API, schema generation.
- **Template / placeholder system** (PPTX): slide templates with named placeholder regions, static + dynamic content, style inheritance.
- **Grid layout** (PPTX): 12-column responsive grid with configurable margins and gutters.
## Full examples
### DOCX
```bash
npm install @json-to-office/json-to-docx
```
```ts
import { generateAndSaveFromJson as docx } from '@json-to-office/json-to-docx';
await docx(
{
name: 'docx',
props: { theme: 'minimal' },
children: [
{ name: 'heading', props: { text: 'Q1 Report', level: 1 } },
{
name: 'paragraph',
props: { text: 'Revenue grew **32%** quarter-over-quarter.' },
},
{
name: 'table',
props: {
columns: [
{
header: { content: 'Metric' },
cells: [{ content: 'Revenue' }, { content: 'Users' }],
},
{
header: { content: 'Value' },
cells: [{ content: '$4.2M' }, { content: '12,847' }],
},
],
},
},
],
},
'report.docx'
);
```
### PPTX
```bash
npm install @json-to-office/json-to-pptx
```
```ts
import { generateAndSaveFromJson as pptx } from '@json-to-office/json-to-pptx';
await pptx(
{
name: 'pptx',
props: {
title: 'Q1 Review',
theme: 'corporate',
grid: { columns: 12, rows: 6 },
},
children: [
{
name: 'slide',
props: { background: { color: 'background' } },
children: [
{
name: 'text',
props: {
text: 'Q1 Results',
style: 'title',
grid: { column: 0, row: 0, columnSpan: 12 },
},
},
{
name: 'chart',
props: {
type: 'bar',
data: [
{
name: 'Revenue',
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
values: [1.2, 2.4, 3.1, 4.2],
},
],
grid: { column: 0, row: 1, columnSpan: 8, rowSpan: 5 },
},
},
],
},
],
},
'deck.pptx'
);
```
### CLI
```bash
# Start the visual playground with live preview
jto docx dev
jto pptx dev
# Generate files directly
jto docx generate ./my-template.json -o ./report.docx
jto pptx generate ./my-template.json -o ./deck.pptx
```
### Visual playground
The dev server gives you a Monaco editor with JSON autocomplete and validation, live document preview, built-in templates, and theme switching, all in the browser. **LibreOffice is not required**: the playground renders previews natively. If LibreOffice (headless) is installed, the playground can optionally use it for high-fidelity PDF rendering, the only way to get pixel-accurate output, especially for PPTX where no browser renderer exists. It also integrates **Claude** (Opus/Sonnet/Haiku) as a built-in AI chat assistant: describe a document in plain English and get schema-validated JSON back, rendered live. Both LibreOffice and Claude are playground-only extras; the core rendering libraries have zero dependency on either.
## Who it's for
- **API-driven SaaS teams**: Document definitions live in the database, rendered on demand. No template files to deploy, no LibreOffice sidecar.
- **LLM-powered generation**: An LLM can reliably emit a schema-validated JSON document definition. No hallucinated method names, no wrong constructor signatures — just data constrained by a schema.
- **Decoupled pipelines**: A data team or visual editor produces JSON; a Node.js service renders it. No shared code, language, or deployment.
## Use cases
- **On-demand reports from a dashboard**: User clicks "Export" → your backend fetches data, builds JSON, renders `.docx` or `.pptx`, returns the file. No template files on disk.
- **LLM document generation**: Prompt an LLM with the TypeBox schema → it outputs valid JSON → render it. No hallucinated method calls, no brittle code generation.
- **Scheduled batch exports**: A cron job queries your DB, assembles JSON definitions, renders hundreds of personalized documents (invoices, contracts, reports) without spinning up LibreOffice.
- **Multi-tenant SaaS templates**: Store document definitions per-tenant in your DB. Tenants customize structure and styling through a UI; your backend renders on demand.
- **Internal tooling / back-office**: Non-developers define documents in the visual playground, save the JSON, and ops renders them via CLI or API — no deploys needed.
- **Headless CMS → Office docs**: Content lives in a CMS as structured data. A pipeline transforms it into json-to-office JSON and renders downloadable `.docx`/`.pptx` files.
- **CI/CD artifacts**: Generate changelogs, release notes, or test reports as `.docx` files directly in your pipeline from structured build data.
## Examples
See the `[examples/](examples/)` directory for complete, runnable JSON definitions:
- **[invoice.docx.json](examples/invoice.docx.json)**: Branded invoice with line items, payment instructions, and retainer terms
## Packages
| Package | Description |
| ------------------------------------------------------- | ------------------------------------ |
| `[@json-to-office/json-to-docx](packages/json-to-docx)` | DOCX generation from JSON |
| `[@json-to-office/json-to-pptx](packages/json-to-pptx)` | PPTX generation from JSON |
| `[@json-to-office/jto](packages/jto)` | CLI + dev server + visual playground |
| `[@json-to-office/jto-cli](packages/jto-cli)` | Lean CLI (no playground deps) |
Internal packages
| Package | Description |
| ----------------------------------------------------- | -------------------------------------- |
| `[@json-to-office/core-docx](packages/core-docx)` | Core DOCX engine |
| `[@json-to-office/core-pptx](packages/core-pptx)` | Core PPTX engine |
| `[@json-to-office/shared](packages/shared)` | Format-agnostic schemas and validation |
| `[@json-to-office/shared-docx](packages/shared-docx)` | DOCX-specific schemas |
| `[@json-to-office/shared-pptx](packages/shared-pptx)` | PPTX-specific schemas |
## Development
```bash
git clone https://github.com/Wiseair-srl/json-to-office.git
cd json-to-office
pnpm install
pnpm build
pnpm dev # Start dev server with hot reload
```
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development guide.
## License
[MIT](LICENSE), Wiseair srl