Markdown Processing

This page describes how Bestatic processes markdown files and what extensions and customizations are available



At the very basic level, Bestatic simply uses Python-Markdown to process all the markdown files. Python-Markdown is very close to the reference implementation of Markdown and hence you can simply write in any standard markdown format and most likely it will be processed correctly. An extensive documentation on Python-Markdown is available here and here.

Multi-Column Layouts with SplitSection

(New in v0.0.30) You can now create sophisticated multi-column layouts directly in your markdown content using the splitsection class on headers and putting section: true in your page's front matter. Note that You should also use a compatible template: sectiontemplate.html.jinja2 template for proper procesing.

With all that in place, in one of your markdown files, you can write:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
## Introduction {.splitsection}

This is the first column content. It can contain anything - text, images, code blocks, etc.

## Features {.splitsection}

This is the second column content. You can have as many split sections as you need.

## Benefits {.splitsection}

This is the third column content.

Bestatic will parse these sections separately and pass them to your templates with:

  • section.content - The section content

  • section.heading - The corresponding heading text

  • section.index - The section index number

Your theme can then utilize Jinja2 logic along with Bootstrap CSS (or another CSS framework of your choice) to render these sections in a multi-column layout. This gives you complete control over responsive column layouts without hardcoding HTML in your markdown.

To learn more about how to use these sections in your templates, please refer to the example template sections of themes page.



Default Extensions

In Bestatic, you have the Attribute Lists, Tables, Meta Data, Fenced Code Blocks, Markdown Custom Blocks, Emoji, CodeHilite of Python-Markdown enabled by default. You can replace all of these extensions with your own custom extensions by setting markdown_replace to true in your bestatic.yaml file and then adding your own extensions to the extensions list. You can then configure each extension with your own custom settings by adding your own settings to the extension_configs list. For example, if you want to replace the default CodeHilite extension with your own custom extension, you can add the following to your bestatic.yaml file:

1
2
3
4
5
6
7
markdown:
  markdown_replace: true
  extensions:
    - my_custom_extension
  extension_configs:
    my_custom_extension:
      custom_setting: true

In this example, we are replacing the default CodeHilite extension with our own custom extension called my_custom_extension. We are also adding a custom setting to the my_custom_extension extension. See Configuring Extensions section for more details on how to configure individual extensions.

CodeHilite

We also have enabled CodeHilite extension which provides nice looking syntax highlighting options for code blocks in the pages (Pygments works under the hood). These linked pages provide information on how you can choose a particular color theme for your syntax highlighting and how you can generate your own CSS file for that (styles.css; this needs to be included in the static/css folder of your theme).

Markdown Custom Blocks

As mentioned above, we have included the markdown-customblocks extension in Bestatic which is a great tool that provides a common markup for parametrizable and nestable components. Without any effort, you can generate nice looking blocks: These can be admonition/call-out blocks, images, Instagram posts, YouTube videos, Mastodon posts, etc. See Built-in generators to get started quickly.

Emoji Support

We have also enabled emoji support in Bestatic out of the box using PyMdown Extensions emoji. We have adopted Twemoji emoji set (as emoji provider) and SVG (as output). While the full list of emojis that are supported can be found in the source code, any standard emoji shortcode (such as the shortcodes available on emoji cheat sheet or this GitHub gist) should work.

Markdown Include

(New in v0.0.30) The markdown-include extension is now included with Bestatic, allowing you to include content from one markdown file into another using the {!path/to/file.md!} syntax. This is not included by default in Bestatic, but you can easily add it to your bestatic.yaml file. Please see the detailed instructions below.

First, enable and configure the extension in your bestatic.yaml:

1
2
3
4
5
6
markdown:
  extensions: 
    - markdown_include.include
  extension_configs:
    markdown_include.include:
      base_path: ./_includes

The base_path setting tells the extension where to look for included files. You should organize your reusable content in the _includes directory (in your root directory) and sub-directories within it (e.g. _includes/codes, _includes/images, _includes/videos, etc.) for proper processing of the markdown files. For example, if you have a file called included_file.md in the _includes/codes directory, you can include it in your markdown file using the {! codes/included_file.md !} syntax.

Now, if you have two files in your _includes directory (e.g. _includes/included_file.md and _includes/examples/another.md), and you want to include the content from them in one of your main markdown files (e.g. main_document.md), you can do it as follows:

1
2
3
4
5
6
7
8
9
# My Main Document

{! included_file.md !}

More content here.

## Another Section

{! examples/another.md !}

Please note that, the included files should be placed in the _includes directory, _mddata directory, or their sub-directories for best processing.

The included file's content will be inserted at that location when Bestatic processes the markdown. This can be really useful for:

  • Reusing common content across multiple pages

  • Including code snippets or examples from external files

  • Keeping complex YAML data in separate files



Custom Markdown Extensions

(New in v0.0.30) You can now add any of the custom Python-Markdown extensions and Pymdown Extensions via your bestatic.yaml file. Note that, you can even replace the default extensions, as described in the Default Extensions section. Pymdown Extensions are already installed when you install Bestatic and hence you just need to add them to your bestatic.yaml file, as described above.

Installation of extensions...

If you want to install a custom extension that is not included in standard Bestatic installation, you can do so by running pip install <extension-name> in your terminal and then add it to your bestatic.yaml file, as described above. Note that, for this to work, you need to have Python and pip installed on your system and you need to install Bestatic as a python package using pipx or pip, as described in the Installation page. Regular OS-level installations of Bestatic will not work for this.

Adding Extensions

In your bestatic.yaml file, add extensions under the markdown section:

1
2
3
4
5
6
7
8
markdown:
  extensions:
    - md_in_html
    - toc
    - markdown_include.include
    - pymdownx.arithmatex
    - pymdownx.betterem
    - pymdownx.magiclink

Any extension from the python-markdown or pymdown-extensions packages can be added this way. Make sure the extension package is installed in your Python environment.



Configuring Extensions

(New in v0.0.30) You can configure individual markdown extensions with custom settings using the extension_configs section within the markdown section of your bestatic.yaml file.

Extension Configuration Example
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
markdown:
  markdown_replace: false # Set to true to replace all the default extensions with your own custom extensions and configurations. 
  extensions:
    - md_in_html
    - toc
    - markdown_include.include
  extension_configs:
    markdown_include.include:
      base_path: ./_includes

    toc:
      marker: "[My Table of Contents]"
      # permalink: true

    codehilite:
      linenos: table # This is the default value for the CodeHilite extension in Bestatic. You can change it to your own liking.

Each extension has its own configuration options. Refer to the Python-Markdown documentation and Pymdown Extensions documentation for available configuration options for each extension.



Custom Shortcodes

(New in v0.0.30) Shortcodes are reusable macros/Python functions that convert simple snippets in your markdown content into predefined HTML markup. You can now create custom shortcodes by writing Python functions and placing them in the _shortcodes directory at the root of your site. You can then use these shortcodes in your markdown content by wrapping them in {!!{ }!!} tags.

Creating a Shortcode

Create a file _shortcodes/alert.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def render(attrs):
    """Render an alert box with error handling
    Usage: {!!{ alert This is a message type=info }!!}
    Types: info, warning, danger, success
    """
    try:
        # Get the alert type with validation
        alert_type = attrs.get('type', 'info')
        valid_types = ['info', 'warning', 'danger', 'success']
        if alert_type not in valid_types:
            alert_type = 'info'  # Default to info if invalid type

        # Get content safely
        content = attrs.get('content', '')
        if not content:
            raise ValueError("Alert shortcode requires content")

        # Render alert with sanitized parameters
        return f'''<div class="alert alert-{alert_type}" role="alert">
  <p>{content}</p>
</div>'''

    except Exception as e:
        import warnings
        warnings.warn(f"Error in alert shortcode: {str(e)}")
        return f'<!-- Alert shortcode error: {str(e)} -->'
Using Shortcodes in Markdown

In your markdown content, use the shortcode:

1
2
3
{!!{ alert Check this out! type=warning }!!}

{!!{ alert This is important information type=info }!!}
More Shortcode Examples

Create a keyboard shortcode in _shortcodes/key.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def render(attrs):
    """Render keyboard keys in <kbd> tags
    Usage: {!!{ key Ctrl+C }!!}
    """
    try:
        # Get the key content
        content = attrs.get('content', '')
        if not content:
            raise ValueError("Key shortcode requires content")

        # Clean up the content and handle combinations
        keys = content.replace('+', '</kbd> + <kbd>')

        return f'<kbd>{keys}</kbd>'

    except Exception as e:
        import warnings
        warnings.warn(f"Error in key shortcode: {str(e)}")
        return f'<!-- Key shortcode error: {str(e)} -->'

Usage:

1
Press {!!{ key Ctrl+C }!!} to copy.

Create a social media shortcode in _shortcodes/social.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
SOCIAL_PLATFORMS = {
    'github': {
        'url': 'https://www.github.com/{}',
        'icon': 'bi bi-github'
    },
    'twitter': {
        'url': 'https://twitter.com/{}',
        'icon': 'bi bi-twitter'
    },
    'linkedin': {
        'url': 'https://www.linkedin.com/in/{}',
        'icon': 'bi bi-linkedin'
    }
}

def render(attrs):
    """Render social media links with icons
    Usage: {!!{ social platform=github username size=36 }!!}
    """
    try:
        # Get platform and username
        platform = attrs.get('platform', '').lower()
        username = attrs.get('content', '')
        size = attrs.get('size', '36')

        if not platform or not username:
            raise ValueError("Social shortcode requires platform and username")

        if platform not in SOCIAL_PLATFORMS:
            raise ValueError(f"Unsupported platform: {platform}")

        platform_data = SOCIAL_PLATFORMS[platform]
        url = platform_data['url'].format(username)
        icon = platform_data['icon']

        return f'''<a href="{url}" target="_blank" rel="noopener">
    <i class="{icon}" style="font-size:{size}px"></i>
</a>'''

    except Exception as e:
        import warnings
        warnings.warn(f"Error in social shortcode: {str(e)}")
        return f'<!-- Social shortcode error: {str(e)} -->'

Usage:

1
{!!{ social platform=github content=yourusername size=48 }!!}

Bestatic will process shortcodes and replace them with the HTML returned by your Python function. Shortcodes:

  • Must be placed in the _shortcodes directory at the root of your site.

  • Must have a render(attrs) function that accepts an attrs dictionary.

  • The attrs dictionary contains all parameters passed to the shortcode.

  • The main content is available as attrs.get('content', '').

  • Named parameters are accessed via attrs.get('parameter_name', 'default').

With custom shortcodes, you can very comprehensively customize the Markdown content of your website and you are only limited by your imagination!