If you write docs with MkDocs, you've probably wanted readers to try an example instead of only reading it. The options I found all had a catch:
- markdown-exec runs code when the site is built and puts the output in the page. Great for output that never changes, but readers can't edit the code and run it again.
- Python in the browser (mkdocs-pyscript, pyodide-mkdocs-theme, Pyodide blocks) lets readers run code, but only Python, and the browser downloads a Python runtime first.
So I built mkdocs-onlinecompiler. You add one word to an ordinary code block, and readers get an editor with a Run button in the page, in Python, C++, Java, Go, Rust, SQL or any of 46 languages.
The code runs on OnlineCompiler.io (I build it), in a fresh sandbox for each run with no network access. Your site stays static: GitHub Pages, Read the Docs or any static host works, and nothing runs on it.
Install it
pip install mkdocs-onlinecompiler
Add it to mkdocs.yml with a widget key:
plugins:
- search
- onlinecompiler:
key: YOUR_WIDGET_KEY
Keep search in the list if your site has search: listing plugins replaces MkDocs' defaults. Also add the package to the requirements file your docs build installs from.
The widget key is free, from the Widgets page of the OnlineCompiler.io dashboard. It's made to sit in public pages: it can run code in the widget but can't call the REST API. Set its allowed domain to your docs domain, and make a second key for mkdocs serve on localhost. To keep it out of the file, read it from the environment:
plugins:
- search
- onlinecompiler:
key: !ENV [ONLINECOMPILER_WIDGET_KEY, ""]
Write runnable
Add runnable after the language of any fenced code block:
```python runnable
for n in range(1, 4):
print(n, "squared is", n * n)
```
After Run:
1 squared is 1
2 squared is 4
3 squared is 9
Code blocks without the word stay exactly as they are. The code comes through as written, <, & and quotes included:
```cpp runnable
#include <iostream>
int main() {
std::cout << "a & b " << (1 < 2) << "\n";
}
```
That printed a & b 1.
The block's language picks the compiler (python is Python 3.14, cpp is G++ 15, js is Node.js 24, sql is SQLite), and lang= names another one when you need it. The attribute style works too, and so do your other options:
```python title="frame.py" runnable lang=python-3.14-pandas2
import pandas as pd
print(pd.__version__)
```
``` {.py .runnable}
print("attribute style")
```
Inside admonitions, content tabs and snippets
This is where Material sites put most of their examples, so it had to work there. With pymdownx.superfences (which Material sites usually have on), runnable blocks work inside admonitions and content tabs:
!!! tip "In an admonition"
```rust runnable
fn main() {
println!("hi from Rust");
}
```
=== "Go"
```go runnable
package main
import "fmt"
func main() { fmt.Println("hi from Go") }
```
=== "Plain"
```python
print("plain in a tab")
```
The Rust runner printed hi from Rust, the Go one hi from Go, and the last tab stayed a plain code block. Blocks pulled in with pymdownx.snippets (--8<--) work too.
How it works
Three details, if you want to know what the plugin does to your build.
It's a Markdown preprocessor, placed carefully. The plugin registers a Python-Markdown preprocessor at priority 28. That's after snippets (32) have been pulled in and whitespace normalised (30), and before fenced code (25) sees anything. It finds the runnable blocks, including ones nested in admonitions and tabs, and stashes the widget's markup in their place. So no other extension can reformat the code, and a page that documents the syntax itself (a runnable block inside a four-backtick block) is left alone.
The code is in the HTML. Each runner is a <div> with the code in a <pre>. Search engines and MkDocs' own search index your examples, and readers see the code before the editor loads.
The script loads only on pages with runners. The plugin appends the widget script to the content of those pages only. Material's instant navigation runs scripts in the content it swaps in, so runners start as readers move between pages without a reload. Runners follow Material's palette toggle (and MkDocs' own theme's dark mode) as the reader switches it.
Mistakes show up in the build: a runnable block in a language the service doesn't run stays a normal code block, and MkDocs prints a warning with the page and the language. With mkdocs build --strict, that stops the build, which is what you want in CI.
What it costs
The plugin is free and MIT licensed. Each press of Run is one run on your OnlineCompiler.io account, and the free plan has 100,000 runs a month with no credit card. You can give the widget key its own monthly limit to cap what a public page can use.
Links
- The full guide, with options and troubleshooting: https://onlinecompiler.io/guides/add-runnable-code-examples-to-mkdocs
- PyPI: https://pypi.org/project/mkdocs-onlinecompiler/
- Source: https://github.com/onlinecompiler-io/mkdocs-onlinecompiler
If you use Docusaurus, there's a plugin for that too: docusaurus-plugin-onlinecompiler. And if your docs run on something else, tell me which. We build integrations for other platforms free of charge.

Top comments (0)