Convert ODS (LibreOffice Calc) to Markdown Tables
.ods file in the browser, parses content.xml, expands repeated row/column runs, and writes each Calc sheet as a GFM pipe table. No LibreOffice install, no headless soffice step, no upload.Processing happens 100% in your browser — nothing is uploaded.
Merged cells and flat ODF
table:number-columns-spanned) cannot be represented in GFM: the value stays in the first cell and the covered cells come through empty. Uncompressed flat ODF files (.fods) and password-protected spreadsheets are not accepted — re-save as a normal .ods in Calc first.How to convert ODS to Markdown
- Drop: drag your .ods file onto the dropzone, or click to browse.
- Process: SheetJS unzips the archive and converts every sheet locally, deriving column alignment and escaping pipes.
- Copy: copy the GFM tables or download the result as a .md file.
Why Convert ODS to Markdown with MD Convert?
Reads the OASIS OpenDocument XML directly
ODS is a ZIP archive whose payload is content.xml, with typed cells (office:value-type of float, date, currency, percentage) and repeat attributes that collapse runs of identical cells. Those repeats are expanded, so the grid you see in Calc is the grid you get in Markdown.
Formatted values with correct alignment
Cells are exported as their displayed text, so dates stay dates and percentages keep their sign rather than arriving as raw fractions. Columns whose body cells are all numeric get `---:` and right-align; text and date columns stay left-aligned.
No office suite required
The usual route is installing LibreOffice and running soffice --convert-to csv, then converting the CSV. This reads the .ods itself in JavaScript — one step, no 400 MB dependency, and it works on a machine you do not administer.
100% local — nothing is uploaded
The file is read through the File API and parsed in a Web Worker inside your tab, then discarded when you close the page. Budgets, payroll extracts and unpublished datasets stay on your device.
ODS to Markdown: Before and After
The sheet name becomes an H2, the first row becomes the header, and the currency column is right-aligned because every value in it is numeric. No LibreOffice process is involved at any point.
<!-- content.xml (excerpt from Sheet "Budget") -->
<table:table table:name="Budget">
<table:table-row>
<table:table-cell office:value-type="string">
<text:p>Item</text:p></table:table-cell>
<table:table-cell office:value-type="string">
<text:p>Cost</text:p></table:table-cell>
</table:table-row>
<table:table-row>
<table:table-cell office:value-type="string">
<text:p>Hosting</text:p></table:table-cell>
<table:table-cell office:value-type="currency"
office:value="1250.5"><text:p>1,250.50</text:p></table:table-cell>
</table:table-row>
</table:table>## Budget
| Item | Cost |
| --- | ---: |
| Hosting | 1,250.50 |Understanding the ODS Format: OASIS OpenDocument v1.3 / ISO/IEC 26300
- Format
- OpenDocument Spreadsheet (ODS)
- Specification
- OASIS OpenDocument v1.3 / ISO/IEC 26300
- Media type
application/vnd.oasis.opendocument.spreadsheet- Parser used
- SheetJS (xlsx)
ODS is the spreadsheet half of OpenDocument, an OASIS standard that was later published as ISO/IEC 26300 — a genuinely open format rather than a vendor format with a specification bolted on afterwards. It is the native save format of LibreOffice Calc, Apache OpenOffice Calc, Gnumeric and Collabora, and it is the format public-sector bodies across the EU are most likely to mandate for interchange.
Structurally an .ods file is a ZIP archive whose payload is `content.xml`, with `styles.xml`, `meta.xml` and a manifest alongside it. The markup is `table:table` for a sheet, `table:table-row` for a row, and `table:table-cell` for a cell, each cell carrying an `office:value-type` attribute (`float`, `date`, `currency`, `percentage`, `string`, `boolean`) plus a typed value attribute such as `office:value` or `office:date-value`. Compared with the OOXML approach, ODS keeps the machine value and its type on the cell itself instead of behind a style index — noticeably easier to read as XML.
ODS has one compression trick worth knowing about, because it is where naive parsers break: repeated cells and rows are collapsed with `table:number-columns-repeated` and `table:number-rows-repeated`. A row of forty blank cells is stored once with a repeat count of forty, and a parser that ignores the attribute silently shifts every subsequent column left. SheetJS expands repeats correctly, which is why the grid you see in Calc is the grid you get in Markdown.
Converting ODS without LibreOffice installed
The usual advice for getting Markdown out of an .ods file is to install LibreOffice and drive it headlessly — `soffice --convert-to csv` — then convert the CSV. That works, but it means a 400 MB office suite, a command line, and a two-step pipeline for what is conceptually one transformation. It is also unavailable on a machine you do not administer, which describes most corporate laptops.
This converter reads the OpenDocument container directly in JavaScript. SheetJS is dynamically imported when a conversion starts, unzips the archive, parses `content.xml`, expands the repeat attributes, and produces the same normalised workbook model it produces for .xlsx. From there the Markdown writer is shared code, which is exactly why ODS and Excel output are byte-for-byte consistent: same alignment logic, same pipe escaping, same per-sheet H2 headings.
Nothing is installed and nothing is uploaded. The file is read through the File API into memory in your tab, parsed in a Web Worker so a large sheet does not freeze the interface, and discarded when you close the page. For a budget, a payroll extract or an unpublished dataset, that is the difference between a conversion and a disclosure.
Value types, dates and the serial-number trap
OpenDocument stores a typed value and a display string on the same cell, which makes it possible to get formatting right without heuristics. The converter takes the formatted text, so a date cell arrives as the date you see in Calc, a percentage arrives with its percent sign, and a currency figure keeps its thousands separators.
This is worth spelling out because the opposite behaviour is the single most common defect in spreadsheet-to-Markdown tooling. Read the raw machine value instead of the formatted one and a date becomes an epoch-relative number — 46265 rather than 2026-08-31 — which is not obviously wrong when you skim the output, only when someone downstream tries to read the table. The same applies to percentages, which are stored as fractions: 0.125 rather than 12.5%.
Booleans come through as their displayed text, and cells that Calc shows as an error (`#DIV/0!`, `#N/A`) come through as that error text rather than being silently blanked. Keeping the error visible is deliberate — a blank cell in a published table invites the reader to assume the data is missing rather than broken.
Multi-sheet files, headers and alignment
Every table in the document is converted in order. Each becomes an H2 heading carrying the sheet name exactly as Calc stores it, followed by its GFM pipe table, and the result reports how many sheets were found. Nothing is selected or discarded on your behalf: you get the whole document as one Markdown file and delete what you do not need, which is faster than re-running a conversion because a picker defaulted to the wrong sheet.
The first row of each table is used as the header row. That is the right assumption for the overwhelming majority of real spreadsheets and the wrong one for a sheet that opens with a merged title banner, so trim the banner before converting if your file has one. Alignment is then derived per column: if every non-empty body cell in a column is numeric — currency symbols, percent signs, thousands separators and parenthesised negatives included — the delimiter row gets `---:` and the column right-aligns. Text and date columns stay left-aligned.
Pipes inside cell text are escaped to `\|` so a value like `A|B` cannot break the row, and in-cell line breaks are flattened to spaces because GFM tables cannot contain a newline inside a cell. Empty trailing rows are dropped rather than emitted as rows of empty pipes.
ODS, XLSX and CSV: which interchange path to take
If your source of truth is Calc, converting the .ods directly is strictly better than saving as .xlsx first. Every format conversion is an opportunity for loss, and the Excel writer in Calc has to map OpenDocument constructs onto OOXML ones; skipping that step removes a translation layer for no cost, since the same engine reads both.
Saving as CSV instead throws away the sheet names and all number formatting, and reintroduces the delimiter problems ODS does not have: a cell containing a comma or a newline now depends on quoting being handled correctly at both ends. It also flattens a multi-sheet document into one sheet per file. Reading the XML directly means there is no delimiter to disagree about.
For automated pipelines, the honest answer is that a scripted approach belongs in the pipeline: LibreOffice headless, or Python with `odfpy` or pandas, gives you scheduling and reproducibility that a browser tab does not. This tool is aimed at the other case — a specific file, right now, containing something you would rather not upload — where the whole job should take a few seconds and leave no trace.
Known limitations of ODS to Markdown conversion
Being explicit about what a converter cannot do saves you a wasted upload. These are the boundaries of what is recoverable from ODS programmatically:
- Merged cells (`table:number-columns-spanned`) cannot survive: GFM has no colspan, so the value stays in the first cell of the range and the covered cells are empty.
- Charts, images, pivot ranges, cell background colours and conditional formatting are dropped — Markdown has no representation for them.
- Formulas are exported as their last-calculated value. There is no calculation engine in the browser, so a formula saved without a cached result comes through blank and is reported as a warning.
- The first row of every sheet is assumed to be the header row; title banners above the data will land in the header.
- Password-protected or encrypted ODS files cannot be opened. Remove the protection in Calc first.
- Flat ODF files (.fods, uncompressed XML) are not accepted — save as .ods before converting.
Who Converts ODS to Markdown?
Linux / FOSS user
Works in LibreOffice Calc and needs Markdown for a README or wiki without installing a Microsoft format round-trip.
Public-sector analyst
Publishes open-data tables held in the mandated OpenDocument format into a static-site documentation portal.
Obsidian / Notion writer
Pulls a Calc budget or tracker into a vault note as a real Markdown table instead of an attached file nobody opens.
Data analyst
Receives .ods exports from a Calc-based team and needs version-controllable, diffable tables.
How You Can Verify the Privacy Claim
Zero server upload
Conversion runs inside your browser tab. Open DevTools, switch to the Network panel, and convert a file: for every format except URL to Markdown you will see no request carrying your document — because there is no endpoint to send it to.
Off the main thread
Heavy parsing is dispatched to a Web Worker, so a 500-page PDF or a large spreadsheet never freezes the interface. Everything is plain JavaScript — no native plugin, no WebAssembly toolchain, nothing to install.
Nothing to sign up for
No login, no quota, no paywall, and no tracking tied to your files. Analytics are cookieless and aggregate only. Read the privacy policy for the full data-flow breakdown, including the one proxied exception.
The parsers doing the work
No proprietary black box: each format is handled by a widely audited open-source library, running client-side at the version pinned in our lockfile.
ODS to Markdown — FAQ
Are ODS formulas exported as values or expressions?
As values. OpenDocument stores both the formula and its last calculated result, and the cached result is what lands in the table. There is no calculation engine in the browser, so a file written by a script without cached results shows those cells blank and raises a warning with the count — opening and re-saving it in LibreOffice Calc fixes that.
Does it support multi-sheet ODS files?
Yes. Every table in the document is converted in order, each as an H2 heading carrying the sheet name followed by its own GFM table, and a warning reports how many sheets were found. You get the whole document as one Markdown file and delete the sections you do not need.
How are dates and encodings handled?
Cells are exported as the text Calc displays, so a date typed as 2026-08-31 exports as 2026-08-31 rather than as an epoch-relative serial number, and percentages keep their percent sign instead of arriving as 0.125. The file is UTF-8 XML inside the ZIP container and the output is UTF-8 Markdown, so accented, CJK and emoji content passes through unchanged.
What happens if a cell contains a pipe character?
It is escaped as \| so the row cannot break. An unescaped pipe inside a GFM cell ends that cell early and shifts every following column, which is the classic way a converted table ends up misaligned. Newlines inside a cell are collapsed to spaces, since GFM tables cannot contain a line break within a cell.
What is the difference between ODS and XLSX conversion here?
Only the container. Both formats are normalised into the same workbook model and then written by the same Markdown code, so alignment, pipe escaping and per-sheet headings are identical. Convert your .ods directly rather than saving as .xlsx first — skipping Calc's OOXML export removes a translation layer for no benefit.