Writing modules¶
Modules are Python files (or packages) that register variables, macros, and filters for use in your Markdown templates. The API is compatible with MkDocs Macros so you can reuse the same define_env(env) pattern.
The define_env(env) function¶
Every module must define a function named define_env that takes one argument, env:
def define_env(env):
"""
Hook for variables, macros, and filters.
"""
# variables
env.variables["site_name"] = "My Docs"
@env.macro
def hello(name):
return f"Hello, {name}!"
@env.filter
def reverse(s):
return s[::-1]
This function is called once when the extension loads. Use it to fill env.variables and to register macros and filters with @env.macro and @env.filter.
Location of the module¶
By default, the extension looks for a module named main (e.g. main.py) in the project root (the directory given by project_root, which defaults to ".").
- Single file:
main.pyin the project root. - Custom name: set
module_namein config, e.g.module_name: "mymacros"→mymacros.pyormymacros/__init__.py. - Subpath: e.g.
module_name: "include/macros"→include/macros.pyorinclude/macros/__init__.pyunderproject_root.
The extension tries, in order:
project_root / f"{module_name}.py"project_root / module_name / "__init__.py"importlib.import_module(module_name)(if the module is on the Python path)
If no module is found or it has no define_env, the extension still runs; it just has no extra variables, macros, or filters from that module.
Pluglets (preinstalled modules)¶
You can also load pluglets: Python packages installed in the environment (e.g. via pip) that define define_env(env). Set the modules config option to a list of package names:
Each pluglet is imported with importlib.import_module(name); its variables, macros, and filters are merged. The local module_name module is loaded first, then pluglets, so the local module can override pluglet names. Pluglets are useful for sharing macros across projects.
Registering variables, macros, and filters¶
Variables¶
Add key/value pairs to env.variables. They become available in every page as {{ key }}.
def define_env(env):
env.variables["author"] = "Alice"
env.variables["version"] = "1.0"
# or with dot notation (same thing):
env.variables.author = "Alice"
Values can be strings, numbers, lists, or dicts; Jinja2 will use them as usual.
Macros¶
Macros are callable in the template, e.g. {{ price(10, 5) }}. Register them with the @env.macro decorator or by calling env.macro(func, name).
def define_env(env):
@env.macro
def price(unit_price, quantity):
return unit_price * quantity
# Custom name
def square(x):
return x * x
env.macro(square, "sq")
In Markdown:
Macros can return a string (including Markdown or HTML). They are executed at render time.
Filters¶
Filters are used with the pipe syntax: {{ value | filter_name }}. Register them with @env.filter.
def define_env(env):
@env.filter
def double(x):
n = int(x) if isinstance(x, str) and x.isdigit() else x
return 2 * n
@env.filter
def reverse(s):
return s[::-1]
In Markdown:
The first argument to the filter is the value on the left of |; additional arguments can be passed in parentheses in Jinja2.
The env object (Markdown Macros)¶
In this extension, env only provides what’s needed for the MkDocs-Macros–compatible API:
| Attribute / method | Description |
|---|---|
env.variables |
Dict of variables merged into the Jinja2 context. Add or change keys here. |
env.macro(fn, name=None) |
Register a macro. Use as @env.macro or env.macro(func) or env.macro(func, "name"). |
env.filter(fn, name=None) |
Register a filter. Use as @env.filter or env.filter(func). |
There is no env.conf, env.config, or env.page in this extension (those exist in the MkDocs Macros plugin because it has access to the MkDocs config and current page). Here, page-level data comes only from YAML front matter, which is parsed and merged into the template variables automatically.
Implementing the module as a package¶
You can use a package (a directory with __init__.py) instead of a single file:
In main/__init__.py:
from .util import format_price
def define_env(env):
env.variables["site"] = "My Docs"
@env.macro
def price(unit, qty):
return format_price(unit * qty)
Set module_name: "main" and the extension will load main/__init__.py.
Notes¶
- Imports: Use normal Python imports at the top of your module;
define_envcan use any code you need. - Security: Macros and filters run with the same privileges as the process that builds the docs. Avoid exposing sensitive data or running arbitrary code from untrusted content.
- Errors: If
define_envraises an exception, the extension may fail to load. Validate config or variables insidedefine_envif needed.