Skip to main content

Anki ↔ Markdown, with bidirectional sync, custom note types, and LLM integration

Project description

AnkiOps

Tests License: MIT PyPI version

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:

![image with set width](media/im.png){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:

![image with set width](media/im.png){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, and
  • ankiops 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:

alt text

To install the add-on, download the folder and put it in your Anki add-ons directory.

How To Get Started

  1. Install AnkiOps via uv from PyPI. This will make the ankiops command available in your shell in an isolated virtual environment. No need to get Python separately.
uv tool install ankiops
  1. Initialize AnkiOps: Make sure that Anki is running, with Anki(Ops)Connect enabled. Run ankiops init in an empty directory of your choice. AnkiOps creates a Git repository, .ankiops.db, the note_types/ folder, and recommended configurations for VS Code. The tutorial flag further creates a sample Markdown deck.
cd my-collection
ankiops init --tutorial
  1. Sync files to Anki: Apply the current files, including Markdown decks, media, and note types, to Anki.
ankiops fa
  1. 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:

  1. Convert your existing notes to the matching AnkiOps note types via Change Note Type… in the Anki browser.
  2. Export your notes from Anki to Markdown using ankiops am.
  3. 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-files or ankiops af: Sync Anki changes into files.
  • ankiops files-to-anki or ankiops 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 into note_types/.

Sync flags:

  • --no-auto-commit, -n: Skip the pre-sync Git snapshot for af, fa, deserialize, fix-image-widths, and llm <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

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ankiops-0.6.5.tar.gz (138.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ankiops-0.6.5-py3-none-any.whl (163.2 kB view details)

Uploaded Python 3

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

Hashes for ankiops-0.6.5.tar.gz
Algorithm Hash digest
SHA256 57325542bd3e49fd7530f5f0575b8320c7e907f174db3566a546734f84bcac70
MD5 945a185be36ac13a91e9a13b7fe84527
BLAKE2b-256 1970adfb4d53f39324facb154d9eb720c62190eff8f2939e12a846b93d3284ca

See more details on using hashes here.

Provenance

The following attestation bundles were made for ankiops-0.6.5.tar.gz:

Publisher: publish.yml on visserle/AnkiOps

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for ankiops-0.6.5-py3-none-any.whl
Algorithm Hash digest
SHA256 e6a8b729636a9f58cef5982f9f80e6fc9d83f50f96fc80c4097fbcec755e57a6
MD5 d61143dc60b9c6d266a41b43877e490b
BLAKE2b-256 b7db7c6265ce4f7994f8a0ed977440e8dc01da6c34203324b7c7b76eea39c20d

See more details on using hashes here.

Provenance

The following attestation bundles were made for ankiops-0.6.5-py3-none-any.whl:

Publisher: publish.yml on visserle/AnkiOps

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page