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

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 / .odp orchestrators + typed parsers for text:* / table:* / draw:* / style:* / chart:* / math:math / form:* / dr3d:* + mimetype, manifest, meta, settings, styles.
  • Opt-in extras (extras in src/main.js, one file per extra under src/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 *-misc passthrough (catch-all _passthrough: true): text, style, draw, table, office, legacy-staroffice.
  • Bundles ready to plug in: odt|ods|odp-large (core + P0) and odt|ods|odp-full (*-large + P1/P2/P3).

Design choices

  • Typed document model — serializable JSON tree rather than a flat object.
  • _extras mechanism — untyped elements and attributes are preserved in _extras and re-emitted on write.
  • mimetype STORED — 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, TextDecoder only.
  • 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

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

Relations

Depends on

Used by

Install

npm install @awacloud/odf@1.0.0

Source

https://github.com/awacloud/awa

Directory: packages/front/office/odf