diff --git a/pkgs/output-template/example.ini b/pkgs/output-template/example.ini
index 4f4d3b9b..c53dea99 100644
--- a/pkgs/output-template/example.ini
+++ b/pkgs/output-template/example.ini
@@ -1,2 +1,3 @@
 wpa_passphrase={{ secret "colours/black"  }}
 think = {{ string.format("%q", secret("colours/blue")) }}
+argonaut = {{ json_quote "hello\ngoodbye\tnext\027" }}
diff --git a/pkgs/output-template/example.ini.expected b/pkgs/output-template/example.ini.expected
index 47c6f93b..ba1960cd 100644
--- a/pkgs/output-template/example.ini.expected
+++ b/pkgs/output-template/example.ini.expected
@@ -1,2 +1,3 @@
 wpa_passphrase=000000
 think = "0000ff"
+argonaut = "hello\ngoodbye\tnext\u001B"
diff --git a/pkgs/output-template/output-template.fnl b/pkgs/output-template/output-template.fnl
index a6365a7c..0d0da280 100644
--- a/pkgs/output-template/output-template.fnl
+++ b/pkgs/output-template/output-template.fnl
@@ -1,11 +1,32 @@
 (local svc (require :anoia.svc))
 
+(fn json-escape [s]
+  ;; All Unicode characters may be placed within the quotation marks,
+  ;; except for the characters that MUST be escaped:
+  ;; quotation mark, reverse solidus, and the control characters (U+0000
+  ;; through U+001F). (RFC 8259)
+  (-> s
+      (string.gsub
+       "[\"\b\f\n\r\t]" {
+                         "\b" "\\b"
+                         "\"" "\\\""
+                         "\f" "\\f"
+                         "\n" "\\n"
+                         "\r" "\\r"
+                         "\t" "\\t"
+                         })
+      (string.gsub
+       "([\x00-\x1b])"
+       (fn [x] (string.format "\\u%04X" (string.byte x))))))
+
+
 (fn substitute [text service opening closing]
   (let [delim (.. opening "(.-)" closing)
         myenv {
                : string
                :secret (fn [x] (service:output x))
                :lua_quote #(string.format "%q" %1)
+               :json_quote (fn [x] (.. "\"" (json-escape x) "\""))
                }]
     (string.gsub text delim
                  (fn [x]