Convert Kindle highlight HTML exports into Markdown, in your browser. Built for Obsidian and other Markdown-based note apps.
Drop one or many .html files exported from the Kindle app — get one .md per file, with YAML frontmatter, headings, blockquoted highlights, and notes/bookmarks. Customize the output template to match whatever vault format you use.
The conversion runs entirely in the browser. Your files never leave your device — no server, no upload, no analytics.
- In the Kindle app, open a book's notebook and tap Share → Export Notes. You'll get an HTML attachment.
- Drop the
.htmlfile (or several at once) onto the dropzone at the top of the page. - Pick what happens with the result via the When conversion finishes select:
- Download Markdown file (default) — one
.mdper input. - Copy to clipboard — the rendered Markdown lands on your clipboard. Multiple files are joined by
---separators in one buffer. - Open in Obsidian — each file is handed to Obsidian's
obsidian://newURL handler in your last-active vault.
- Download Markdown file (default) — one
- Multi-file conversion. Drop many
.htmlfiles at once; one.mdper input. - YAML frontmatter with
title,author, andaliases, ready for Obsidian. - Three output modes — download, copy to clipboard, or hand off to Obsidian.
- Both Kindle export formats supported. The older
Highlight (Yellow) - …heading and the newer markup with<span class="annotation_yellow">…</span>color spans both parse correctly. - Customizable Handlebars template with subtle in-editor syntax highlighting. The full set of variables, helpers, and date-formatting tokens is documented in a built-in reference panel. The template auto-saves to the device's localStorage as you type.
- Live preview. Toggle between editing the template source and previewing it rendered against a baked-in sample dataset, so you can iterate without dropping a real file each time.
- Copy template to clipboard for sharing or backup. The button shows an in-place "Copied" confirmation.
The default template produces this for each book:
---
title: ...
author: ...
aliases: ['"..." by ...']
---
## Highlights
From *...* by ...:
### Section heading
> A highlight.
A note.
*Bookmark* at Chapter 2 > Page 25 · Location 410The template is Handlebars. Below is everything you can use; the same reference is also available inline in the customize panel.
Wrap a name in double curly braces. Built-in values:
{{title}}— the book's title{{author}}— the author or author list{{date}}— the moment of conversion (use theformatDatehelper below to render it)
{{#each sections}} … {{/each}} repeats its inner block once per chapter. Inside the loop:
{{heading}}— the chapter name (may be empty for entries before any heading){{#each entries}} … {{/each}}— repeats once per highlight, note, or bookmark
Inside an entry:
{{type}}—"highlight","note", or"bookmark"{{text}}— the highlighted or noted text{{location}}— Kindle's location string (e.g.Chapter 1 > Page 19 · Location 285){{color}}— only present for highlights
Combine {{#if}} with the eq helper to render only highlights (or only notes, or only bookmarks):
Pass {{date}} to the formatDate helper with a format string:
| Template | Output |
|---|---|
{{formatDate date "yyyy-MM-dd"}} |
2024-01-05 |
{{formatDate date "MMMM d, yyyy"}} |
January 5, 2024 |
{{formatDate date "yyyy-MM-dd[T]HH:mm"}} |
2024-01-05T08:04 |
Wrap literal characters in [brackets] so they aren't interpreted as tokens (the T in ISO timestamps, for example).
Tokens (examples for Jan 5, 2024 at 08:04:09):
| Token | Meaning | Example |
|---|---|---|
yyyy / yy |
year | 2024 / 24 |
MMMM / MMM |
month name | January / Jan |
MM / M |
month number | 01 / 1 |
dd / d |
day of month | 05 / 5 |
HH / H |
hour (24h) | 08 / 8 |
mm / m |
minute | 04 / 4 |
ss / s |
second | 09 / 9 |
If you embed a value inside a single-quoted YAML string (the default aliases field does this), wrap it with {{yamlEscape …}} to double any apostrophes — otherwise titles like Surely You're Joking, Mr. Feynman! break the YAML parser.
No file ever leaves your device — there's no server-side component. Your settings (template + chosen output mode) are stored in your browser's localStorage on the current device only.
- Multi-paragraph highlights are flattened. When you select across multiple paragraphs in the Kindle app, Kindle merges those paragraphs into a single block of text before writing the HTML export. The paragraph breaks aren't recoverable from the export, so the converted Markdown reflects what Kindle gave us.
bun install
bun run dev # bun's HTML dev server with HMR (Tailwind plugin via bunfig.toml)
bun test # extract + template tests via happy-dom
bun run build # writes dist/index.html, .css, .jsTested with Bun 1.3+. Deployed on Vercel (bun run build → dist/).
src/index.html— entry. Dropzone + collapsible customize panel inside one nested "platter" card.src/styles.css— Tailwind v4 (viabun-plugin-tailwind) plus the.platter/.platter-pad/.platter-inner-cardclasses that drive concentric corner radii viacalc()from a single--platter-radiusknob.src/scripts/extract.ts—extractKindleData(doc): KindleData. Pure function: takes a parsedDocument, returns{ title, author, date, sections: [{ heading, entries: [{ type, text, location, color? }] }] }.src/scripts/template.ts—DEFAULT_TEMPLATE,renderTemplate(data, template),formatDate,tryCompileTemplate(debounced edit-time validation),highlightHandlebars(syntax overlay),SAMPLE_DATA(used by the preview toggle).src/scripts/settings.ts— localStorage I/O +buildObsidianUri.src/scripts/main.ts— DOM glue: file input, accordion, preview toggle, copy/reset, conversion loop.tests/extract.test.ts,tests/template.test.ts— 27 tests via Bun's runner with@happy-dom/global-registratorforDOMParser.tests/fixtures/*.html— minimal Kindle-shaped exports covering single-author name swap, multi-author, all entry types, modern color-span format, empty body, and a missing-noteText edge case.build.ts— production bundle entry (Bun.build+bun-plugin-tailwind, minified).
CC0-1.0 — public domain. Use it however you like.