zunaid-farouque53 downloadsSync flashcards directly to Anki using an AST markdown engine via AnkiConnect.
A deterministic, AST-powered synchronization pipeline and Obsidian plugin bridging Obsidian and Anki.
Traditional flashcard sync tools rely on fragile Regular Expressions (Regex) that break when confronted with modern Markdown complexities like nested code blocks, escaped characters, LaTeX math formulas, Obsidian callouts, HTML tables, or block transclusions (![[SourceNote#^block-id]]). Worse, many tools re-serialize your entire Markdown file through formatters, destroying your custom formatting, spacing, and personal styling.
Anki AST Sync solves this by parsing your Obsidian notes into an Abstract Syntax Tree (AST) via the unified / remark engine. It understands the true syntactic structure of your notes, ensuring safe, non-destructive synchronization with Anki while keeping your Markdown files pristine.
$...$, $$...$$) are safely ignored.<!--anki-id: uuid-->) by calculating exact byte offsets on raw file buffers. It never round-trips Markdown through a serializer, preserving 100% of your vault's original indentation, line endings, and custom syntax.SYNC, WARN, SKIP, ERROR). The preview parser and the sync engine share the exact same underlying logic.error and skip) hard-block writes to Anki so malformed cards will never corrupt your Anki collection.![[SourceNote#^block-id]]). The engine recursively fetches, parses, and grafts transcluded content directly into your flashcards prior to sync.#anki/noteType/YourModel).flowchart TD
subgraph Obsidian ["Obsidian Vault"]
Note["Markdown Note (.md)"]
FrontFilter{"AnkiSync: on?"}
SourceAST["Source AST (remark/unified)"]
SourceOffsets["Extract Card Bounds & Injection Offsets"]
end
subgraph Engine ["AST Sync Engine"]
TransGraft["Graft Transclusions (![[note#^block]])"]
MediaRes["Resolve Media & Queue Uploads"]
GraftedAST["Grafted AST & HTML Compiler"]
DocResolve["parseCardDocument (Resolve Card Types)"]
Gate{"Sync Eligibility Gate"}
end
subgraph Anki ["Anki Desktop"]
DupCheck["Vault Duplicate Preflight"]
AnkiConnect["AnkiConnect HTTP Bridge (Port 8765)"]
Collection[("Anki Collection")]
end
subgraph WriteBack ["Vault Safe Write-Back"]
SurgicalInject["Surgical Buffer Splice [anki-id: uuid comment]"]
end
Note --> FrontFilter
FrontFilter -->|Yes| SourceAST
FrontFilter -->|No| Ignored["Skipped"]
SourceAST --> SourceOffsets
SourceAST --> TransGraft
TransGraft --> MediaRes
MediaRes --> GraftedAST
GraftedAST --> DocResolve
DocResolve --> Gate
Gate -->|Error / Skip| Blocked["Hard Blocked (No Anki Write)"]
Gate -->|Sync / Warn| DupCheck
DupCheck --> AnkiConnect
AnkiConnect --> Collection
Collection -->|Return Note IDs| SurgicalInject
SourceOffsets -.->|Byte Offsets| SurgicalInject
SurgicalInject --> Note
<!--anki-id: uuid-->) right at the calculated offset at the end of the card.::: or --- inside a Python code block or a LaTeX matrix, regex-based tools break. An AST parser knows that node belongs to code or math and ignores it completely.2055492159).In Anki Desktop, open Tools → Add-ons → AnkiConnect → Config and ensure your webCorsOriginList includes Obsidian:
{
"apiKey": null,
"apiPort": 8765,
"webCorsOriginList": [
"http://localhost",
"app://obsidian.md"
]
}
[!IMPORTANT] Restart Anki Desktop after editing this configuration to apply the new settings. You can verify AnkiConnect is running by visiting
http://127.0.0.1:8765in any browser (it should display"AnkiConnect").
zunaidFarouque/Obsidian-Anki-AST-Engine.Add AnkiSync: on to your note frontmatter. You can optionally set a target deck and tags:
---
AnkiSync: on
target_anki_deck: General Knowledge
file_anki_tags: geography, capitals
---
#### What is the capital of Australia?
:::
Canberra
You have several convenient ways to sync:
Ctrl/Cmd + P):#### level 4) and continues until the next heading of equal or shallower depth.:::, :::r, :::t, ::: FieldName) must be placed on their own line (at line-start). Delimiters cannot be placed inline in the middle of a line or inside headings.$...$, $$...$$) are ignored by the AST parser.basic)Maps to Anki's standard Basic model (Fields: Front, Back). Requires a line-start ::: delimiter.
Write your question in the card body before the delimiter, and the answer after it:
#### Binary Search
What is the time complexity of binary search in the worst case?
:::
O(log n) because the search space is halved in each step.
When the ::: delimiter immediately follows the heading (empty front body), the heading text itself is used as the Front:
#### What is the speed of light in vacuum?
:::
Approximately 3 × 10⁸ m/s.
{{word}}: On a basic card, bare {{word}} emits a warning and stays literal text.{{c1::...}} on Basic: By default, stays literal with a warning, unless the setting inferClozeFromManualSyntaxOnBasic is enabled.reversible)Generates two cards in Anki (Card 1: Front → Back, Card 2: Back → Front) using the Basic (and reversed card) model.
:::r Delimiter (No Tag Needed)#### French Vocabulary
Bonjour
:::r
Hello
(Heading-as-front is also supported when :::r immediately follows the heading).
#anki/cardType/reversible Tag#### Chemical Elements #anki/cardType/reversible
Gold
:::
Au
typed)Prompts you to type the answer in Anki's review screen using the Basic (type in the answer) model.
:::t Delimiter#### Linux Commands
What command prints the current working directory in Linux?
:::t
pwd
Separate alternative acceptable answers on the answer line with pipes (|):
#### Capital of France
Name a major city in France:
:::t
Paris | Lyon | Marseille
cloze)Maps to Anki's Cloze model (Fields: Text, Back Extra).
[!IMPORTANT] Cloze deletions MUST be in the Text region (before
:::, or the whole card body if no:::is present). Never put:::before the cloze text — that places the deletions in the Back Extra field and triggers a fatal error!
#### The Krebs Cycle #anki/cardType/cloze
The citric acid cycle takes place in the {{c1::mitochondrial matrix}} and generates {{c2::NADH}} and {{c3::FADH2}}.
Use ::: after the cloze text to provide additional context or reference material:
#### The Krebs Cycle #anki/cardType/cloze
The citric acid cycle takes place in the {{c1::mitochondrial matrix}} and generates {{c2::NADH}} and {{c3::FADH2}}.
:::
Extra reference: Discovered by Hans Krebs in 1937. It consists of eight enzymatic reactions.
Add hints using :: inside the deletion:
#### Organelles #anki/cardType/cloze
The {{c1::mitochondria::powerhouse organelle}} produces ATP.
Under a #anki/cardType/cloze heading or when anki_cardDefault: cloze is set, you can write shorthand {{term}} or {{term::hint}} without typing c1:: or c2::. The engine automatically groups identical terms and numbers them in sequence:
### Biochemistry #anki/cardType/cloze
#### Cellular Respiration
{{Glucose}} and {{oxygen}} produce {{carbon dioxide}} and water. Breakdown of {{glucose}} begins with glycolysis.
Result: Both {{glucose}} occurrences become c1, {{oxygen}} becomes c2, and {{carbon dioxide}} becomes c3.
custom)Synchronize cards directly to any custom Anki note model and field layout.
#### Ephemeral #anki/noteType/Vocab
::: Word
ephemeral
::: Definition
Lasting for a very short time; transitory.
::: Example
Fashions are ephemeral, but style endures.
::: FieldName at line-start, followed by exactly one space, then the field name matching your Anki model (case-insensitive).anki_customCardDefault: Vocab in frontmatter so any card with ::: FieldName blocks resolves to Vocab without needing a heading tag.Headings shallower than the card declaration level (#, ##, ### when cards are ####) can declare card or note types for an entire section:
### Medical Vocabulary #anki/noteType/Vocab
#### Card 1
::: Word
prognosis
::: Definition
The likely course of a medical condition.
#### Card 2
::: Word
etiology
::: Definition
The cause or set of causes of a disease.
#anki/ (e.g. #anki/cardType/cloze, #anki/noteType/Vocab) or #anki_card_* are engine directives. They are used for type resolution and are never synced to Anki as tags.#biology, #exam-2026) ARE synced to Anki as tags.includeParentHeadersAsTags is enabled, heading titles in the ancestor chain are concatenated into hierarchical tags (e.g. Biology::Genetics).Embed media using standard Markdown or Obsidian wikilinks:
#### Anatomy of the Heart
:::
![[heart-diagram.png]]
The left ventricle pumps oxygenated blood through the aortic valve.
The engine resolves the file from your vault or attachment folder, generates a Base64 payload, and uploads it safely to Anki's media storage via AnkiConnect.
Seamlessly reuse content from other vault notes without duplication:
#### Proof of the Pythagorean Theorem
:::
![[Math Theorems#^pythagoras-proof]]
During synchronization, the engine dereferences the block embed, pulls the transcluded AST subtree into the card, and uploads the expanded HTML to Anki.
Configure synchronization behavior per file using YAML frontmatter properties:
| Frontmatter Key | Type | Default | Description |
|---|---|---|---|
AnkiSync |
boolean / string |
off |
Set to on or true to enable sync for the note. |
target_anki_deck |
string |
Settings default | Overrides the target Anki deck for all cards in this note. |
file_anki_tags |
string |
"" |
Comma-separated extra tags applied to all cards in this note. |
cardDeclarationHeadingLevel |
number (1–6) |
4 |
Heading level defining card envelopes for this note (default: 4 for ####). |
delimiter |
string |
::: |
Overrides front/back separator for this note. |
includeParentHeadersAsTags |
boolean |
true |
Toggles hierarchical tags from ancestor headings. |
anki_cardDefault |
string |
basic |
Default built-in card type (basic, cloze, reversible, typed). |
anki_customCardDefault |
string |
— | Default custom note type (e.g. Vocab) when using ::: FieldName blocks. |
anki_customLayoutMap |
object / string |
— | Custom field mapping for remapping fields. |
When Live card preview is enabled in Settings, the CodeMirror 6 editor renders interactive visual indicators directly beside card headings:
:::, cloze card with no deletions in Text, duplicate front in vault).:::r, cloze deletions only in Back Extra, conflicting tags). Writes to Anki are hard-blocked to prevent corrupting your collection.Click any badge in the editor to open the Card Preview Inspector Modal:
Basic, Cloze, Custom).Access these anytime via the Command Palette (Ctrl/Cmd + P):
| Command | Action |
|---|---|
| Sync current note to Anki | Synchronizes flashcards in the active note to Anki. |
| Sync vault to Anki | Scans all configured vault folders and syncs all eligible notes. |
| Dry-run sync current note to Anki | Simulates sync for the active note without modifying files or Anki. |
| Dry-run sync vault to Anki | Simulates full vault sync and displays intended actions in a results dialog. |
| Check AnkiConnect connection | Pings AnkiConnect and reports the API version or connection error. |
| Create new Anki note | Creates a new note pre-configured with AnkiSync: on and a starter card. |
| Toggle Anki sync for current note | Toggles AnkiSync: on / off in the active note's frontmatter. |
| Set target Anki deck for current note | Fuzzy-search your Anki decks to set target_anki_deck. |
| Insert card template... | Modal picker to insert Basic, Reversible, Typed, or Cloze card templates. |
| Wrap selection as cloze deletion | Wraps selected text in {{c1::...}} with intelligent auto-incrementing. |
| Jump to next / previous card in note | Fast navigation between card headings in long notes. |
| Jump to next card with sync issue | Jumps directly to the next card with a warning or error badge. |
| Open current card in Anki desktop | Locates and displays the active card inside Anki's Card Browser. |
Before writing cards to Anki, the engine performs a preflight duplicate check across your vault. If two cards have identical deck targets and front content, a collision warning is surfaced, preventing accidental duplicate creation.
When notes or cards are deleted from Obsidian, their corresponding Anki notes become "orphans". During full-vault sync, Anki AST Sync detects orphaned cards and prompts you with safe resolution choices:
obsidian-sync-ignore so you are never prompted again.Open Settings → Anki AST Sync in Obsidian to customize:
http://127.0.0.1:8765).target_anki_deck is not specified.Obsidian-Anki-AST).4 for ####).:::).Shortest, Relative, Absolute).Off, Shaded, Shaded with divider).c1, c2, c3) automatically.{{...}} shorthand by default.Ask each sync or Off).For headless terminal sync, CI/CD pipelines, or automated scripts, the underlying engine can run directly via Bun:
git clone https://github.com/zunaidFarouque/Obsidian-Anki-AST-Engine.git
cd Obsidian-Anki-AST-Engine
bun install
bun run build
Copy config.json.example to config.json:
{
"vaultPath": "/path/to/your/obsidian/vault",
"scanFolders": ["Notes", "Flashcards"],
"defaultAnkiDeck": "Synced from Obsidian",
"defaultEngineTag": "Obsidian-Anki-AST",
"ankiConnectUrl": "http://127.0.0.1:8765",
"delimiter": ":::",
"defaultCardDeclarationHeadingLevel": 4,
"autoCreateDecks": true,
"autoCreateStockNoteModels": true,
"inferClozeFromManualSyntaxOnBasic": true
}
Run CLI commands:
# Verify AnkiConnect connectivity
bun run sync -- --check
# Dry-run sync (safe simulation)
bun run sync -- --dry-run
# Live synchronization
bun run sync
2055492159).app://obsidian.md and http://localhost are in webCorsOriginList under Tools → Add-ons → AnkiConnect → Config, then restart Anki.AnkiSync: on.#### level 4).Only in one minimal, non-destructive way: when a card is first synchronized to Anki, the plugin splices a small tracking comment (<!--anki-id: uuid-->) directly at the end of the card content. It never touches your headings, and never reformats, re-indents, or rewrites your Markdown.
We maintain a strict Test-Driven Development (TDD) culture with 800+ test cases covering AST parsing, cloze extraction, transclusions, media resolution, duplicate detection, and editor decorations.
# Run the complete test suite
bun test
# Run tests in watch mode
bun test --watch
# Build the Obsidian plugin and dist bundle
bun run build
# Run linter
bun run lint
Distributed under the MIT License. See LICENSE for more information.