diff --git a/module_index.json b/module_index.json
index adb1015..fc30539 100755
--- a/module_index.json
+++ b/module_index.json
@@ -337,7 +337,7 @@
         "version": "0.10",
         "author": "Emanuil Rusev & Starbeamrainbowlabs",
         "description": "An upgraded (now default!) parser based on Emanuil Rusev's Parsedown Extra PHP library (https:\/\/github.com\/erusev\/parsedown-extra), which is licensed MIT. Please be careful, as this module adds some weight to your installation.",
-        "lastupdate": 1571590586,
+        "lastupdate": 1571601235,
         "optional": false,
         "extra_data": {
             "Parsedown.php": "https:\/\/raw.githubusercontent.com\/erusev\/parsedown\/fe7a50eceb4a3c867cc9fa9c0aa906b1067d1955\/Parsedown.php",
diff --git a/modules/parser-parsedown.php b/modules/parser-parsedown.php
index eea9919..4e7df37 100644
--- a/modules/parser-parsedown.php
+++ b/modules/parser-parsedown.php
@@ -799,8 +799,25 @@ class PeppermintParsedown extends ParsedownExtreme
 	}
 	
 	protected function blockFencedCodeComplete($block) {
+		global $settings;
 		$result = parent::blockFencedCodeComplete($block);
-		error_log("[code block] " . var_export($block, true));
+		
+		$language = preg_replace("/^language-/", "", $block["element"]["element"]["attributes"]["class"]);
+		
+		if(!isset($settings->parser_ext_renderers->$language))
+			return $result;
+		
+		$text = $result["element"]["element"]["text"];
+		$renderer = $settings->parser_ext_renderers->$language;
+		
+		$result["element"] = [
+			"name" => "img",
+			"attributes" => [
+				"alt" => "Diagram rendered by {$renderer->name}",
+				"src" => "?action=parsedown-render-ext&language=$language&source=".rawurlencode($text)
+			]
+		];
+		
 		return $result;
 	}
 	
diff --git a/peppermint.guiconfig.json b/peppermint.guiconfig.json
index 5271d18..e5f82c9 100644
--- a/peppermint.guiconfig.json
+++ b/peppermint.guiconfig.json
@@ -21,6 +21,32 @@
 	"parser": { "type": "text", "description": "The parser to use when rendering pages. Defaults to an extended version of parsedown (http://parsedown.org/)", "default": "parsedown" },
 	"parser_cache": { "type": "checkbox", "description": "Whether parser output should be cached to speed things up. The cache directory is <code>._cache</code> in the data directory - delete it if you experience issues (unlikely).", "default": true },
 	"parser_cache_min_size": { "type": "number", "description": "The minimum size a source string must be (in bytes) before it's considered eligible for caching.", "default": 1024 },
+	"parser_ext_renderers": { "type": "parserext", "description": "Used by the parsedown parser as an object mapping fenced code block languages to their respective external renderers. Should be in the form <code>language_code</code> → <code>external renderer definition</code>. See the default for examples on how to define an external renderer.", "default": {
+		"nomnoml": {
+			"name": "nomnoml",
+			"description": "The nomnoml UML diagram renderer. Requires the nomnoml-cli npm package to be globally installed.",
+			"url": "http://nomnoml.com/",
+			"cli": "nomnoml --format svg",
+			"cli_mode": "pipe",
+			"output_format": "image/svg+xml"
+		},
+		"plantuml": {
+			"name": "PlantUML",
+			"description": "The PlantUML diagram renderer. Supports many different diagram types. Requires plantuml to be installed.",
+			"url": "http://plantuml.com/",
+			"cli": "plantuml -tsvg -pipe",
+			"cli_mode": "pipe",
+			"output_format": "image/svg+xml"
+		},
+		"lilypond": {
+			"name": "LilyPond",
+			"description": "A music notation typesetter. Requires lilypond to be installed.",
+			"url": "http://lilypond.org/manuals.html",
+			"cli": "lilypond --png --output {output_file} {input_file}",
+			"cli_mode": "file",
+			"output_format": "image/png"
+		}
+	} },
 	"interwiki_index_location": { "type": "text", "description": "The location to find the interwiki wiki definition file, which contains a list of wikis along with their names, prefixes, and root urls. May be a URL, or simply a file path - as it's passed to file_get_contents(). If left blank, interwiki link parsing is disabled.", "default": null },
 	"clean_raw_html": { "type": "checkbox", "description": "Whether page sources should be cleaned of HTML before rendering. It is STRONGLY recommended that you keep this option turned on.", "default": true },
 	"all_untrusted": { "type": "checkbox", "description": "Whether to treat both page sources and comment text as untrusted input. Untrusted input has additional restrictions to protect against XSS attacks etc. Turn on if your wiki allows anonymous edits.", "default": false},
