Pure JavaScript library to read and write OpenDocument documents
(ODF — .odt, .ods, .odp) in the browser. No npm dependency beyond
the monorepo framework: ZIP, XML parsing/serialization and the ODF
package container are either implemented here or consumed from
@awacloud/fw.
The package exposes a typed document model for the three formats (a
serializable JSON tree), a core set of parsers and orchestrators, and
opt-in extras that promote more of the ODF vocabulary into typed fields.
Whatever the typed model does not cover is preserved: unknown XML stays
in _extras, and write(read(x)) re-emits the package parts it does
not regenerate (pictures, thumbnails, embedded objects) byte-for-byte
with their manifest media types, together with the read styles,
settings and metadata; meta:generator is rewritten to
@awacloud/odf. One limitation: a frame anchored directly in a paragraph
is re-emitted after the paragraph's runs, not at its original position
(inside a text:span, frames, fields, spacing and nested spans keep
their position). It mirrors the architecture of the sibling package
@awacloud/ooxml.
Installation
npm install @awacloud/odf
In the browser, via import map:
<script type="importmap">
{ "imports": {
"@awacloud/fw": "/node_modules/@awacloud/fw/src/main.js",
"@awacloud/fw/": "/node_modules/@awacloud/fw/src/",
"@awacloud/odf": "/node_modules/@awacloud/odf/src/main.js",
"@awacloud/odf/": "/node_modules/@awacloud/odf/src/"
}}
</script>
Quick Start
Core (factory wiring)
Every odf module is a { name, dependencies, factory } descriptor resolved by
an @awacloud/fw ModuleRuntime. The package entry exports the fw modules
the odf modules depend on (fw_require) next to the odf ones (modules):
import { ModuleRuntime } from '@awacloud/fw/core/runtime.js';
import { fw_require, modules } from '@awacloud/odf';
const runtime = new ModuleRuntime();
runtime.registerAll(fw_require);
runtime.registerAll(modules);
const odt = runtime.resolve('odt');
const bytes = odt.write(odt.fromText(['Hello, world.']));
const decoded = odt.read(bytes);
console.log(odt.toText(decoded)); // → "Hello, world."
Typed paragraph manipulation
Continuing with the odt instance resolved above:
const doc = {
body: [
odt.paragraph('Title', { styleName: 'Title' }),
{
type: 'paragraph',
runs: [
{ type: 'text', value: 'plain ' },
{ type: 'span', value: 'styled', styleName: 'T1' },
{ type: 'line-break' },
{ type: 'text', value: 'next line' }
]
}
]
};
const styledBytes = odt.write(doc, { meta: { title: 'Doc', creator: 'Alice' } });
Reading styles back
odt.read() turns a style reference into semantic fields (bold / italic
/ strike / monospace, list numbering, bordered cells, margins-aligned
tables) only when the style is fully mapped and comes from the content.xml
automatic styles or from the styles.xml office:styles named styles; it
never follows style:parent-style-name chains, ignores the automatic styles
of styles.xml, and leaves every other style as an opaque styleName — see
docs/guide/read-write-odt.md.
Pre-built bundles (dist/)
Each assembly root (odt, odt-large, odt-full, and the same three for
ods and odp) ships as a two-surface build, generated by
tools/generate-bundles.mjs (repository only, not part of the published
package; a driver over @awacloud/tool-prebuild-generator) and regenerated
with:
bun run gen:bundles
| Surface | Path | dependencies |
|---|---|---|
dist/build/<root>.{js,min.js,meta.json} (@awacloud/odf/build/<root>.js) |
fw-mode | the fw modules xml, bitstream, huffman, deflate, zip and crc32 — DI-injected by an @awacloud/fw ModuleRuntime |
dist/standalone/<root>.{js,min.js,meta.json} (@awacloud/odf/standalone/<root>.js) |
framework-free | none — every fw + odf-local factory inlined |
dist/build/index.js (@awacloud/odf/build/index.js) is an fw-mode barrel
re-exporting the whole @awacloud/odf namespace (the registration arrays and
every named descriptor) for bulk registration on an @awacloud/fw runtime.
See docs/api/bundles/prebuilt/README.md
for the resolve-key table and usage examples.
Source structure
src/
├── main.js entry point — `modules[]`, `extras[]`, `bundle[]`, `fw_require[]` + named descriptors
├── errors.js OdfError + ParseError/RenderError/ContractError
├── (xml parser consumed from `@awacloud/fw/io/codec/xml.js`)
├── _shared/ odfShared (namespaces, XML helpers) + odfWalker (hook engine)
├── pkg/ ODF container (ZIP + mimetype + manifest.xml)
├── meta/ meta.xml (dc:* + meta:*)
├── settings/ settings.xml (preserve-unknowns)
├── style/ styles.xml, automatic styles, page layout, master pages
├── text/ text:p, headings, lists, sections, fields, tracked changes, style registry
├── table/ draw/ chart/ math/ form/ dr3d/ number/ mc/
├── odt/ ods/ odp/ top-level orchestrators (+ their walkers)
├── extra/ opt-in extras (typed or passthrough)
└── bundles/ `*-large` / `*-full` bundle descriptors
Hook .use(...)
The odt / ods / odp orchestrators expose a walker extensible via
.use(...extensions): each extra can publish hydrate* / dehydrate* hooks
(Paragraph, Span, Heading, List, Table, Cell, Frame, Slide, Metadata,
Settings, Styles — each orchestrator walks the node types it carries).
Idempotent (duplicates are filtered out). See
docs/guide/extending.md and
docs/api/ for the per-module coverage.
Documentation
docs/README.md— full indexdocs/guide/getting-started.md— first stepsdocs/guide/read-write-odt.mddocs/guide/pkg-overview.md— ODF containerdocs/api/— API reference per module
Tests
Co-located unit tests (src/**/*.test.js) + integration tests in tests/;
run them from the monorepo root:
bun test packages/front/office/odf/
Coverage
- Core —
.odt/.ods/.odporchestrators + typed parsers fortext:*/table:*/draw:*/style:*/chart:*/math:math/form:*/dr3d:*+ mimetype, manifest, meta, settings, styles. - Opt-in extras (
extrasinsrc/main.js, one file per extra undersrc/extra/), in four tiers:- P0 deep typing: tracked-changes, fields, lists, table-advanced, page-styles, properties-typed, shapes, presentation.
- P1 complementary deep typing: text-meta-extended, sections-advanced, toc/index, image-extended, chart-typed, animations-smil, forms-controls, number-format-extended, meta-extended, math-mathml.
- P2 secondary-domain typing: dr3d-3d, database-sources, settings-extended, script-macros, dsig-signatures.
- P3
*-miscpassthrough (catch-all_passthrough: true): text, style, draw, table, office, legacy-staroffice.
- Bundles ready to plug in:
odt|ods|odp-large(core + P0) andodt|ods|odp-full(*-large+ P1/P2/P3).
Design choices
- Typed document model — serializable JSON tree rather than a flat object.
_extrasmechanism — untyped elements and attributes are preserved in_extrasand re-emitted on write.mimetypeSTORED — first ZIP entry, compression 0, as required by the ODF spec.- Single dependency — only
@awacloud/fw(workspace). - Worker-safe — each factory is self-sufficient (serializable via
factory.toString()). - Browser-only — no Node API:
Uint8Array,TextEncoder,TextDecoderonly. - Bounded ZIP reading —
read()caps entry count, total uncompressed size and per-entry ratio (see the guide's security section). - Mirror of
@awacloud/ooxml— same factory pattern, same conventions, same docs format.
See also
@awacloud/ooxml— sibling OOXML library@awacloud/fw— shared runtime + zip + crc32 + codecsCHANGELOG.md— L0 → L4 history
Exposed sub-paths
| Sub-path | Target | Usage |
|---|---|---|
@awacloud/odf |
src/main.js |
Index — modules[], extras[], bundle[], fw_require[] and every named descriptor |
@awacloud/odf/errors |
src/errors.js |
odfErrors — OdfError / ParseError / RenderError / ContractError factory |
@awacloud/odf/odt |
src/odt/odt.js |
.odt orchestrator |
@awacloud/odf/odt-large |
src/bundles/odt-large.js |
Core odt + P0 extras |
@awacloud/odf/odt-full |
src/bundles/odt-full.js |
odt-large + P1/P2/P3 extras |
@awacloud/odf/ods |
src/ods/ods.js |
.ods orchestrator |
@awacloud/odf/ods-large |
src/bundles/ods-large.js |
Core ods + P0 extras |
@awacloud/odf/ods-full |
src/bundles/ods-full.js |
ods-large + P1/P2/P3 extras |
@awacloud/odf/odp |
src/odp/odp.js |
.odp orchestrator |
@awacloud/odf/odp-large |
src/bundles/odp-large.js |
Core odp + P0 extras |
@awacloud/odf/odp-full |
src/bundles/odp-full.js |
odp-large + P1/P2/P3 extras |
@awacloud/odf/pkg |
src/pkg/package.js |
ODF container (ZIP + mimetype + manifest) |
@awacloud/odf/text |
src/text/content.js |
textContent — <office:text> body orchestrator |
@awacloud/odf/table |
src/table/table.js |
tableTable — <table:table> parser/renderer |
@awacloud/odf/draw |
src/draw/frame.js |
drawFrame — <draw:frame> wrapping image / text-box / object children |
@awacloud/odf/chart |
src/chart/chart.js |
chartChart — <chart:chart> root + chart content helpers |
@awacloud/odf/math |
src/math/math.js |
mathMath — embedded MathML passthrough |
@awacloud/odf/form |
src/form/forms.js |
formForms — <office:forms> + typed form:* controls |
@awacloud/odf/dr3d |
src/dr3d/dr3d.js |
dr3dScene — <dr3d:scene> + typed lights and solids |
@awacloud/odf/extra/* |
src/extra/*.js |
Direct imports of the opt-in extras |
@awacloud/odf/bundles/* |
src/bundles/*.js |
Direct imports of the bundles |
@awacloud/odf/build/* |
dist/build/* |
Pre-built bundles — fw-mode (dependencies declared) + the index.js barrel |
@awacloud/odf/standalone/* |
dist/standalone/* |
Pre-built bundles — framework-free (every fw factory inlined) |
Maturity
L4 (awa.maturity in package.json) — the highest level of the
monorepo's maturity scale: typed .odt / .ods / .odp, tested and
documented to publication quality.
Licence
AGPL-3.0-only — see LICENSE.
Copyright (c) 2026 AwaCloud SAS
This package is also available under a commercial licence from AwaCloud SAS,
as stated in NOTICE.
Project
- Website: https://awaforge.eu
- Source:
packages/front/office/odf - Issues: https://github.com/awacloud/awa/issues
- Security policy: https://github.com/awacloud/awa/blob/@awacloud/odf@1.0.0/SECURITY.md
Relations
Depends on
Used by
Install
npm install @awacloud/odf@1.0.0Source
https://github.com/awacloud/awa
Directory: packages/front/office/odf