JSON DSL (renderDocx)
The renderDocx() function accepts a JSON-serializable schema — ideal for AI/LLM-driven generation, API integrations, or storing document templates as data.
Schemas from external sources
Platform defaults are intended for trusted schemas: Node enables local image reads and npm/local plugins; the browser enables URL plugins. For API requests or LLM output, validate the structure and explicitly restrict runtime capabilities:
import { renderDocx } from 'docx-kit/node'
const doc = await renderDocx(
{ content: [{ type: 'paragraph', text: 'External content' }] },
{ pluginLoader: false, resolveImage: false },
)
const bytes = await doc.toUint8Array()These settings also apply to images compiled by trusted plugins such as Figure. Register trusted plugins with .use() in application code. To allow selected sources, supply a pluginLoader with an allowLoad policy and a resolveImage adapter that enforces your application's resource boundary. Manifest validation and document structure validation do not authorize file access or sandbox executable plugins.
Basic Usage
import { renderDocx } from 'docx-kit'
const doc = await renderDocx({
page: { size: 'A4', margin: '20mm' },
styles: {
h1: { fontSize: 24, fontWeight: 'bold' },
p: { fontSize: 12 },
},
content: [
{ type: 'heading', level: 1, text: 'My Document', className: 'h1' },
{ type: 'paragraph', text: 'This document was generated from JSON.', className: 'p' },
{ type: 'pageBreak' },
{ type: 'heading', level: 2, text: 'Section 2', className: 'h1' },
{ type: 'paragraph', text: 'More content...', className: 'p' },
],
})
const blob = await doc.toBlob()Schema Structure
interface DocxSchema<TStyles> {
content: BlockNode<TStyles>[] // Ordered block nodes
page?: DocxKitConfig['page'] // Page dimensions
styles?: TStyles // Named stylesheet
}All Node Types
Heading Nodes
{
type: 'heading',
level: 1 | 2 | 3 | 4 | 5 | 6,
text: string,
className?: string,
id?: string,
style?: DocxStyleRule,
}{ type: 'heading', level: 1, text: 'Title', className: 'title' }
{ type: 'heading', level: 2, text: 'Section' }
{ type: 'heading', level: 3, text: 'Sub-section', id: 's1' }Paragraph Nodes
{
type: 'paragraph',
text: string,
className?: string,
id?: string,
style?: DocxStyleRule,
children?: InlineNode[], // rich content
}// Simple
{ type: 'paragraph', text: 'A simple paragraph.', className: 'body' }
// Rich with inline children
{
type: 'paragraph',
children: [
{ type: 'text', text: 'Regular text ' },
{ type: 'text', text: 'bold text', style: { fontWeight: 'bold' } },
{ type: 'text', text: ' and back to normal.' },
],
}Image Nodes
{
type: 'image',
data: string | Uint8Array | ArrayBuffer | Blob,
width?: UnitValue,
height?: UnitValue,
alt?: string,
imageType?: 'png' | 'jpeg' | 'jpg' | 'gif' | 'bmp',
floating?: boolean | { wrap?: string, x?: UnitValue, y?: UnitValue },
className?: string,
style?: DocxStyleRule,
}data accepts bytes, a Blob, or a data URL directly. When using renderDocx() from docx-kit/node, it also accepts local file paths and file: URLs. Core/browser callers must supply bytes/data URLs or provide an explicit resolveImage adapter in the runtime options; paths are not fetched automatically.
Table Nodes
{
type: 'table',
columns: TableColumn[],
data: Record<string, unknown>[],
bordered?: boolean,
striped?: boolean,
header?: boolean,
headerCellStyle?: DocxStyleRule,
cellStyle?: DocxStyleRule,
className?: string,
style?: DocxStyleRule,
}{
type: 'table',
columns: [
{ key: 'name', title: 'Name' },
{ key: 'value', title: 'Value', align: 'right' },
{
key: 'trend',
title: 'Trend',
align: 'center',
render: (val) => [
{ type: 'text', text: val as string, style: { color: '#22c55e' } },
],
},
],
data: [
{ name: 'Revenue', value: '$1.2M', trend: '↑ 15%' },
{ name: 'Costs', value: '$800K', trend: '↓ 3%' },
],
bordered: true,
headerCellStyle: { fontWeight: 'bold', backgroundColor: '#f5f5f5' },
}Page Break Nodes
{ type: 'pageBreak' }Plugin Nodes
{
type: 'plugin',
name: 'qrcode' | 'echarts' | string, // registered plugin name
options: Record<string, unknown>, // plugin-specific options
style?: DocxStyleRule,
}Important: Plugin nodes require the plugin to be declared in the schema's
pluginsarray, or registered with.use()before export.
import { echartsPlugin, qrcodePlugin, renderDocx } from 'docx-kit'
const doc = await renderDocx({ content: [...] })
doc
.use(qrcodePlugin())
.use(echartsPlugin())
const blob = await doc.toBlob()Complete JSON Example
import { echartsPlugin, qrcodePlugin, renderDocx } from 'docx-kit'
const report = {
page: { size: 'A4', margin: '20mm 25mm' },
styles: {
title: { fontSize: 28, fontWeight: 'bold', color: '#1a1a2e', textAlign: 'center' },
h2: { fontSize: 18, fontWeight: 'bold', color: '#333' },
body: { fontSize: 12, lineHeight: 1.5 },
small: { fontSize: 9, color: '#999' },
},
content: [
{ type: 'heading', level: 1, text: 'Monthly Report', className: 'title' },
{ type: 'paragraph', text: 'June 2026', className: 'small', style: { textAlign: 'center' } },
{ type: 'heading', level: 2, text: 'Executive Summary', className: 'h2' },
{ type: 'paragraph', text: 'Revenue exceeded projections by 12%. Key product lines showed strong growth across all regions.', className: 'body' },
{ type: 'heading', level: 2, text: 'Key Metrics', className: 'h2' },
{
type: 'table',
columns: [
{ key: 'metric', title: 'Metric' },
{ key: 'value', title: 'Value', align: 'right' },
{ key: 'change', title: 'Change', align: 'center' },
],
data: [
{ metric: 'Total Revenue', value: '$1.45M', change: '+12%' },
{ metric: 'New Customers', value: '847', change: '+23%' },
{ metric: 'Churn Rate', value: '2.1%', change: '-0.5%' },
{ metric: 'NPS Score', value: '72', change: '+5' },
],
bordered: true,
striped: true,
headerCellStyle: { fontWeight: 'bold', backgroundColor: '#f8f9fa' },
},
{ type: 'pageBreak' },
{ type: 'heading', level: 2, text: 'Revenue Chart', className: 'h2' },
{
type: 'plugin',
name: 'echarts',
options: {
option: {
title: { text: 'Monthly Revenue' },
xAxis: { data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'] },
yAxis: {},
series: [{ type: 'bar', data: [200, 220, 250, 240, 270, 290] }],
},
caption: 'Figure 1: Monthly revenue trend',
},
},
{ type: 'heading', level: 2, text: 'Contact', className: 'h2' },
{
type: 'plugin',
name: 'qrcode',
options: {
text: 'https://example.com/contact',
size: 128,
caption: 'Scan to visit our website',
},
},
],
}
// Build, register plugins, and export in the browser
const doc = await renderDocx(report)
doc
.use(echartsPlugin())
.use(qrcodePlugin())
const url = URL.createObjectURL(await doc.toBlob())
const anchor = document.createElement('a')
anchor.href = url
anchor.download = 'monthly-report.docx'
anchor.click()
URL.revokeObjectURL(url)AI/LLM Integration
The JSON DSL is designed for AI-driven document generation. Feed the schema structure to an LLM and it can generate complete .docx files:
import { renderDocx } from 'docx-kit/node'
// Example: AI generates this JSON from a natural language prompt
const aiResponse = {
page: { size: 'A4' },
content: [
{ type: 'heading', level: 1, text: '{{TITLE}}' },
{ type: 'paragraph', text: '{{CONTENT}}' },
],
}
// Template interpolation
function fillTemplate(schema: any, vars: Record<string, string>) {
const json = JSON.stringify(schema)
const filled = json.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? '')
return JSON.parse(filled)
}
const filled = fillTemplate(aiResponse, {
TITLE: 'Q2 Earnings Report',
CONTENT: 'Revenue grew 15% to $1.45M, exceeding analyst expectations.',
})
const doc = await renderDocx(filled)
await doc.save('ai-report.docx')