> ## Documentation Index
> Fetch the complete documentation index at: https://anki.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks & Filters

Hooks are the way you should connect your add-on code to Anki. If the
function you want to alter doesn’t already have a hook, please see the
section below about adding new hooks.

There are two different kinds of "hooks":

* Regular hooks are functions that don’t return anything. They are run
  for their side effects, and may sometimes alter the objects they
  have been passed, such as inserting an extra item in a list.

* "Filters" are functions that return their first argument, after
  maybe changing it. An example filter is one that takes the text of a
  field during card display, and returns an altered version.

The distinction is necessary because some data types in Python can be
modified directly, and others can only be modified by creating a changed
copy (such as strings).

## New Style Hooks

A new style of hook was added in Anki 2.1.20.

Imagine you wish to show a message each time the front side of a card is
shown in the review screen. You’ve looked at the source code in
reviewer.py, and seen the following line in the showQuestion() function:

```python theme={null}
gui_hooks.reviewer_did_show_question(card)
```

To register a function to be called when this hook is run, you can do
the following in your add-on:

```python theme={null}
from aqt import gui_hooks

def myfunc(card):
  print("question shown, card question is:", card.q())

gui_hooks.reviewer_did_show_question.append(myfunc)
```

Multiple add-ons can register for the same hook or filter - they will
all be called in turn.

To remove a hook, use code like:

```
gui_hooks.reviewer_did_show_question.remove(myfunc)
```

:warning: Functions you attach to a hook should not modify the hook while they are executing, as it will break things:

```
def myfunc(card):
  # DON'T DO THIS!
  gui_hooks.reviewer_did_show_question.remove(myfunc)

gui_hooks.reviewer_did_show_question.append(myfunc)
```

An easy way to see all hooks at a glance is to look at the
[Hooks Reference](/addons/hooks-reference) page.

If you have set up type completion as described in an earlier section,
you can also see the hooks in your IDE:

<video controls autoplay loop muted alt="Autocomplete in PyCharm">
  <source src="https://mintcdn.com/anki/4yLyWj6_UVW-sSDI/media/autocomplete.mp4?fit=max&auto=format&n=4yLyWj6_UVW-sSDI&q=85&s=baa60ba7bfc494b00c1e70a6f1463de7" type="video/mp4" data-path="media/autocomplete.mp4" />
</video>

In the above video, holding the command/ctrl key down while hovering
will show a tooltip, including arguments and documentation if it exists.
The argument names and types for the callback can be seen on the bottom
line.

For some examples of how the new hooks are used, please see
[https://github.com/ankitects/anki-addons/blob/master/demos/](https://github.com/ankitects/anki-addons/blob/master/demos/).

Most of the new style hooks will also call the legacy hooks (described
further below), so old add-ons will continue to work for now, but add-on authors
are encouraged to update to the new style as it allows for code
completion, and better error checking.

## Notable Hooks

For a full list of hooks and their documentation, see the
[Hooks Reference](/addons/hooks-reference) page, or browse the source directly:

* [The GUI hooks](https://github.com/ankitects/anki/blob/master/qt/tools/genhooks_gui.py)
* [The pylib hooks](https://github.com/ankitects/anki/blob/master/pylib/tools/genhooks.py)

### Webview

Many of Anki's screens are built with one or more webviews, and there are
some hooks you can use to intercept their use.

From Anki 2.1.22:

* `gui_hooks.webview_will_set_content()` allows you to modify the HTML that
  various screens send to the webview. You can use this for adding your own
  HTML/CSS/Javascript to particular screens. This will not work for external
  pages - see the Anki 2.1.36 section below.
* `gui_hooks.webview_did_receive_js_message()` allows you to intercept
  messages sent from Javascript. Anki provides a `pycmd(string)` function in
  Javascript which sends a message back to Python, and various screens such as
  reviewer.py respond to the messages. By using this hook, you can respond
  to your own messages as well.

From Anki 2.1.36:

* `webview_did_inject_style_into_page()` gives you an opportunity to inject
  styling or content into external pages like the graphs screen and congratulations
  page that are loaded with load\_ts\_page().

#### Managing External Resources in Webviews

Add-ons may expose their own web assets by utilizing `aqt.addons.AddonManager.setWebExports()`. Web exports registered in this manner may then be accessed under the `/_addons` subpath.

For example, to allow access to a `my-addon.js` and `my-addon.css` residing
in a "web" subfolder in your add-on package, first register the corresponding web export:

```python theme={null}
from aqt import mw
mw.addonManager.setWebExports(__name__, r"web/.*(css|js)")
```

Then, append the subpaths to the corresponding web\_content fields within a function subscribing to `gui_hooks.webview_will_set_content`:

```python theme={null}
def on_webview_will_set_content(web_content: WebContent, context) -> None:
    addon_package = mw.addonManager.addonFromModule(__name__)
    web_content.css.append(f"/_addons/{addon_package}/web/my-addon.css")
    web_content.js.append(f"/_addons/{addon_package}/web/my-addon.js")
```

Note that '/' will also match the os specific path separator.

## Legacy Hook Handling

Older versions of Anki used a different hook system, using the functions
runHook(), addHook() and runFilter().

For example, when the scheduler (anki/sched.py) discovers a leech, it
calls:

```python theme={null}
runHook("leech", card)
```

If you wished to perform a special operation when a leech was
discovered, such as moving the card to a "Difficult" deck, you could do
it with the following code:

```python theme={null}
from anki.hooks import addHook
from aqt import mw

def onLeech(card):
    # can modify without .flush(), as scheduler will do it for us
    card.did = mw.col.decks.id("Difficult")
    # if the card was in a cram deck, we have to put back the original due
    # time and original deck
    card.odid = 0
    if card.odue:
        card.due = card.odue
        card.odue = 0

addHook("leech", onLeech)
```

An example of a filter is in [aqt/editor.py](https://github.com/ankitects/anki/blob/main/qt/aqt/editor.py). The editor calls the
"editFocusLost" filter each time a field loses focus, so that add-ons
can apply changes to the note:

```python theme={null}
if runFilter(
    "editFocusLost", False, self.note, self.currentField):
    # something updated the note; schedule reload
    def onUpdate():
        self.loadNote()
        self.checkValid()
    self.mw.progress.timer(100, onUpdate, False)
```

Each filter in this example accepts three arguments: a modified flag,
the note, and the current field. If a filter makes no changes it returns
the modified flag the same as it received it; if it makes a change it
returns True. In this way, if any single add-on makes a change, the UI
will reload the note to show updates.

The Japanese Support add-on uses this hook to automatically generate one
field from another. A slightly simplified version is presented below:

```python theme={null}
def onFocusLost(flag, n, fidx):
    from aqt import mw
    # japanese model?
    if "japanese" not in n.model()['name'].lower():
        return flag
    # have src and dst fields?
    for c, name in enumerate(mw.col.models.fieldNames(n.model())):
        for f in srcFields:
            if name == f:
                src = f
                srcIdx = c
        for f in dstFields:
            if name == f:
                dst = f
    if not src or not dst:
        return flag
    # dst field already filled?
    if n[dst]:
        return flag
    # event coming from src field?
    if fidx != srcIdx:
        return flag
    # grab source text
    srcTxt = mw.col.media.strip(n[src])
    if not srcTxt:
        return flag
    # update field
    try:
        n[dst] = mecab.reading(srcTxt)
    except Exception, e:
        mecab = None
        raise
    return True

addHook('editFocusLost', onFocusLost)
```

The first argument of a filter is the argument that should be returned.
In the focus lost filter this is a flag, but in other cases it may be
some other object. For example, in anki/collection.py, \_renderQA()
calls the "mungeQA" filter which contains the generated HTML for the
front and back of cards. latex.py uses this filter to convert text in
LaTeX tags into images.

In Anki 2.1, a hook was added for adding buttons to the editor. It can
be used like so:

```python theme={null}
from aqt.utils import showInfo
from anki.hooks import addHook

# cross out the currently selected text
def onStrike(editor):
    editor.web.eval("wrap('<del>', '</del>');")

def addMyButton(buttons, editor):
    editor._links['strike'] = onStrike
    return buttons + [editor._addButton(
        "iconname", # "/full/path/to/icon.png",
        "strike", # link name
        "tooltip")]

addHook("setupEditorButtons", addMyButton)
```

## Adding Hooks

If you want to modify a function that doesn’t already have a hook,
please submit a pull request that adds the hooks you need.

In your PR, please describe the use-case you're trying to solve. Hooks that
are general in nature will typically be approved; hooks that target a very
specific use case may need to be refactored to be more general first. For an
example of what this might look like, please see [this PR](https://github.com/ankitects/anki/pull/2340).

The hook definitions are located in [pylib/tools/genhooks.py](https://github.com/ankitects/anki/tree/main/pylib/tools/genhooks.py) and [qt/tools/genhooks\_gui.py](https://github.com/ankitects/anki/blob/main/qt/tools/genhooks_gui.py). When building Anki, the build scripts will
automatically update the hook files with the definitions listed there.

Please see the [docs/](https://github.com/ankitects/anki/tree/main/docs) folder in the source tree for more information.
