diff --git a/doc/manual/generate-builtins.jq b/doc/manual/generate-builtins.jq
deleted file mode 100644
index c38799479671609ab337191710287ab2cb8408cb..0000000000000000000000000000000000000000
--- a/doc/manual/generate-builtins.jq
+++ /dev/null
@@ -1,6 +0,0 @@
-. | to_entries | sort_by(.key) | map(
-  "  - `builtins." + .key + "` "
-  + (.value.args | map("*" + . + "*") | join(" "))
-  + "  \n\n"
-  + (.value.doc | split("\n") | map("    " + . + "\n") | join("")) + "\n\n"
-) | join("")
diff --git a/doc/manual/generate-builtins.nix b/doc/manual/generate-builtins.nix
new file mode 100644
index 0000000000000000000000000000000000000000..416a7fdba1c985c07f442a58b833a49c4f509939
--- /dev/null
+++ b/doc/manual/generate-builtins.nix
@@ -0,0 +1,14 @@
+with builtins;
+with import ./utils.nix;
+
+builtins:
+
+concatStrings (map
+  (name:
+    let builtin = builtins.${name}; in
+    "  - `builtins.${name}` " + concatStringsSep " " (map (s: "*${s}*") builtin.args)
+    + "  \n\n"
+    + concatStrings (map (s: "    ${s}\n") (splitLines builtin.doc)) + "\n\n"
+  )
+  (attrNames builtins))
+
diff --git a/doc/manual/generate-manpage.jq b/doc/manual/generate-manpage.jq
deleted file mode 100644
index dd632f162a7a215762d80d9872326c60082126db..0000000000000000000000000000000000000000
--- a/doc/manual/generate-manpage.jq
+++ /dev/null
@@ -1,44 +0,0 @@
-def show_flags:
-  .flags
-  | map_values(select(.category != "config"))
-  | to_entries
-  | map(
-      "  - `--" + .key + "`"
-      + (if .value.shortName then " / `" + .value.shortName + "`" else "" end)
-      + (if .value.labels then " " + (.value.labels | map("*" + . + "*") | join(" ")) else "" end)
-      + "  \n"
-      + "    " + .value.description + "\n\n")
-  | join("")
-  ;
-
-def show_synopsis:
-  "`" + .command + "` [*flags*...] " + (.args | map("*" + .label + "*" + (if has("arity") then "" else "..." end)) | join(" ")) + "\n\n"
-  ;
-
-def show_command:
-  . as $top |
-  .section + " Name\n\n"
-  + "`" + .command + "` - " + .def.description + "\n\n"
-  + .section + " Synopsis\n\n"
-  + ({"command": .command, "args": .def.args} | show_synopsis)
-  + (if .def | has("doc")
-     then .section + " Description\n\n" + .def.doc + "\n\n"
-     else ""
-     end)
-  + (if (.def.flags | length) > 0 then
-      .section + " Flags\n\n"
-      + (.def | show_flags)
-    else "" end)
-  + (if (.def.examples | length) > 0 then
-      .section + " Examples\n\n"
-      + (.def.examples | map(.description + "\n\n```console\n" + .command + "\n```\n" ) | join("\n"))
-      + "\n"
-     else "" end)
-  + (if .def.commands then .def.commands | to_entries | map(
-      "# Subcommand `" + ($top.command + " " + .key) + "`\n\n"
-      + ({"command": ($top.command + " " + .key), "section": "##", "def": .value} | show_command)
-    ) | join("") else "" end)
-  ;
-
-"Title: nix\n\n"
-+ ({"command": "nix", "section": "#", "def": .} | show_command)
diff --git a/doc/manual/generate-manpage.nix b/doc/manual/generate-manpage.nix
new file mode 100644
index 0000000000000000000000000000000000000000..4709c0e2c99f3fa53038c0583567be594884a959
--- /dev/null
+++ b/doc/manual/generate-manpage.nix
@@ -0,0 +1,56 @@
+with builtins;
+with import ./utils.nix;
+
+let
+
+  showCommand =
+    { command, section, def }:
+    "${section} Name\n\n"
+    + "`${command}` - ${def.description}\n\n"
+    + "${section} Synopsis\n\n"
+    + showSynopsis { inherit command; args = def.args; }
+    + (if def ? doc
+       then "${section} Description\n\n" + def.doc + "\n\n"
+       else "")
+    + (let s = showFlags def.flags; in
+       if s != ""
+       then "${section} Flags\n\n${s}"
+       else "")
+    + (if def.examples or [] != []
+       then
+         "${section} Examples\n\n"
+         + concatStrings (map ({ description, command }: "${description}\n\n```console\n${command}\n```\n\n") def.examples)
+       else "")
+    + (if def.commands or [] != []
+       then concatStrings (
+         map (name:
+           "# Subcommand `${command} ${name}`\n\n"
+           + showCommand { command = command + " " + name; section = "##"; def = def.commands.${name}; })
+           (attrNames def.commands))
+       else "");
+
+  showFlags = flags:
+    concatStrings
+      (map (longName:
+        let flag = flags.${longName}; in
+        if flag.category or "" != "config"
+        then
+          "  - `--${longName}`"
+          + (if flag ? shortName then " / `${flag.shortName}`" else "")
+          + (if flag ? labels then " " + (concatStringsSep " " (map (s: "*${s}*") flag.labels)) else "")
+          + "  \n"
+          + "    " + flag.description + "\n\n"
+        else "")
+        (attrNames flags));
+
+  showSynopsis =
+    { command, args }:
+    "`${command}` [*flags*...] ${concatStringsSep " "
+      (map (arg: "*${arg.label}*" + (if arg ? arity then "" else "...")) args)}\n\n";
+
+in
+
+command:
+
+"Title: nix\n\n"
++ showCommand { command = "nix"; section = "#"; def = command; }
diff --git a/doc/manual/generate-options.jq b/doc/manual/generate-options.jq
deleted file mode 100644
index ccf62e8eddef35c113090d755c729249cf08c548..0000000000000000000000000000000000000000
--- a/doc/manual/generate-options.jq
+++ /dev/null
@@ -1,16 +0,0 @@
-. | to_entries | sort_by(.key) | map(
-  "  - `" + .key + "`  \n\n"
-  + (.value.description | split("\n") | map("    " + . + "\n") | join("")) + "\n\n"
-  + "    **Default:** " + (
-      if .value.value == "" or .value.value == []
-      then "*empty*"
-      elif (.value.value | type) == "array"
-      then "`" + (.value.value | join(" ")) + "`"
-      else "`" + (.value.value | tostring) + "`"
-      end)
-  + "\n\n"
-  + (if (.value.aliases | length) > 0
-     then "    **Deprecated alias:** " + (.value.aliases | map("`" + . + "`") | join(", ")) + "\n\n"
-     else ""
-     end)
-) | join("")
diff --git a/doc/manual/generate-options.nix b/doc/manual/generate-options.nix
new file mode 100644
index 0000000000000000000000000000000000000000..7afe279c3738273e2f243eed76d1d51595664971
--- /dev/null
+++ b/doc/manual/generate-options.nix
@@ -0,0 +1,21 @@
+with builtins;
+with import ./utils.nix;
+
+options:
+
+concatStrings (map
+  (name:
+    let option = options.${name}; in
+    "  - `${name}`  \n\n"
+    + concatStrings (map (s: "    ${s}\n") (splitLines option.description)) + "\n\n"
+    + "    **Default:** " + (
+      if option.value == "" || option.value == []
+      then "*empty*"
+      else if isBool option.value
+      then (if option.value then "`true`" else "`false`")
+      else "`" + toString option.value + "`") + "\n\n"
+    + (if option.aliases != []
+       then "    **Deprecated alias:** " + (concatStringsSep ", " (map (s: "`${s}`") option.aliases)) + "\n\n"
+       else "")
+    )
+  (attrNames options))
diff --git a/doc/manual/local.mk b/doc/manual/local.mk
index 297a7341499976e254cc86948bc13fc1c47d42c1..c219153e9d5cf1239fa44666f5c0ee7540eda545 100644
--- a/doc/manual/local.mk
+++ b/doc/manual/local.mk
@@ -24,12 +24,12 @@ $(d)/%.8: $(d)/src/command-ref/%.md
 $(d)/nix.conf.5: $(d)/src/command-ref/conf-file.md
 	$(trace-gen) lowdown -sT man $^ -o $@
 
-$(d)/src/command-ref/nix.md: $(d)/nix.json $(d)/generate-manpage.jq
-	jq -r -f doc/manual/generate-manpage.jq $< > $@
+$(d)/src/command-ref/nix.md: $(d)/nix.json $(d)/generate-manpage.nix $(bindir)/nix
+	$(trace-gen) $(bindir)/nix eval --experimental-features nix-command --impure --raw --expr 'import doc/manual/generate-manpage.nix (builtins.fromJSON (builtins.readFile $<))' > $@
 
-$(d)/src/command-ref/conf-file.md: $(d)/conf-file.json $(d)/generate-options.jq $(d)/src/command-ref/conf-file-prefix.md
-	cat doc/manual/src/command-ref/conf-file-prefix.md > $@
-	jq -r -f doc/manual/generate-options.jq $< >> $@
+$(d)/src/command-ref/conf-file.md: $(d)/conf-file.json $(d)/generate-options.nix $(d)/src/command-ref/conf-file-prefix.md $(bindir)/nix
+	@cat doc/manual/src/command-ref/conf-file-prefix.md > $@
+	$(trace-gen) $(bindir)/nix eval --experimental-features nix-command --impure --raw --expr 'import doc/manual/generate-options.nix (builtins.fromJSON (builtins.readFile $<))' >> $@
 
 $(d)/nix.json: $(bindir)/nix
 	$(trace-gen) $(bindir)/nix __dump-args > $@
@@ -37,9 +37,9 @@ $(d)/nix.json: $(bindir)/nix
 $(d)/conf-file.json: $(bindir)/nix
 	$(trace-gen) env -i NIX_CONF_DIR=/dummy HOME=/dummy $(bindir)/nix show-config --json --experimental-features nix-command > $@
 
-$(d)/src/expressions/builtins.md: $(d)/builtins.json $(d)/generate-builtins.jq $(d)/src/expressions/builtins-prefix.md
-	cat doc/manual/src/expressions/builtins-prefix.md > $@
-	jq -r -f doc/manual/generate-builtins.jq $< >> $@
+$(d)/src/expressions/builtins.md: $(d)/builtins.json $(d)/generate-builtins.nix $(d)/src/expressions/builtins-prefix.md $(bindir)/nix
+	@cat doc/manual/src/expressions/builtins-prefix.md > $@
+	$(trace-gen) $(bindir)/nix eval --experimental-features nix-command --impure --raw --expr 'import doc/manual/generate-builtins.nix (builtins.fromJSON (builtins.readFile $<))' >> $@
 
 $(d)/builtins.json: $(bindir)/nix
 	$(trace-gen) $(bindir)/nix __dump-builtins > $@
diff --git a/doc/manual/utils.nix b/doc/manual/utils.nix
new file mode 100644
index 0000000000000000000000000000000000000000..50150bf3e46ccc97d7b9eebf5081167689452bc4
--- /dev/null
+++ b/doc/manual/utils.nix
@@ -0,0 +1,7 @@
+with builtins;
+
+{
+  splitLines = s: filter (x: !isList x) (split "\n" s);
+
+  concatStrings = concatStringsSep "";
+}