Skip to content

Post-production of pages


Within mkdocs-macros, we call post-production all actions that follow the definition of macros, with the define_env(env) hook.

They are of three types of post-production:

  1. Modifying markdown pages (either before or after macro rendering)
  2. Influencing the HTML pages generated by mkdocs
  3. Performing operations after mkdocs has completely executed

Markdown pages (headers or footers, etc.)


From version 0.5.2

From version 1.0.0

The env.raw_markdown attribute is deprecated. Use env.markdown instead.

There are specific cases where you want your module code to be able to modify the markdown code of a page, without using macros. One typical example is when you wish to alter the pages generated e.g. by adding footer information at the bottom of each page, or header information at the top of each page, or any other operation..

The proper time to do that, would be before or after the macros (Jinja2 directives) have been processed.

Technical note: a limitation of the macros mechanism

The define_env() function operates at the time when MkDocs prepares the configuration of website (the on_config() event). This is a global event, i.e. any change made at this point will affect the whole website.

The limitation is that the define_env() function is "aware" of the general configuration, not of the content of single pages.

True, it allows you to declare macros which will be interpreted later, for each page (on the on_page() event). But it won't allow you to modify pages outside of that mechanism.

To act on such cases that vary with each markdown page (and depend on each page, not on the general configuration), you may use the two hooks in your module, which are executed before the markdown is actually rendered:

  1. on_pre_page_macros(env) : just before the macros are rendered on a specific page (macros are still present). At this stage you can still insert text that contains macros.
  2. on_post_page_macros(env) : just after the macros are rendered on a specific page (macros have turned into their markdown equivalent). At this stage you can insert

You can access the markdown through the env.markdown property (string).

Application to headers or footers

From version 1.0.0

on_pre_page_macros(env) works as shown only from that version.

For lower versions, in on_post_page_macros(env) replace env.raw_markdown with env.markdown

Headers or footers can be added before or after the rendering process. If you add, e.g. a footer before the rendering process, the macros inserted will be interpreted.

You may a add a variable footer after the rendering process, but in that case you must use the Python syntax of the module to calculate the values that go in each page footer.

Availability of env.markdown

env.markdown is accessible only at this stage of the production process (page production). It is not available in the define_env() hook, which is executed before the production of the pages has started.

If you wish to add a footer, you have the choice between:

  • using macros (before rendering):
def on_pre_page_macros(env):
    Actions to be done before macro interpretation,
    when the macros have not yet been rendered.
    footer = "\n\n{{ page.title }}"
    env.markdown += footer
  • or doing it programmatically:
def on_post_page_macros(env):
    Actions to be done after macro interpretation,
    when the macros have been rendered
    This will add a (Markdown or HTML) footer -- produced by Python.
    footer = "f'\n\n'{}"
    env.markdown += footer

If you wish to use jinja2 directives, do it it before rendering:

def on_pre_page_macros(env):
    Actions to be done before macro interpretation,
    footer = "\n\n{% include('') %}"
    env.markdown += footer

Technical Notes

Time of execution

on_pre_page_macros(env) and on_post_page_macros(env) are executed by the on_page_markdown() event of MkDocs:

  • before the rendering the page
  • before or after interpretation of the macros, respectively

They operates on all pages, page by page.

Use of global variables

To facilitate the communication within your module between define_env() and on_page_markdown() you may want to define global variables. For a refresher on this, see the summary on W3 Schools.

Content and availability of attributes

the env. markdown and attributes are available only from the point of on_pre_page_macros() on. Thay are not available for the define_env(env) hook.

env.markdown contains the markdown of the page, before rendering (in on_pre_page_macros()) or after rendering (in on_post_page_macros())) contains notably the following information:

Attribute Value
title title of the page
abs_url the absolute url of the page from the top of the hierarchy
canonical_url the complete url of the page (typically with https://...)
markdown the whole markdown code (before or after rendering).
meta the meta data dictionary, as updated (typically) from the YAML header. is a copy so modifying it won't affect the rendering process (no black magic allowed).

Html pages


It is possible to influence to some degree, the production of html files, mostly by acting on templates.

Trickling values into the mkdocs theme

You may want to programmatically add some meta values to a page, which should be forwarded to the HTML theme used by mkdocs (which has its own, distinct Jinja2 engine).

This is called trickling (values are trickled from the mkdocs-macros environment of variables to the html page).

Do not confuse

Note that that the Jinja2 engine that operates on a mkdocs theme's pages is completely distinct from the mkdocs-macros Jinja2 engine, which is used to render macros on Markdown pages. The rendering of template variables occurs at a later stage of the page production process.

For example you'd want to be able to always have a value for this:

<meta name="description" content="{{ page.meta.description }}" />

Normally metadata would be defined in the YAML header of the markdown page:

title: my title
description: This is a description


But supposing this was not the case ? Or supposing you want to check or alter that information?

def on_post_page_macros(env):
    Actions to be done after macro interpretation,
    when the markdown is already generated
    # This information will get carried into the HTML template.['description'] = ...

Post-build files

From version 0.5


Sometimes, you want your Python code to add some files to the HTML website that MkDocs has just produced (after MkDoc's usual production workflow).

These could be:

  • an extra HTML page
  • an additional or updated image
  • a RSS feed
  • a form processor (written for example in the php language)
  • ....


The logical idea is to add files to the site (HTML) directory, which is given by env.conf['site_dir'].

Beware the of the 'disappeared file' trap

One problem will occur if you attempt to add files to the site directory from within the define_env() function in your macro module.

The file will be created, but nevertheless it is going to "disappear".

The reason is that the code of define_env() is executed during the on_config event of MkDocs; and you can expect the site directory to be wiped out later, during the build phase (which produces the HTML files). So, of course, the files you just created will be deleted.

Post-Build Actions

The solution to do that, is to perform those additions as post-build actions (i.e. executed with on_post_build event).

Here is an example. Suppose you want to add a special file (e.g. HTML).

import os
MY_FILENAME = 'foo.html'
my_HTML = None

def define_env(env):
    "Definition of the module"

    # put here your HTML content
    my_HTML = ......

def on_post_build(env):
    "Post-build actions"

    site_dir = env.conf['site_dir']
    file_path = os.path.join(site_dir, MY_FILENAME)
    with open(file_path, 'w') as f:

The mkdocs-macros plugin will pick up that function and execute it during as on on_post_build() action.

Argument of on_post_build()

In this case, the argument is env (as for define_env()); it is not config as in the on_post_build() method in an MkDocs plugin.

If you want to get the plugin's arguments, you can find them in the env.conf dictionary.

Global variables

To facilitate the communication between define_env() and on_post_build you may want to define global variables within your module (in this example: MY_FILENAME and my_HTML).


Do not forget that any variable assigned for the first time within a function is by default a local variable: its content will be lost once the function is fully executed.

In the example above, my_HTML must appear in the global definitions; which is why it was assigned an empty value.