O3O DocBuilder and conversion
o3oscript for text documents
Every operation of a type = text script: page setup, text style, header and footer, formatted runs, headings, paragraphs, lists, tables, images and page breaks, each with a parameter table and an example.
On this page
A type = "text" script builds a text document. Content goes in the body array (1 to 20,000 blocks) in reading order; each block has a type of heading, paragraph, list, table, image or page_break. Save formats: docx, odt, pdf.
Paper and margins: page#
The page object
sizeA3 | A4 | A5 | Letter | Legal | objectoptionalDefault:A4A named paper size, or{"width", "height"}in mm (50 to 2000).orientationportrait | landscapeoptionalDefault:portraitOrientation.marginobjectoptionalDefault:20 mm each sidePage margins.margin.top / right / bottom / leftnumber 0–2000 (mm)optionalDefault:20Margin of each side.
The style object (default text style)
fontstringoptionalDefault font.sizenumber 4–400 (pt)optionalDefault font size.color#RRGGBBoptionalDefault text colour.line_spacingnumber 50–300 (%)optionalLine spacing; 100 is single.
{
"page": {
"size": "A4",
"orientation": "portrait",
"margin": {
"top": 20,
"right": 15,
"bottom": 20,
"left": 30
}
},
"style": {
"font": "Liberation Serif",
"size": 13,
"color": "#000000",
"line_spacing": 120
}
}Header and footer#
The header and footer are a single line applied to every page. content mixes plain strings, run objects and auto-updating fields.
The header or footer object
contentarray, 1–50 itemsrequiredEach item is a string (≤ 1000 characters), a run object, or a field{"field": ...}.alignleft | center | rightoptionalDefault:centerAlignment.sizenumber 4–400 (pt)optionalFont size.color#RRGGBBoptionalText colour.
Auto-updating field
fieldpage_number | page_count | daterequiredpage_number: current page;page_count: total pages;date: build date as dd/MM/yyyy.
{
"header": {
"content": [
{
"text": "CÔNG TY CỔ PHẦN O3O",
"bold": true
},
" · Tài liệu nội bộ"
],
"align": "left",
"size": 9,
"color": "#555555"
},
"footer": {
"content": [
"Trang ",
{
"field": "page_number"
},
"/",
{
"field": "page_count"
},
" · Ngày in: ",
{
"field": "date"
}
],
"align": "right",
"size": 9
}
}Formatted runs: run#
Everything that accepts runs (headings, paragraphs, list items, table cells) and header or footer content uses the same run object: a stretch of text sharing one format. When a block has both text and runs, runs wins.
The run object
textstring, ≤ 100,000requiredThe run's text.boldbooleanoptionalBold.italicbooleanoptionalItalic.underlinebooleanoptionalUnderline.strikebooleanoptionalStrikethrough.superscriptbooleanoptionalSuperscript.subscriptbooleanoptionalSubscript.color#RRGGBBoptionalText colour.background#RRGGBBoptionalBackground (highlight) colour.sizenumber 4–400 (pt)optionalFont size.fontstringoptionalFont name.linkURL, ≤ 2048optionalTurns the run into a hyperlink.
{
"type": "paragraph",
"runs": [
{
"text": "Hạn nộp hồ sơ: "
},
{
"text": "30/09/2026",
"bold": true,
"color": "#C00000"
},
{
"text": ". Hướng dẫn tại "
},
{
"text": "office.o3o.vn",
"link": "https://office.o3o.vn",
"underline": true,
"color": "#1D55B8"
},
{
"text": ". Ký hiệu hoá học của nước là H"
},
{
"text": "2",
"subscript": true
},
{
"text": "O."
}
]
}Headings: heading#
The heading block
type"heading"requiredBlock type.levelinteger 1–6requiredHeading level.textstring, ≤ 2000optionalPlain text. Eithertextorrunsis required.runsarray of runs, 1–200optionalFormatted text.alignleft | center | right | justifyoptionalAlignment.color#RRGGBBoptionalText colour.page_break_beforebooleanoptionalDefault:falseStart a new page before the heading.
{
"type": "heading",
"level": 2,
"text": "Điều 3. Giá trị hợp đồng",
"color": "#1D55B8"
}Headings map to the Heading 1 to Heading 6 paragraph styles, so they appear in the editor's navigator and become bookmarks in PDF output.
Paragraphs: paragraph#
The paragraph block
type"paragraph"requiredBlock type.textstring, ≤ 100,000optionalPlain text; a\nbecomes a line break inside the paragraph. Eithertextorrunsis required.runsarray of runs, 1–2000optionalFormatted text.alignleft | center | right | justifyoptionalAlignment.spacing_beforenumber (mm)optionalSpace before the paragraph.spacing_afternumber (mm)optionalSpace after the paragraph.line_spacingnumber 50–300 (%)optionalLine spacing of this paragraph.indent_leftnumber (mm)optionalLeft indent.indent_rightnumber (mm)optionalRight indent.indent_firstnumber −200–200 (mm)optionalFirst-line indent; a negative value makes a hanging indent.keep_with_nextbooleanoptionalDefault:falseKeep with the next paragraph.page_break_beforebooleanoptionalDefault:falseStart a new page before it.
{
"type": "paragraph",
"text": "Căn cứ Bộ luật Dân sự năm 2015;\nCăn cứ nhu cầu và khả năng của hai bên.",
"align": "justify",
"indent_first": 10,
"spacing_after": 3,
"line_spacing": 130
}Lists: list#
The list block
type"list"requiredBlock type.stylebullet | numberoptionalDefault:bulletBullets or numbering.itemsarray, 1–5000 itemsrequiredEach item is a string (≤ 10,000 characters) or an item object.
The list item object
textstring, ≤ 10,000optionalPlain text. Eithertextorrunsis required.runsarray of runs, 1–200optionalFormatted text.itemsarray, 1–1000 itemsoptionalSub-list, same style as the parent.
{
"type": "list",
"style": "number",
"items": [
"Bên A thanh toán trong 7 ngày làm việc.",
{
"text": "Bên B có trách nhiệm:",
"items": [
"Bàn giao token bản quyền",
{
"runs": [
{
"text": "Hỗ trợ kỹ thuật "
},
{
"text": "trong giờ hành chính",
"italic": true
}
]
}
]
},
"Hai bên cùng bảo mật thông tin."
]
}Tables: table#
The table block
type"table"requiredBlock type.columnsarray, 1–63 columnsoptionalEach column is{"width", "align"}:widthis a relative width from 1 to 1000 (default 1).headerarray of cells, 1–63optionalHeader row.rowsarray of rows, 1–10,000requiredEach row is an array of 1 to 63 cells.borderfalse | border objectoptionalfalse: no lines; an object: draw every line, outer border and inner grid.header_fill#RRGGBBoptionalHeader row background.header_color#RRGGBBoptionalHeader row text colour.header_boldbooleanoptionalDefault:trueBold header row.zebra_fill#RRGGBBoptionalBackground of even data rows.repeat_headerbooleanoptionalDefault:trueRepeat the header row on each page.width_percentnumber 10–100optionalDefault:100Table width as a percentage of the text area.alignleft | center | rightoptionalDefault:leftTable position whenwidth_percentis below 100.sizenumber 4–400 (pt)optionalFont size inside the table.cell_paddingnumber 0–20 (mm)optionalDefault:1.5Cell padding.spacing_afternumber (mm)optionalSpace after the table.
Table cell as an object
textstring, ≤ 20,000optionalPlain text. Eithertextorrunsis required.runsarray of runs, 1–200optionalFormatted text.alignleft | center | right | justifyoptionalHorizontal alignment.valigntop | middle | bottomoptionalVertical alignment.boldbooleanoptionalBold.italicbooleanoptionalItalic.color#RRGGBBoptionalText colour.fill#RRGGBBoptionalCell background.
The border object
widthnumber 0.25–6 (pt)optionalDefault:0.5Line width.color#RRGGBBoptionalLine colour.
{
"type": "table",
"columns": [
{
"width": 1,
"align": "center"
},
{
"width": 5
},
{
"width": 2,
"align": "right"
}
],
"header": [
"STT",
"Hạng mục",
"Số tiền (đồng)"
],
"rows": [
[
1,
"Thiết kế giao diện",
"8.000.000"
],
[
2,
"Lập trình và kiểm thử",
"24.000.000"
],
[
{
"text": "Cộng",
"bold": true,
"align": "right"
},
null,
{
"text": "32.000.000",
"bold": true,
"fill": "#EAF1FB"
}
]
],
"border": {
"width": 0.5,
"color": "#7F7F7F"
},
"header_fill": "#1D55B8",
"header_color": "#FFFFFF",
"zebra_fill": "#F7F9FC",
"size": 11,
"spacing_after": 4
}Merged cells in text tables.
Images: image#
The image block
type"image"requiredBlock type.srcobjectrequiredImage source:{"url"}or{"base64", "mime"}.widthnumber (mm)optionalWidth. With only one side given, the other follows the original ratio.heightnumber (mm)optionalHeight.alignleft | center | rightoptionalDefault:leftImage alignment.captionstring, ≤ 1000optionalCaption: a centred italic paragraph right below the image.altstring, ≤ 1000optionalAlternative text for screen reader users.
Image source src
urlhttp/https URL, ≤ 2048optionalDocBuilder downloads the image under the SSRF rules. UseurlORbase64.base64stringoptionalStandard Base64 image data, WITHOUT adata:prefix, at most 10 MB decoded.mimeimage/png | image/jpeg | image/gif | image/svg+xmloptionalDefault:image/pngImage type when using base64.
{
"type": "image",
"src": {
"url": "https://example.com/images/so-do-to-chuc.png"
},
"width": 120,
"align": "center",
"caption": "Hình 1. Sơ đồ tổ chức",
"alt": "Sơ đồ tổ chức công ty"
}{
"type": "image",
"src": {
"base64": "iVBORw0KGgoAAAANSUhEUgAAAKAAAABaCAIAAACwpMoFAAAAsUlEQVR42u3RMQ0AIAwAwapgIYhhZccMyhiRhoA6aC55BX8x9lXhwgLAAizAAizAAizAgAVYgAVYgAVYgAELsAALsAALsAALMGABFmABFmABVgJ+q6twgAELsAALsAALsAADFmABFmABFmABBizAAizAAizAAizAgAVYgAVYgAVYGbidqcIBBizAAizAAizAAgxYgAVYgAVYgAUYsAALsAALsAALsAADFmABFmABFmClPpawm2fxo+U6AAAAAElFTkSuQmCC",
"mime": "image/png"
},
"height": 20,
"alt": "Ba dải màu"
}Page breaks: page_break#
{
"type": "page_break"
}A page_break block has only the type key. To make a heading or paragraph always start on a new page, set page_break_before: true on that block instead.
Complete example: a quotation#
bao-gia.json builds a two-page quotation: header, numbered footer, headings, formatted runs, a table with a total row, nested lists and a page break. The script has 28 units.
{
"o3oscript": 1,
"type": "text",
"meta": {
"title": "Báo giá O3O Office Online",
"author": "Công ty Cổ phần O3O",
"subject": "Báo giá bản quyền theo số kết nối",
"keywords": [
"báo giá",
"O3O Office Online"
],
"lang": "vi-VN"
},
"page": {
"size": "A4",
"orientation": "portrait",
"margin": {
"top": 20,
"right": 20,
"bottom": 20,
"left": 25
}
},
"style": {
"font": "Liberation Sans",
"size": 11,
"line_spacing": 115
},
"header": {
"content": [
{
"text": "O3O Office Online",
"bold": true,
"color": "#1D55B8"
},
" · Báo giá số BG-2026-0917"
],
"align": "left",
"size": 9
},
"footer": {
"content": [
"Trang ",
{
"field": "page_number"
},
" / ",
{
"field": "page_count"
}
],
"align": "center",
"size": 9,
"color": "#666666"
},
"body": [
{
"type": "heading",
"level": 1,
"text": "BÁO GIÁ BẢN QUYỀN",
"align": "center",
"color": "#1D55B8"
},
{
"type": "paragraph",
"align": "center",
"spacing_after": 6,
"runs": [
{
"text": "Số: BG-2026-0917",
"italic": true
},
{
"text": " · Ngày lập: 21/09/2026",
"italic": true
}
]
},
{
"type": "heading",
"level": 2,
"text": "1. Thông tin khách hàng"
},
{
"type": "paragraph",
"runs": [
{
"text": "Kính gửi: ",
"bold": true
},
{
"text": "Công ty TNHH Thương mại và Dịch vụ An Phát"
}
]
},
{
"type": "paragraph",
"spacing_after": 4,
"runs": [
{
"text": "Địa chỉ: ",
"bold": true
},
{
"text": "Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội"
}
]
},
{
"type": "heading",
"level": 2,
"text": "2. Chi tiết báo giá"
},
{
"type": "paragraph",
"spacing_after": 3,
"text": "Đơn giá áp cho toàn bộ số kết nối của đơn hàng. Một kết nối là một phiên soạn thảo đồng thời."
},
{
"type": "table",
"columns": [
{
"width": 1,
"align": "center"
},
{
"width": 6
},
{
"width": 2,
"align": "right"
},
{
"width": 2,
"align": "right"
},
{
"width": 3,
"align": "right"
}
],
"header": [
"STT",
"Hạng mục",
"Số kết nối",
"Đơn giá (USD)",
"Thành tiền (USD)"
],
"rows": [
[
1,
"O3O Office Online, bản doanh nghiệp, thuê bao 1 năm",
120,
"5,00",
"600,00"
],
[
2,
"Hỗ trợ triển khai và đấu nối Nextcloud",
null,
null,
"0,00"
],
[
{
"text": "Tổng cộng",
"bold": true,
"align": "right"
},
null,
null,
null,
{
"text": "600,00",
"bold": true,
"fill": "#EAF1FB"
}
]
],
"border": {
"width": 0.5,
"color": "#7F7F7F"
},
"header_fill": "#1D55B8",
"header_color": "#FFFFFF",
"header_bold": true,
"zebra_fill": "#F7F9FC",
"repeat_header": true,
"size": 10,
"spacing_after": 4
},
{
"type": "heading",
"level": 2,
"text": "3. Điều khoản"
},
{
"type": "list",
"style": "number",
"items": [
"Báo giá có hiệu lực 30 ngày kể từ ngày lập.",
"Bản quyền được cấp dưới dạng token ký số, nạp vào máy chủ của khách hàng.",
{
"text": "Phạm vi của bản doanh nghiệp:",
"items": [
"Số kết nối đồng thời theo đơn hàng",
"API DocBuilder đầy đủ",
"Cập nhật trong suốt thời hạn thuê bao"
]
}
]
},
{
"type": "paragraph",
"spacing_before": 8,
"align": "right",
"runs": [
{
"text": "ĐẠI DIỆN BÊN BÁN",
"bold": true
}
]
},
{
"type": "paragraph",
"align": "right",
"runs": [
{
"text": "(Ký, ghi rõ họ tên và đóng dấu)",
"italic": true,
"size": 9
}
]
},
{
"type": "page_break"
},
{
"type": "heading",
"level": 2,
"text": "Phụ lục: Cách đếm kết nối"
},
{
"type": "list",
"style": "bullet",
"items": [
"Một người mở ba tài liệu ở chế độ sửa được tính là ba kết nối.",
"Phiên chỉ đọc và các yêu cầu tới DocBuilder không được tính.",
"Khi chạm trần, phiên đang mở không bị ngắt và thao tác lưu không bị chặn."
]
}
],
"save": [
{
"format": "docx",
"filename": "bao-gia.docx"
}
]
}curl -sS http://localhost:8080/v1/build \
-H "Authorization: Bearer O3O_DEMO_KEY" \
-H "Content-Type: application/json" \
--data-binary @bao-gia.json \
-o bao-gia.docx -w "HTTP %{http_code}\n"import { readFile, writeFile } from "node:fs/promises";
const BASE_URL = "http://localhost:8080";
const HEADERS = { Authorization: "Bearer O3O_DEMO_KEY" };
const script = JSON.parse(await readFile("bao-gia.json", "utf8"));
const res = await fetch(`${BASE_URL}/v1/build`, {
method: "POST",
headers: { ...HEADERS, "Content-Type": "application/json" },
body: JSON.stringify(script),
signal: AbortSignal.timeout(90_000),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
await writeFile("bao-gia.docx", Buffer.from(await res.arrayBuffer()));import json
import requests
BASE_URL = "http://localhost:8080"
HEADERS = {"Authorization": "Bearer O3O_DEMO_KEY"}
with open("bao-gia.json", encoding="utf-8") as f:
script = json.load(f)
r = requests.post(f"{BASE_URL}/v1/build", headers=HEADERS, json=script, timeout=90)
if not r.ok:
raise RuntimeError(f"{r.status_code}: {r.text}")
with open("bao-gia.docx", "wb") as fh:
fh.write(r.content)<?php
$payload = file_get_contents("bao-gia.json");
$ch = curl_init("http://localhost:8080/v1/build");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["Authorization: Bearer O3O_DEMO_KEY", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 90,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($body === false || $status >= 400) {
throw new RuntimeException("HTTP $status: " . ($body === false ? curl_error($ch) : $body));
}
file_put_contents("bao-gia.docx", $body);using System.Net.Http.Headers;
using System.Text;
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(90) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "O3O_DEMO_KEY");
var json = await File.ReadAllTextAsync("bao-gia.json");
using var res = await http.PostAsync("http://localhost:8080/v1/build",
new StringContent(json, Encoding.UTF8, "application/json"));
if (!res.IsSuccessStatusCode)
throw new HttpRequestException($"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
await File.WriteAllBytesAsync("bao-gia.docx", await res.Content.ReadAsByteArrayAsync());