codaforge
v0.1.3
Published
Turn any Coda doc into clean Markdown or a searchable static site — faithfully, with no tool-specific bias.
Maintainers
Readme
coda-forge
Turn any Coda doc into clean Markdown or a searchable static site — faithfully.
Coda has no good export for canvas-heavy docs. Its API export and "copy as Markdown"
both flatten tables, multi-column layouts, callouts, page icons, and embeds. But the
published web renders everything correctly — as bespoke kr-* DOM, not semantic HTML,
so naïve scrapers lose the same formatting the API does.
coda-forge reconstructs high-fidelity Markdown from that rendered DOM: nested lists,
block & pull quotes, callouts, split-header tables, multi-column layouts, dividers,
page icons, bold/italic/code, internal-link rewriting, and embed resolution (Coda
proxies YouTube/Vimeo/SlideShare through iframely — these are resolved to native
embeds). Then it can build a self-contained, searchable static site from the result.
No API token needed for public docs. Nothing about any specific doc is baked in.
Install
npm i -g codaforge # CLI (installs `coda-forge` and `codaforge` commands)
# or use it ad-hoc:
npx codaforge --helpScraping needs a Chromium. coda-forge uses playwright-core and will use your system
Chrome/Edge automatically, or point it at a binary:
export CODA_FORGE_CHROME=/path/to/chrome # optionalQuick start
# 1) A public Coda doc → a folder of clean Markdown (+ assets + embeds)
npx codaforge md https://yourdoc.coda.io/ -o ./out
# 2) A public Coda doc → a searchable static site
npx codaforge site https://yourdoc.coda.io/ -o ./site --title "My Docs"
npx serve ./site
# 3) A single saved Coda HTML file → Markdown (offline, no browser)
npx codaforge render page.html -o page.mdThe site output is fully self-contained (HTML + one CSS + one JS + a JSON search
index): a hierarchical sidebar mirroring the doc's page tree, client-side search,
light/dark themes, image lightbox, and rendered video/slide embeds.
Config file
Drop a coda-forge.config.mjs in your project (auto-detected), or pass --config:
export default {
source: { type: 'scrape', url: 'https://yourdoc.coda.io/', concurrency: 5 },
output: './site',
site: {
title: 'My Docs',
accent: '#2947d6',
intro: 'Everything you need to know.',
lenses: [
// Config-driven "lenses": extra grouped index pages. The engine knows nothing
// about your content model — you decide what to group and how.
{ id: 'guides', title: 'Guides', match: (p) => /guide/i.test(p.title) },
{ id: 'api', title: 'API', match: (p) => p.markdown.includes('## Endpoint') },
],
},
};See examples/startup-secrets.config.mjs for a
real doc rebuilt with multiple lenses.
Private docs (Coda API)
For docs you own, use the API source (no browser needed):
export default {
source: { type: 'api', docId: 'AbCdEf', token: process.env.CODA_API_TOKEN },
output: './site',
};| | Scrape (rendered DOM) | Coda API | |---|---|---| | Auth | none — any public doc | token + doc access | | Private docs | ✗ | ✓ | | Fidelity (canvas) | high | Coda's export (lossier) | | Needs a browser | yes | no |
Programmatic API
import { codaHtmlToMarkdown, forge, forgeSite, generateSite } from 'codaforge';
// Pure: HTML → Markdown + metadata (no I/O, no browser)
const { title, icon, markdown, embeds, links } = codaHtmlToMarkdown(html, {
title: 'My Page',
links: { rewrite: (slug) => `/docs/${slug}` },
});
// Whole doc → forged pages
const pages = await forge({ source: { url: 'https://doc.coda.io/' } });
// Whole doc → static site
await forgeSite({ source: { url: 'https://doc.coda.io/' }, output: './site' });Sub-path exports: codaforge/transform, codaforge/source, codaforge/site.
What it handles
- Split-header tables — Coda renders the header row as a separate, duplicated sticky bar; coda-forge reunites it with the body and drops the duplicate.
- Emphasis through zero-width chars — Coda sprinkles zero-width characters that break Markdown bold/italic delimiters; these are stripped first.
- iframely embeds — proxied YouTube/Vimeo become native embeds; SlideShare decks keep the (cross-origin-embeddable) iframely URL.
- Collapsed nav — the whole page tree is expanded before discovery; hierarchy is derived from visual indentation, so it works regardless of Coda's CSS classes.
- Page icons, callouts, block/pull quotes, multi-column layouts, dividers.
Limitations
- Interactive Coda widgets (fillable tables, buttons, live formulas) can't be reproduced — they appear as static content or links.
- The API source uses Coda's async export, which is lossier than the rendered DOM for heavily-formatted canvases.
- Images are referenced at their original (Coda CDN) URLs by default.
License
MIT © soup up (soupup.ai)
