Building a Quarto Extension

Building my First Quarto Shortcode Extension

Quarto is a scientific publishing system used to build documents, presentations, books and websites. Quarto extensions are a powerful way to extend the behavior of Quarto. These extensions are built using Lua, a language that I do not have much familiarity with but, building a simple extension is actually quite straighforward.

In this example I'll create a shortcode extension that allows you to add comments to a Quarto document. The comments can be turned on or off by using an environemnt variable defined in


To initialize a Quarto shortcode extension, use the following command and enter a name for the extension (in this case comment)

1quarto create extension shortcode

The following folder structure will be created:

2├── _extensions  
3│   └── comment  
4│       ├── _extension.yml  
5│       └── comment.lua  
6├── .gitignore  
7├── .luarc.json  
8├── example.qmd

I use VS Code for coding. When the quarto create command is executed, VS Code opens a new window at the comment folder, ready for development.

Building the Shortcode Extension

This Quarto extension allows an author to add comments to a Quarto document. It also adds a parameter to control if comments should included on not when a file is rendered. I've found this extension useful when adding instructional comments to a template.

The extension contains three shortcodes. One to start a comment block, one to end a comment block and one to add an inline comment.
The code concept is pretty simple. For comment blocks, surround the contents in a div with either the cmt class (which can be used to control styling) when the comment variable is set to true, or display: none when the comment variable is set to false. For inline comments surround the contents in a span with the cmt class when the comment variable is set to true.

The comment.lua file is shown below.

 1-- start a comment block
 2function comment_start(args, kwargs, meta)
 3  cmt = meta['show_comments']
 4  if cmt == nil or cmt == false then
 5    return pandoc.RawBlock('html', "<div style = 'display: none;'>")
 6  else
 7    return pandoc.RawBlock('html', "<div class = 'cmt'>")
 8  end
11-- end a comment block
12function comment_stop()
13  return pandoc.RawBlock('html', "</div>")
16-- inline comment
17function comment(args, kwargs, meta)
18  txt = pandoc.utils.stringify(args[1])
19  cmt = meta['show_comments']
20  if cmt == true then
21    return pandoc.RawInline('html', "<span class = 'cmt'>" .. txt .. "</span>")
22  end 

In addition to the comment.lua file, an example.qmd should be written (plus, in this case, a style.css css file). If the example.qmd file is previewed then it will automatically update as you edit comment.lua - a great way to ensure that your extension is working as expected. The example.qmd and style.css files for the comment extension are shown below:

 2title: "Comment Example"
 4  html:
 5    css: style.css
 6show_comments: true
 9## Comments Test - Commented text within a paragraph
11This is some uncommented text.
12{{< comment_start >}}
13Here is a <b>comment</b> containing some instructional information.
14{{< comment_stop >}}
15Finally, some additional uncommented text.
17## Comments Test - Inline commenting
19This comment is an inline comment {{< comment "It can include instruction within the text" >}} followed by addititional text.
1.cmt {
2  color: #AAAA00

To run in interactive / preview mode simple execute the command:

1quarto preview example.qmd

Exploring the Files

The example.qmd file contains some example Quarto to test the extension along with a header. The header applies styles in the style.css file (the .cmt class) and also defines a boolean parameter called show_comments. The comment.lua file contains three functions, each evaluating to a shortcode in Quarto. They are each explored below.


This shortcode starts a comment block. It takes three arguments, args, kwargs and meta (arguments, named arguments and document/project-level metadata). In this function we only use the document/project-level metadata and run the command cmt = meta['show_comments'] to create a variable, cmt holding the metadata show_comments, a boolean. Generally, arguments passed to lua will be a list of pandoc inlines and require pandoc.utils.stringify() to convert to strings. In our case, we are only passing a single value in show_comments and it will be a boolean.
If the cmt variable is false or missing then a div block is started with display: none to hide it. If the cmt variable is true then a div block is started with the .cmt style.


This simply closes the div initiated by comment_start().


This shortcode adds a comment inline. It works slightly differently to comment_start() by reading the first argument from the shortcode using txt = pandoc.utils.stringify(args[1]). It then defines the cmt variable as in comment_start() and, if cmt is true, outputs the commented text, returning nothing if cmt is not true.


In this example, I've included show_comments as a document-level parameter. It could be substituted at the project-level in _quarto.yml to manage a set of documents / website. It can also be included in _variables.yml as a project-level variable in which case the line cmt = meta['show_comments'] would be replaced by cmt = meta['_quarto-vars']['show_comments'] in order to access variables defined in the _variables.yml file.

Final Steps

Finally, a licence file was added (MIT licence), README was edited and the extension was pushed to GitHub at To install to a quarto project simply run:

1quarto add harveyl888/comment


Quarto includes methods to build all manner of extensions for documents and projects. It is pretty simple to pick up lua, the scripting language for Quarto extensions, which means writing shortcodes should be fairly straightforward. The use of an example file along with Quarto's hot reload provides a powerful way to build up shortcodes in an interactive manner1.

  1. Note: the lua print command can be used when developing in an interactive manner - it will print to the terminal whenever the example file is re-rendered. ↩︎