Converts Markdown file(s) to the Anki cards.
The markdown it reads is Flashcard Markdown, a specification with a conformance corpus that MDAnki runs in its own test suite. MDAnki conforms as a consumer: it parses everything the format calls valid, and never refuses a file because one card in it is malformed.
- Node.js v24+ (ESM-only)
- pnpm (preferred package manager)
pnpm install -g @shbernal/mdankiConvert a single markdown file:
mdanki library.md anki.apkgConvert a single markdown file and let MDAnki pick the output path (current directory with .apkg extension):
mdanki library.mdConvert files from directory recursively:
mdanki ./documents/library ./documents/anki.apkgUsing all available options:
mdanki library.md anki.apkg --deck Library --template ~/.config/mdanki/templateRun without downloading remote assets (offline-friendly) or adjust the remote fetch timeout:
mdanki library.md --no-remote-media
mdanki library.md --remote-timeout 15000Import just generated .apkg file to Anki ("File" - "Import").
Install as a dependency and use the transformer directly:
pnpm add @shbernal/mdankiimport { convertMarkdownToAnkiDeck } from "@shbernal/mdanki";
const target = await convertMarkdownToAnkiDeck("./notes.md", {
// target: './notes.apkg', // optional; inferred when source is a file
deckName: "My Deck",
allowRemoteMedia: true,
});// ESM only (Node 24+)
import { Transformer, resolveTargetPath } from "@shbernal/mdanki";
const source = "./notes.md";
const target = await resolveTargetPath(source);
const transformer = new Transformer(source, target, {
deckName: "My Deck",
templatePath: undefined, // set this to override the default cards
allowRemoteMedia: true,
remoteFetchTimeoutMs: 15_000,
});
await transformer.transform();source: a file (.mdor.markdown) or a directory to recurse throughtarget: absolute path to the.apkgto write; useresolveTargetPathto pick./<source>.apkgautomatically for single files (directories must supply one)deckName: overrides the top-level#heading and default name from settingstemplatePath: directory containingfront.html,back.html,style.cssif you want a custom templateallowRemoteMedia: fetch and embed remote images/assets found in markdownremoteFetchTimeoutMs: timeout (ms) for remote fetchesnow: epoch milliseconds to build the deck at, defaulting to the current time. Every timestamp in the archive derives from this one reading, so a fixed value makes the output byte-identical across runs
Convert an entire folder of markdown files into one deck:
const transformer = new Transformer("./notes", "/abs/path/to/notes.apkg");
await transformer.transform();Use a custom template:
const transformer = new Transformer("notes.md", "notes.apkg", {
templatePath: "/home/user/.config/mdanki/template",
});
await transformer.transform();Helpers such as resolveTargetPath and configuration utilities are exported from the package root. The CLI remains available via the mdanki binary for global installs.
To override the default card template (defaults live here) use the --template option and point to a directory containing these files (names are fixed):
your-template/
front.html
back.html
style.css
For example:
mdanki library.md anki.apkg --template ~/.config/mdanki/templateThe contents of front.html, back.html, and style.css are used as the question, answer, and CSS respectively. If the directory or any file is missing, MDAnki falls back to the built-in defaults.
MDAnki implements Flashcard Markdown version 1.0, and nothing beyond it. The grammar is not configurable: the card separator, the front/back separator and the tag pattern used to be regexes you could override, which is how a tool ends up with a dialect only it can read. They were removed in 4.0.0.
The sections below cover what you write day to day; the specification is the authority on everything else, including the cases the two disagree about — those are bugs reported against the spec repository.
MDAnki supports .md and .markdown files.
A card is a ## heading and everything below it, up to the next heading of level 1 or 2.
The heading is the front, the body is the back. The markdown below makes two cards.
## What's the Markdown?
Markdown is a lightweight markup language with plain-text-formatting syntax.
Its design allows it to be converted to many output formats,
but the original tool by the same name only supports HTML.
## Who created Markdown?
John Gruber created the Markdown language in 2004 in collaboration with
Aaron Swartz on the syntax.
Nothing else ends a card. A blank line does not, a ### heading does not — it is body
content — and neither does the end of a list.
To put more than the heading on the front, separate the two sides with a line of exactly
***:
## YAGNI
Describe this acronym and why it's so important.
***
"You aren't gonna need it" (YAGNI) is a principle of extreme programming
(XP) that states a programmer should not add functionality until deemed
necessary.
Only that exact spelling separates. ---, ___, * * * and **** are ordinary
thematic breaks and stay in the card, as does a *** inside a fenced code block.
Changed in 4.0.0.
%used to be the separator and is not recognized any more: a%line is body text now. MDAnki warns when it sees one. See the changelog.
When parsing a single markdown file, the deck name comes from the top-level # heading.
Text between that heading and the first card belongs to no card and is not converted.
Tags are bare #tag tokens, written the way Obsidian writes them: letters and digits,
_, -, and / to nest, with at least one non-numeric character. A tag is not
recognized inside a code span or a fenced code block.
A tag counts wherever it appears in the card. Whether it is rendered depends on the line it is on:
- a line that is nothing but tags is metadata — it sets the tags and does not appear on the card
- a tag inside a sentence sets the tag and stays visible, because removing it would turn "The #verbs group of motion" into "The group of motion"
The card below gets three tags: algorithms, OOP and data-structures.
## Binary tree
In computer science, a binary tree is a tree data structure in which each node has at most two children, which are referred to as the left child and the right child.
#algorithms #OOP #data-structures
Tags can also be set for a whole file, in YAML frontmatter, and the two are unioned:
---
tags:
- computer-science
- data-structures
---
The value has to be a YAML sequence. A scalar (tags: a, b) and the singular tag: key
are not read as tags — Obsidian dropped both in 1.9, and reading them here would mean
your vault and your flashcards disagree about the same file. MDAnki says so rather than
ignoring them quietly. Any other frontmatter key is yours, and is ignored without
complaint.
Anki nests tags with :: where the file nests with /; MDAnki translates on export, so
#french/grammar arrives in Anki as french::grammar.
Changed in 4.0.0. The
[#tag]()link form is not recognized any more and renders as visible text in the answer. MDAnki warns when it sees one.
Code blocks can be written with and without specifying a language name:
```java
public static void main(String[] args) {
System.out.println("Hello, World!");
}
```
``` echo "Hello, World!" ```
The last code block will be treated by MDAnki as Bash code. The default language is bash (see src/configs/settings.ts).
Note! Creating a block without language name is not fully supported and should be eliminated in usage. Take a look at this:
echo "Code block with language name"echo "Code block without language name"
MDAnki supports code highlighting for these languages:
actionscript, applescript, aspnet, bash, basic, batch, c, coffeescript, cpp, csharp, d, dart, erlang, fsharp, go, graphql, groovy, handlebars, java, json, latex, less, livescript, lua, makefile, markdown, markup-templating, nginx, objectivec, pascal, perl, php, powershell, python, r, ruby, rust, sass, scheme, smalltalk, smarty, sql, stylus, swift, typescript, vim, yaml.
You can use links to image files inside markdown, MDAnki will parse them and add those images to the import collection. It's allowed to use two styles for writing images:
MDAnki and Anki can support LaTeX. Install LaTeX for your OS and use the [latex] attribute within Markdown files.
[latex]\\[e^x -1 = 3\\][/latex]
Converting a big Markdown file you can get a memory limit error like this:
Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value 16777216...
For overcoming this error, replace sql.js:
cp node_modules/sql.js/js/sql-memory-growth.js node_modules/sql.js/js/sql.jsMore info here.

