Skip to title

How to create Anki plugins

Page created

How to create simple plugins for Anki.

This is based on two very similar plugins I have made for Anki. OxfordDefine and krdictDefine

Motivation and context

Anki is a flashcard memorisation software which lets you create flashcards of whatever content you want. The program schedules your daily flashcards based on how you did the previous review and how long ago that was. A 2015 paper on medical students found 31% of respondents used Anki to practice for a high stakes assessment1.

I’ve been using Anki regularly for the past year in order to learn Korean (한굴어) and increase my English vocabulary.

At the end of most months I would sit down and create a deck for the words I encountered throughout that month.

The basic app is available via the desktop, web, Android and iOS (though the iOS version isn’t free). Plugins however are only available on the desktop version. Since I use the convenient Android app for most of my practice there isn’t much I can do with plugins. The one thing that has been quite nice is what I’ll name Card generators.

Card generators are apps that speed up the card making progress. The first one I found retrieved word definitions and pronunciations from Merriam-Webster. It made card creation so much easier and produced highly consistent cards.

The problem, Merriam-Webster is an American dictionary but I am Australian and take more influence from British English than American. There were also some Australian words that did not show up.

The Oxford dictionary however had Australian specific words like yakka.

I’ve already done this process for two dictionaries, the Oxford and krdict. There are two major steps that need to take place. Gaining access to the API of the dictionary then parsing the dictionary data.

This will be different for different dictionaries/sources of information. If no API is present scraping the data may be your only option. Perhaps something like beautiful soup but more on that in Tips and Tricks.

The Anki add-on documentation is lacking in many areas, really only teaching how to setup a basic template and some basic debugging. Regardless it’s here

Since it’s open source so we could dive in ourselves to see what’s happening. Instead checkout out this by Julien Sobczak with his detailed look into what makes Anki tick. It’s a little outdated now, a lot of the back-end has been rewritten in Rust but the front-end remains written in Python with Qt.

If the data you’re reading in is complicated or has many possible values then decisions need to be made on what to add for each. Since I wanted a simple solution that works for 99% of cases I’m happy with my current approach. Perhaps you’re not in which case a full blown UI for choosing which fields to keep could help.

What to do

The directory structure was based on the add-on documentation

1
2
3
4
5
6
7
8
9
10
OxfordDefineAddon // the actual addon
├── config.json
├── config.md
├── images
│   ├── books16.png
│   └── books.png
├── __init__.py // the file required to run the add-on
├── main.py
├── meta.json
└── oxford.py

Create this folder structure in the place Anki keeps its add-ons. You can find this folder from Anki via Tools > Add-ons > View Files. It should be named addons21/. Since I like to keep all my work under a Projects/ folder I have symbolically linked it from Projects/ to addons21/.

The most important file here is the __init__.py which is used to start the add-on. Without it the add-on will not start.

The whole add-on can be created in __init__.py but for organisational reasons I’ve split it into main.py and oxford.py.

1
2
# __init__.py
from . import main

My main.py file contains contains information regarding the front-end in addition to business logic. Interactions between Anki and the Oxford dictionary API are held in oxford.py.

The first step will be to create a new button in the card editor. This add-on uses legacy hook handling via addHook. More information can be found in the official documentation. You will notice that there is a newer way of registering hooks. The change looks pretty minor and I’d recommend you try out the new way on your own.

Additional buttons added to the editor
Additional buttons added to the editor

In order to add a button you need to create a hook (function) that registers the button in the editor and appends it to the buttons list.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def addMyButton(buttons, editor):
    # register the button in the editor first
    oxfordButton = editor.addButton(icon=os.path.join(os.path.dirname(__file__), "images", "books16.png"),
                                    cmd="OxDict",
                                    func=insertDefinition,
                                    tip=f"Oxford Define Word {PRIMARY_SHORTCUT}",
                                    toggleable=False,
                                    label="",
                                    keys=PRIMARY_SHORTCUT,
                                    disables=False)
    # don't forget to append the button to this list
    buttons.append(oxfordButton)
    # and return the list
    return buttons

addHook("setupEditorButtons", addMyButton)

The insertDefinition is a function which you will need to fill in. For now just stub it out. You should now see a new, useless button appear in your editor.

The parsing logic required for your API will be different to what I’ve done for the Oxford API. You can check the repo for what I’ve done though it’s not a pretty sight. Regardless you’re on your own.

Well, there is one a couple of important, parting messages.

Tips and Tricks

Debugging

When debugging you can see print statements if you run Anki via the terminal. You can also display a message box via showInfo available via

1
from aqt.utils import showInfo

Python packages

If you want to use a Python package to make coding easier you will need to include it whole as part of the Anki add-on if you want your add-on to be portable. Docs

Luckily request is available out of the box which makes HTTP requests a lot easier.

krdict returns XML and I didn’t want to parse XML with ye old inbuilt XML parser so I got myself a module.

xmltodict is a single file python package which was very easy to add. The file can be placed anywhere but I decided to move it to its own folder lib/ (don’t forget the __init__.py and imported it via

1
from .libs import xmltodict

Configuration for end users

The config.json, config.md and meta.json allow you to add options to your app. config.json specifies what options are available for the user to provide. In my case an app-key is required to access dictionary APIs.

1
2
3
4
5
6
{
    "APP_ID": "YOUR_ID_HERE",
    "APP_KEY": "YOUR_KEY_HERE",
    "PRIMARY_SHORTCUT": "ctrl+alt+d",
    "WHAT_TO_INSERT": "all"
}

If this information is not clear enough for users you can specify a config.md for an arbitrary description. Here I’ve labeled what each variable is for.

1
2
3
4
* `APP_ID`: Get your unique ID from https://developer.oxforddictionaries.com/
* `APP_KEY`: Get your unique KEY from above
* `PRIMARY_SHORTCUT`: The shortcut used instead of pushing the button. Restart to change.
* `WHAT_TO_INSERT`: Choices are `all`, `pronunciation` and `text`

The meta.json contains the values the user has set. If you have a public repo make sure you don’t add your meta.json into it. More here

  1. https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4673073/