How to build a simple Wagtail Streamfield code block with highlight.js
12 March 2025
By default, the formatting features displayed in Wagtail's RichTextField
can be modified to include code
, which then allows a user to enter inline code. However, there is no such default means of supporting code blocks. For example:
class BasicPage(Page):
pass
Existing solutions
Several solutions already exist to tackle this, including:
- wagtailcodeblock, which uses PrismJS for highlighting
- wagtail-code-blocks and Syntax Highlighted Code Blocks with Wagtail CMS, which use Pygments for highlighting
- Create a Wagtail Code Block with highlightjs which uses highlight.js for highlighting
Another solution(?)
Given my preference for highlight.js, the obvious choice was to implement the solution described by the last of these links. However, whilst it is an extensive tutorial, I really only needed a simple solution which provided a custom Streamfield block that would allow a user to:
- select the language for which syntax highlighting should be applied
- (optionally) add a filename, to display at the top of a code block
- add the code, to display in the body of the code block
How to
My solution was to:
- create a custom Streamfield block model (
CodeBlock
) - create a custom template (
code_block.html
) which would be used to render the new block - edit my existing
blog_page.html
template to include the relevant CSS and JavaScript files required by highlight.js
For simplicity, I put the CodeBlock
model and the code_block.html
template in my existing, general-purpose base
app.
The CodeBlock
model
Notably, the CodeBlock
model:
- subclasses Wagtail's
StructBlock
(as per Wagtail's documentation) - defines a
language
field which is aChoiceBlock
whose choices are key-value pairs wherein each key matches the alias of a language supported by highlight.js - defines an optional
filename
field - defines a
code
field with aform_classname
that can be used to target the field in the admin UI - defines a template to use when rendering the block
from wagtail.blocks import CharBlock, ChoiceBlock, StructBlock, TextBlock
class CodeBlock(StructBlock):
"""
A custom Wagtail Streamfield block for displaying code highlighted using
highlight.js.
"""
language = ChoiceBlock(
choices=[
("bash", "Bash"),
("cs", "C#"),
("css", "CSS"),
("django", "Django"),
("dockerfile", "Dockerfile"),
("gdscript", "GDScript"),
("graphql", "GraphQL"),
("javascript", "JavaScript"),
("json", "JSON"),
("html", "HTML"),
("tex", "LaTeX"),
("less", "Less"),
("markdown", "Markdown"),
("nginx", "Nginx"),
("plaintext", "Plaintext"),
("postgresql", "PostgreSQL"),
("php", "PHP"),
("python", "Python"),
("python-repl", "Python REPL"),
("scss", "SCSS"),
("sql", "SQL"),
("twig", "Twig"),
("typescript", "TypeScript"),
("xml", "XML"),
("yml", "YAML"),
],
required=False
)
filename = CharBlock(
required=False
)
code = TextBlock(
form_classname="code-block",
)
class Meta:
template = "base/code_block.html"
The code_block.html
template
Notably, the code_block.html
template:
- uses
value
to access the model's fields - renders the optional filename markup separately from the code
- uses
language-{{ value.language }}
as a target for highlight.js
{% load wagtailcore_tags %}
{% if value.filename %}
<span class="filename"># {{ value.filename }}</span>
{% endif %}
<pre><code class="language-{{ value.language }}">{{ value.code }}</code></pre>
The blog_page.html
template
Notably, the blog_page.html
template:
- uses local, minified copies both of highlight.js and the a11y-dark theme
- calls
highlightAll
viacode.js
{% extends "base.html" %}
{% load static wagtailcore_tags %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/a11y-dark.min.css' %}">
{% endblock extra_css %}
{% block extra_js %}
<script src="{% static 'js/highlight.min.js' %}" defer></script>
<script src="{% static 'js/code.js' %}" defer></script>
{% endblock %}
{% block content %}
{% include_block page.body %}
{% endblock %}
Code
For the complete website code, please see https://github.com/input/lm-wagtail.