Anki ↔ Markdown, with bidirectional sync, custom note types, and LLM integration
Project description
AnkiOps
AnkiOps is a bidirectional bridge between Anki and your filesystem. Each deck becomes a Markdown file so you can manage your collection from your favorite text editor. Edit in plain text, version with Git, enhance with LLMs, and sync changes both ways.
Advantages
- ✏️ Edit Anki decks as highly readable Markdown files
- 🔄 Two-way sync of notes, note types, decks and media files
- ⚡ Sync thousands of notes in under a second
- ⚙️ Bring your own note types
- ✨ Improve content with programmable LLM tasks
- 👥 Share decks on GitHub and collaborate with others
How It Works
Markdown Files
One Markdown file is one deck. Each note is separated by a blank line, three dashes, and another blank line:
Q: Question text here
A: Answer text here:
Multiple lines supported
E: Extra information (optional)
M: Content behind a "more" button (optional)
---
T: Text with
- {{c1::multiple}}
- {{c2::cloze deletions}}.
E: Some *extra* info:
{width=700}
---
<!-- tags: exam -->
Q: What is this?
C1: A multiple choice note
C2: with
C3: automatically randomized answers
A: 1, 3
You can use any Markdown syntax (except the horizontal rule) in the note content, including italics, bold text, lists, tables, images, math equations, syntax-highlighted code blocks, and more. AnkiOps automatically converts Markdown to HTML for Anki and back again.
After the first sync with Anki, AnkiOps adds metadata comments for each note:
<!-- note_key: 2fd62bcaa861 -->
<!-- note_type: AnkiOpsQA -->
Q: Question text here
A: Answer text here:
Multiple lines supported
E: Extra information (optional)
M: Content behind a "more" button (optional)
---
<!-- note_key: ef0108255d7d -->
<!-- note_type: AnkiOpsCloze -->
T: Text with
- {{c1::multiple}}
- {{c2::cloze deletions}}.
E: Some *extra* info:
{width=700}
---
<!-- note_key: 332e64bba6fe -->
<!-- note_type: AnkiOpsChoice -->
<!-- tags: exam -->
Q: What is this?
C1: A multiple choice note
C2: with
C3: automatically randomized answers
A: 1, 3
The note_key is a stable identifier independent of Anki's note IDs and it is used to track notes across syncs. The note_type comment is added just for the user's reference. Neither comment should be edited by hand. The tags comment is user-editable and synced with Anki's tags.
Collection Structure
This is the basic structure of an AnkiOps collection:
├── note_types/
│ ├── AnkiOpsQA/
│ │ ├── Front.template.anki
│ │ ├── Back.template.anki
│ │ └── note_type.yaml
│ ├── AnkiOpsCloze/
│ ├── AnkiOpsStyling.css
│ └── SyntaxHighlighting.css
├── media/
│ └── image1_hash.png
├── llm/
├── .ankiops.db
├── Deck1.md
└── Deck1__Subdeck1.md
The .ankiops.db file is the heart of AnkiOps. It connects the note_key values in the Markdown files to Anki's internal note IDs.
Note Types
[!NOTE] AnkiOps only acts on note types defined within the
note_types/folder.
AnkiOps automatically infers the note type for each note by a unique set of identifying field labels (e.g. Q: for Question and A: for Answer, which by default is AnkiOpsQA). Labels are defined in the note_type.yaml file within each note type folder. They define field names, field labels (identifying and non-identifying), card templates, and the styling. Note types are fully customizable.
| Default note type | Identifying labels |
|---|---|
AnkiOpsQA |
Q:, A: |
AnkiOpsCloze |
T: |
AnkiOpsClozeHideAll |
THA: |
AnkiOpsReversed |
F:, B: |
AnkiOpsInput |
Q:, I: |
AnkiOpsChoice |
Q:, choice labels such as C1:, plus A: |
AnkiOpsImageOcclusion |
IO_*: labels for image occlusion fields |
Generic, non-identifying labels such as E: for Extra can be added to any note type. To see the assigned labels in your collection, run ankiops note-types, or look up the note type definitions in note_types/ manually. Note type inference depends on unique sets of identifying labels. All notes managed by AnkiOps have an additional field called AnkiOps Key that stores the note_key in Anki.
Synchronization
AnkiOps has two sync commands, which make one side match the other:
ankiops af(anki to files): After editing notes, tags, or decks in Anki, andankiops fa(files to anki): After editing Markdown decks, media, or note types.
Both sync operations create, update, move, and delete managed notes, and handle media files and note types. Before syncing, an automatic Git snapshot is created.
Add-On
For basic usage, you can use AnkiOps without the add-on. The add-on enables AnkiOpsConnect, which AnkiOps needs for operations related to sharing. If you do not want to install the add-on, you can use AnkiOps with AnkiConnect (AnkiOpsConnect is twice as fast though).
Another feature of the add-on are the toolbar buttons for af and fa:
To install the add-on, download the folder and put it in your Anki add-ons directory.
How To Get Started
- Install AnkiOps via uv from PyPI. This will make the
ankiopscommand available in your shell in an isolated virtual environment. No need to get Python separately.
uv tool install ankiops
- Initialize AnkiOps: Make sure that Anki is running, with Anki(Ops)Connect enabled. Run
ankiops initin an empty directory of your choice. AnkiOps creates a Git repository,.ankiops.db, thenote_types/folder, and recommended configurations for VS Code. The tutorial flag further creates a sample Markdown deck.
cd my-collection
ankiops init --tutorial
- Sync files to Anki: Apply the current files, including Markdown decks, media, and note types, to Anki.
ankiops fa
- Sync Anki back to files: After you review or edit cards in Anki, apply those changes to your local files. Each sync makes one side match the other. Inspect the Git diff to easily track all changes.
ankiops af
FAQ
Why should I use AnkiOps?
There is a joke among software engineers that eventually every filesystem grows into a database, and every database grows into a filesystem. Both approaches have their merits. AnkiOps enables the filesystem solution for the Anki database. It mirrors a full collection in a folder of your choice, where each deck becomes a Markdown file, and media and note-type definitions are stored along with it. Having your collection represented in the filesystem enables straightforward version control, automation, and collaboration.
How is this different from other Markdown tools?
Most Markdown-to-Anki tools import one way: you write Markdown and push it to Anki. AnkiOps lets you edit in either place and sync back. It stores each deck as one Markdown file, so you browse decks instead of hundreds of per-card files. It also keeps custom note type definitions beside your decks, which lets you edit both card content and card structure from your editor.
Is it safe to use?
Yes, AnkiOps will never modify notes that are not defined within the note_types/ folder. Your existing collection won't be affected, and you can safely mix managed and unmanaged notes within one deck. Additionally, AnkiOps only syncs if the activated profile matches the one it was initialized with. For Markdown files, AnkiOps automatically creates a Git commit of your collection folder before every sync, so you can always revert changes if needed.
What is the recommended workflow?
We emphatically recommend VS Code because it has a stable Markdown previewer and handles images with the dedicated media/ folder well. AnkiOps ejects a configuration for VS Code that works out of the box with all features provided by AnkiOps.
How can I migrate my existing notes into AnkiOps?
You can migrate existing notes in three ways: write matching note type files in note_types/, copy note types from Anki with ankiops note-types --add <name>, or convert notes to the default AnkiOps note types with Change Note Type… in the Anki browser.
For Anki browser conversion, use this workflow:
- Convert your existing notes to the matching AnkiOps note types via
Change Note Type…in the Anki browser. - Export your notes from Anki to Markdown using
ankiops am. - Review the Git diff after the first re-import. Original Anki HTML may not match CommonMark, so the first Markdown-to-Anki sync can change formatting slightly. Formatting issues can be fixed by hand or via the LLM task fix-html.
How can I make use of LLMs with AnkiOps?
If you want it simple, just prompt an LLM (Codex, Claude Code, etc.) to edit your Markdown files. If you want it integrated, AnkiOps has a dedicated LLM module that runs through your serialized (JSON) collection and applies edits to the Markdown files. Only works with OpenAI's response API.
How do I upgrade AnkiOps to the latest version?
AnkiOps is in early development, so breaking changes are expected. Use uv upgrade ankiops to upgrade AnkiOps. Delete local AnkiOps support files except your Markdown decks, re-initialize the same folder with ankiops init, and export from Anki with ankiops am. Since all files are git-tracked, you can easily spot any changes and roll back if needed.
What other features are there?
Image Width Normalization
Handling multiple images in a single Anki note often leads to inconsistent widths. AnkiOps can normalize similar widths or force a width across a deck:
ankiops fix-image-widths # normalize widths within 10px tolerance
ankiops fix-image-widths --deck "Deck1" --width 500 # fix width
JSON serialization
Serialize a collection (or one deck tree) to portable JSON and deserialize it back using the ankiops serialize and ankiops deserialize commands.
How does collaboration work? (experimental)
Collab decks are ordinary GitHub repositories cloned inside the collection. The collection root remains one VS Code workspace, while VS Code shows each collab source as its own repository.
Publish a local deck tree:
ankiops collab publish "Psychology" owner/psychology-deck
collab publish requires stable note_key metadata and authenticated GitHub CLI.
It moves the selected deck tree into collab/<owner>/<repo>/, copies referenced
media and note types, creates an independent repository, and pushes it to GitHub.
Published repositories are always public.
Subscribe to and update a collab deck:
ankiops collab subscribe owner/psychology-deck
ankiops collab update owner/psychology-deck
ankiops fa
Submit local collab edits:
ankiops collab status owner/psychology-deck
ankiops collab submit owner/psychology-deck \
--message "Clarify attention terminology"
collab update changes files only; run ankiops fa after reviewing them.
collab submit commits changes only inside the selected source and opens a pull
request. Contributors without write permission use an authenticated fork
automatically. If local and upstream edits overlap, the subscribed deck remains
unchanged. AnkiOps preserves editable base, local, and upstream copies; edit the
marked Markdown it reports and rerun collab update.
If GitHub authentication, upload, or pull-request creation fails, AnkiOps keeps
the commit on the recovery branch shown in the output. Rerun the reported
collab submit command to reuse it. Your local Git configuration supplies the
commit author; the authenticated GitHub account uploads it and opens the pull
request. Submit output shows both identities.
Command Reference
Global options:
--debug: Enable debug logging.--version: Show the installed version.--help: Show help.
Core commands:
ankiops init [--tutorial]: Initialize the current directory as an AnkiOps collection.ankiops anki-to-filesorankiops af: Sync Anki changes into files.ankiops files-to-ankiorankiops fa: Sync file changes into Anki.ankiops note-types: Show note types and the label registry.ankiops note-types --add <name>: Copy a note type from Anki intonote_types/.
Sync flags:
--no-auto-commit,-n: Skip the pre-sync Git snapshot foraf,fa,deserialize,fix-image-widths, andllm <task> --run.
Serialization:
ankiops serialize [-o path] [--deck name] [--no-subdecks]ankiops deserialize [-i path] [--overwrite]
Image tools:
ankiops fix-image-widths [--deck name] [--no-subdecks] [--tolerance px] [--width px]
LLM tools:
ankiops llm: Show configured tasks and recent jobs.ankiops llm <task> [--model model] [--deck deck]: Show a dry-run plan.ankiops llm <task> --run [--model model] [--deck deck]: Run a task.ankiops llm --job <id|latest>: Inspect one job.
Collab deck tools:
ankiops collab publish <deck> <owner>/<repo>ankiops collab subscribe <owner>/<repo>ankiops collab status [owner/repo]ankiops collab update [owner/repo]ankiops collab submit <owner>/<repo> [-m|--message text]
Contributing
Bug fixes, documentation improvements, tests, and PRs are welcome!
Inspired by
- hashcards: Markdown-first flashcards
- AnkiCollab: Collaborative Anki decks
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ankiops-0.6.5.tar.gz.
File metadata
- Download URL: ankiops-0.6.5.tar.gz
- Upload date:
- Size: 138.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57325542bd3e49fd7530f5f0575b8320c7e907f174db3566a546734f84bcac70
|
|
| MD5 |
945a185be36ac13a91e9a13b7fe84527
|
|
| BLAKE2b-256 |
1970adfb4d53f39324facb154d9eb720c62190eff8f2939e12a846b93d3284ca
|
Provenance
The following attestation bundles were made for ankiops-0.6.5.tar.gz:
Publisher:
publish.yml on visserle/AnkiOps
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ankiops-0.6.5.tar.gz -
Subject digest:
57325542bd3e49fd7530f5f0575b8320c7e907f174db3566a546734f84bcac70 - Sigstore transparency entry: 2069892188
- Sigstore integration time:
-
Permalink:
visserle/AnkiOps@8d627e32f3a511e98c9aadc87fe98e2045a960f6 -
Branch / Tag:
refs/tags/v0.6.5 - Owner: https://github.com/visserle
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8d627e32f3a511e98c9aadc87fe98e2045a960f6 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ankiops-0.6.5-py3-none-any.whl.
File metadata
- Download URL: ankiops-0.6.5-py3-none-any.whl
- Upload date:
- Size: 163.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6a8b729636a9f58cef5982f9f80e6fc9d83f50f96fc80c4097fbcec755e57a6
|
|
| MD5 |
d61143dc60b9c6d266a41b43877e490b
|
|
| BLAKE2b-256 |
b7db7c6265ce4f7994f8a0ed977440e8dc01da6c34203324b7c7b76eea39c20d
|
Provenance
The following attestation bundles were made for ankiops-0.6.5-py3-none-any.whl:
Publisher:
publish.yml on visserle/AnkiOps
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ankiops-0.6.5-py3-none-any.whl -
Subject digest:
e6a8b729636a9f58cef5982f9f80e6fc9d83f50f96fc80c4097fbcec755e57a6 - Sigstore transparency entry: 2069892262
- Sigstore integration time:
-
Permalink:
visserle/AnkiOps@8d627e32f3a511e98c9aadc87fe98e2045a960f6 -
Branch / Tag:
refs/tags/v0.6.5 - Owner: https://github.com/visserle
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8d627e32f3a511e98c9aadc87fe98e2045a960f6 -
Trigger Event:
release
-
Statement type: