Install
lexicalToOdt() is available via the odf-kit/lexical sub-export —
no separate package needed:
Basic usage
Call editor.getEditorState().toJSON() to get the
SerializedEditorState, then pass it to lexicalToOdt(). The
function returns a Uint8Array containing a complete, valid
.odt file.
import { lexicalToOdt } from "odf-kit/lexical"; import { writeFileSync } from "fs"; // Get serialized state from your Lexical editor const editorState = editor.getEditorState().toJSON(); const bytes = await lexicalToOdt(editorState, { pageFormat: "A4", }); writeFileSync("document.odt", bytes); // → Valid .odt file, opens in LibreOffice, Google Docs, Word
Browser usage
odf-kit/lexical is pure ESM — it works in any modern browser. Call
lexicalToOdt() and trigger a download with the resulting bytes.
import { lexicalToOdt } from "odf-kit/lexical"; const editorState = editor.getEditorState().toJSON(); const bytes = await lexicalToOdt(editorState, { pageFormat: "A4" }); const blob = new Blob([bytes], { type: "application/vnd.oasis.opendocument.text" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "document.odt"; a.click(); URL.revokeObjectURL(url);
Images
Base64 data URL images are embedded automatically. For remote image URLs (e.g. images
stored on a CDN or IPFS), provide a fetchImage callback that resolves the URL
to raw bytes:
const bytes = await lexicalToOdt(editorState, { pageFormat: "A4", fetchImage: async (src) => { const response = await fetch(src); return new Uint8Array(await response.arrayBuffer()); }, });
Page format options
Pass a LexicalToOdtOptions object as the second argument:
const bytes = await lexicalToOdt(editorState, { pageFormat: "letter", // "A4" | "letter" | "legal" | "A3" | "A5" marginTop: "2.5cm", marginBottom: "2.5cm", marginLeft: "2.5cm", marginRight: "2.5cm", });
Supported Lexical node types
All standard Lexical node types are supported:
| Node type | ODT output |
|---|---|
| paragraph | text:p — with alignment support |
| heading (h1–h6) | text:h with Heading 1–6 styles |
| quote | Indented paragraph (1cm left margin) |
| code | Monospace paragraph (Courier New) |
| list (bullet) | Unordered text:list |
| list (number) | Ordered text:list with numFormat |
| custom-list | Ordered with lower-alpha / upper-alpha / upper-roman |
| table | table:table with colSpan / rowSpan |
| image (decorator) | draw:frame with embedded image |
| horizontalrule | Paragraph with bottom border |
| text | text:span with bold, italic, underline, strikethrough, code, subscript, superscript, color |
| link / autolink | text:a hyperlink |
| linebreak | text:line-break |
| code-highlight | Monospace text run |
| hashtag | Plain text run |
Proton Docs integration
Proton Docs uses Lexical as its editor engine. Adding ODT export requires changes to three
files in applications/docs-editor/src/app/Conversion/Exporter/:
Step 1 — Create EditorOdtExporter.ts:
import { lexicalToOdt } from 'odf-kit/lexical' import { EditorExporter } from './EditorExporter' export class EditorOdtExporter extends EditorExporter { async export(): Promise<Uint8Array<ArrayBuffer>> { return lexicalToOdt(this.editor.getEditorState().toJSON(), { pageFormat: 'A4', fetchImage: async (src) => { const b64 = await this.callbacks.fetchExternalImageAsBase64(src) if (!b64) return undefined const binary = atob(b64.split(',')[1] ?? b64) return Uint8Array.from(binary, c => c.charCodeAt(0)) }, }) } }
Step 2 — Add one case to ExportDataFromEditorState.ts:
case 'odt': return new EditorOdtExporter(editorState, callbacks).export()
Step 3 — Add 'odt' to the export type union in
DataTypesThatDocumentCanBeExportedAs in @proton/docs-shared.
That's the complete integration — three files, one new case statement.
TypeScript types
The odf-kit/lexical sub-export includes full TypeScript types. The input type
is loosely typed to accept any valid Lexical SerializedEditorState without
requiring a Lexical dependency in your project:
import { lexicalToOdt } from "odf-kit/lexical"; import type { LexicalToOdtOptions, LexicalSerializedEditorState, } from "odf-kit/lexical";