---
title: "Webtrotion Configuration Generator"
slug: webtrotion-configuration-generator
canonical_url: https://nerdymomocat.github.io/posts/webtrotion-configuration-generator/
collection: Notes
published_at: 2025-02-25T00:00:00.000Z
updated_at: 2025-10-29T00:00:00.000Z
tags: 
  - Webtrotion
  - Coding
  - Tools
excerpt: "A single page HTML+CSS+JavaScript file to create the constants config that you can copy paste into your webtrotion setup"
author: "Nerdy Momo Cat"
---

## Navigation Context

- Canonical URL: https://nerdymomocat.github.io/posts/webtrotion-configuration-generator/
- You are here: Home > Posts > Notes > Webtrotion Configuration Generator

### Useful Next Links
- [Home](https://nerdymomocat.github.io/)
- [Bundles](https://nerdymomocat.github.io/collections/bundles/)
- [Logbook](https://nerdymomocat.github.io/collections/logbook/)
- [Notes](https://nerdymomocat.github.io/collections/notes/)
- [Stream](https://nerdymomocat.github.io/collections/stream/)

### Related Content

#### Other Pages Mentioned On This Page
- [Introducing Webtrotion](https://nerdymomocat.github.io/posts/introducing-webtrotion/)

Webtrotion is a single-file configuration system that uses `constants-config.json`. Using LLMs, I created a simple HTML script that generates JSON you can copy directly into your `constants-config.json`.

While this isn't polished, I created it mainly to test an LLM's ability to generate pure HTML rather than defaulting to React for 1000+ line projects. I didn't spend time refining it since I expect limited usage, but it's here if you need it.

/\* Container styling \*/ #config-generator { display: flex; flex-wrap: wrap; gap: 1rem; position: relative; font-family: monospace; max-width: 1200px; margin: auto; padding: 1rem; background: var(--color-bgColor, #ffffff); color: var(--color-textColor, #000); border: 4px solid var(--color-textColor); box-shadow: 8px 8px 0 var(--color-textColor); } /\* Panel styling \*/ #config-generator #config-form, #config-generator #json-preview-container { flex: 1 1 500px; } @media (max-width: 40rem) { #config-generator { flex-direction: column; } #config-generator #config-form, #config-generator #json-preview-container { flex: 1 1 100%; } #config-generator .form-row label { flex: 1 0 200px; } } /\* Header styling \*/ #config-generator h2 { margin-top: 1rem; border-bottom: 2px solid var(--color-accent, #c00); padding-bottom: 0.3rem; } /\* Form sections \*/ #config-generator .form-section { margin-bottom: 1rem; padding: 0.5rem; border: 3px solid var(--color-accent); box-shadow: 4px 4px 0 var(--color-accent-2); border-radius: 0; transition: transform 0.2s; } #config-generator .form-section:hover { transform: translate(-2px, -2px); box-shadow: 6px 6px 0 var(--color-accent-2); } /\* Form rows and inputs \*/ #config-generator .form-row { display: flex; flex-wrap: wrap; align-items: center; margin-bottom: 0.5rem; } #config-generator .form-row label { flex: 1 0 100px; margin-right: 0.5rem; } #config-generator .form-row input\[type="text"\], #config-generator .form-row input\[type="url"\], #config-generator .form-row input\[type="number"\] { flex: 2 0 200px; padding: 0.5rem; border: 2px solid var(--color-textColor); border-radius: 0; margin-right: 0.5rem; background: var(--color-bgColor); transition: all 0.2s; } #config-generator .form-row input\[type="text"\]:focus, #config-generator .form-row input\[type="url"\]:focus, #config-generator .form-row input\[type="number"\]:focus { outline: none; box-shadow: 4px 4px 0 var(--color-accent); transform: translate(-2px, -2px); } #config-generator .form-row input\[type="checkbox"\] { transform: scale(1.2); margin-right: 0.6rem; width: 1rem; height: 1rem; border: 2px solid var(--color-textColor); border-radius: 0; } /\* Color pickers and groups \*/ #config-generator .color-picker-group { display: inline-flex; align-items: center; margin-right: 1rem; } #config-generator .color-picker-group input\[type="color"\] { flex-shrink: 0; width: 2em; height: 2em; padding: 0; border: 2px solid var(--color-textColor); border-radius: 0; margin-right: 0.3rem; vertical-align: middle; -webkit-appearance: none; -moz-appearance: none; appearance: none; box-shadow: 2px 2px 0 var(--color-accent); } #config-generator .color-picker-group input\[type="text"\] { width: 80px; /\* Set fixed width for color text inputs \*/ flex: none; /\* Override flex behavior \*/ min-width: 0; /\* Allow shrinking below default min-width \*/ vertical-align: middle; font-size: xx-small; margin: 2px 0px; } #config-generator .color-group { flex: 1; display: flex; align-items: center; } #config-generator .color-group span { margin-right: 0.3rem; font-weight: bold; } /\* Redirects row \*/ #config-generator .redirect-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; } #config-generator .redirect-row input\[type="text"\] { flex: 1; padding: 0.3rem; border: 1px solid var(--color-accent-2, #000); } #config-generator .redirect-row button.delete-row { background: var(--color-bgColor) !important; color: #fff; border: 2px solid var(--color-textColor) !important; padding: 0.1rem; cursor: pointer; box-shadow: 2px 2px 0 var(--color-textColor) !important; } #config-generator button#add-redirect { background: var(--color-accent-2, #333) !important; color: #fff; border: none; padding: 0.2rem; cursor: pointer; margin-top: 0.5rem; } /\* JSON preview panel \*/ #config-generator #json-preview { white-space: pre-wrap; padding: 1rem; border: 3px solid var(--color-textColor); height: 100%; overflow-y: auto; font-family: monospace; box-shadow: 4px 4px 0 var(--color-accent-2); border-radius: 0; } /\* Floating Buttons \*/ #config-generator .floating-buttons { position: absolute; bottom: 1rem; right: 1rem; display: flex; gap: 0.5rem; } #config-generator .floating-buttons button { background: var(--color-accent, #c00); color: #fff; border: none; padding: 0.5rem 1rem; cursor: pointer; } /\* Toast \*/ #config-generator #toast { position: absolute; bottom: 1rem; right: 1rem; background: var(--color-accent, #c00); color: #fff; padding: 0.5rem 1rem; border-radius: 3px; display: none; border: 3px solid var(--color-textColor); box-shadow: 4px 4px 0 var(--color-textColor); font-weight: bold; } @media (max-width: 40rem) { #config-generator { grid-template-columns: 1fr; } } /\* Hidden class \*/ #config-generator .hidden { display: none; } /\* Required field star \*/ #config-generator .required::after { content: '\*'; color: #ff0000; margin-left: 4px; font-size: 1.2rem; font-weight: bold; } /\* Global button styles inside #config-generator \*/ #config-generator button { border: 3px solid var(--color-textColor) !important; background: var(--color-accent) !important; color: var(--color-bgColor) !important; padding: 0.5rem !important; font-weight: bold !important; font-size: 1rem !important; box-shadow: 4px 4px 0 var(--color-textColor) !important; transition: all 0.2s !important; border-radius: 0 !important; } #config-generator button:hover { transform: translate(-2px, -2px); box-shadow: 6px 6px 0 var(--color-textColor) !important; } #config-generator button:active { transform: translate(2px, 2px); box-shadow: 0 0 0 var(--color-textColor) !important; } /\* Fieldset and legend \*/ #config-generator fieldset { border: 2px solid var(--color-accent-2) !important; border-radius: 0; box-shadow: 4px 4px 0 var(--color-accent); } #config-generator legend { padding: 0 0.5rem; font-weight: bold; color: var(--color-accent); } /\* Section headers within form sections \*/ #config-generator .form-section h3 { color: var(--color-accent); border-bottom: 3px solid var(--color-accent); padding-bottom: 0.5rem; margin-bottom: 1rem; font-weight: bold; font-size: 1.2rem; }

Database ID: 

Data Source ID: 

Author: 

### Tracking

Google Analytics

Use GA: 

Public GA Tracking ID: 

Umami

Use Umami: 

Data Website ID: 

Self-hosted: 

Self-hosted Umami URL: 

Google Search Console HTML Tag: 

### Socials

Email: 

GitHub: 

Google Scholar: 

Semantic Scholar: 

DBLP: 

Substack: 

Facebook: 

Twitter: 

Threads: 

Instagram: 

Mastodon: 

Bluesky: 

Calendar: 

Ko-fi: 

Buy Me A Coffee: 

This GitHub Repo: 

Custom Domain: 

Base Path: 

### Giscus

Use Giscus: 

Data Repo: 

Data Repo ID: 

Data Category: 

Data Category ID: 

Data Mapping: 

Data Input Position: 

Data Reactions Enabled: 

### Bluesky Comments

Show Comments From Bluesky: 

Auto Search For Match

Turn on Auto Search: 

Author: 

Echo Feed Emoji: 

### Webmention

Use Webmentions: 

Webmention API Key: 

Webmention Link: 

### Shortcodes

HTML Render: 

HTML Inject: 

Alt Text Start: 

Alt Text End: 

Expressive Code Start: 

Expressive Code End: 

Shiki Transform: 

Table Shortcode: 

### References

Site Links In Page: 

External Links In Page: 

Media/File Links In This Page: 

Links To This Page: 

Popovers: 

Footnotes: 

Recent Posts On Home Page: 

Full Width Social Embeds: 

Optimize Images: 

Number of Posts Per Page: 

Enable Lightbox: 

Menu Pages Collection: 

Heading Blocks (comma separated): 

Full Preview Collections (comma separated): 

Hide Underscore Slugs In Lists: 

Home Page Slug: 

All Footnotes Page Slug: 

### OG Setup

Columns: 

Excerpt: 

Title Font URL: 

Footnote Font URL: 

### Theme

Background:

Light:

 

Dark:

 

Text:

Light:

 

Dark:

 

Link:

Light:

 

Dark:

 

Accent:

Light:

 

Dark:

 

Accent-2:

Light:

 

Dark:

 

Quote:

Light:

 

Dark:

 

Fontfamily Google Fonts

Combined URL: 

Sans Font Name: 

Serif Font Name: 

Mono Font Name: 

### Redirects

Add Row

Generate JSON Copy JSON

Database ID is required!

Generated JSON will appear here...

import \* as prettier from "https://unpkg.com/prettier@3.5.2/standalone.mjs"; import \* as prettierPluginBabel from "https://unpkg.com/prettier@3.5.2/plugins/babel.mjs"; import \* as prettierPluginEstree from "https://unpkg.com/prettier@3.5.2/plugins/estree.mjs"; // Helper: show toast function showToast(message) { const toast = document.getElementById("toast"); toast.textContent = message; toast.style.display = "block"; setTimeout(() => { toast.style.display = "none"; }, 3000); } // Helper: convert hex color to space-separated RGB string function hexToRgbString(hex) { if (!hex || !/^#(\[A-Fa-f0-9\]{3}){1,2}$/.test(hex)) { return ""; // Return empty for invalid hex format } let c = hex.substring(1).split(''); if (c.length === 3) { c = \[c\[0\], c\[0\], c\[1\], c\[1\], c\[2\], c\[2\]\]; } c = '0x' + c.join(''); const r = (c >> 16) & 255; const g = (c >> 8) & 255; const b = c & 255; return \`${r} ${g} ${b}\`; } // Color picker synchronization document.querySelectorAll(".color-picker-group").forEach((group) => { const colorInput = group.querySelector('input\[type="color"\]'); const textInput = group.querySelector('input\[type="text"\]'); colorInput.addEventListener("input", () => { textInput.value = colorInput.value; }); textInput.addEventListener("input", () => { if (/^#(\[0-9A-Fa-f\]{3}|\[0-9A-Fa-f\]{6})$/.test(textInput.value.trim())) { colorInput.value = textInput.value.trim(); } }); }); // Redirects: dynamic rows (no pre-added row) const redirectsContainer = document.getElementById("redirects-container"); function addRedirectRow(sourceVal = "", targetVal = "") { const row = document.createElement("div"); row.className = "redirect-row"; const source = document.createElement("input"); source.type = "text"; source.placeholder = "Source path"; source.value = sourceVal; const target = document.createElement("input"); target.type = "text"; target.placeholder = "Target path"; target.value = targetVal; const del = document.createElement("button"); del.textContent = "🗑️"; del.type = "button"; del.className = "delete-row"; del.addEventListener("click", () => { redirectsContainer.removeChild(row); }); row.appendChild(source); row.appendChild(target); row.appendChild(del); redirectsContainer.appendChild(row); } document.getElementById("add-redirect").addEventListener("click", (e) => { e.preventDefault(); addRedirectRow(); }); // Toggle GA Tracking row document.getElementById("use-ga").addEventListener("change", (e) => { document.getElementById("ga-tracking-row").classList.toggle("hidden", !e.target.checked); }); // Toggle Umami container and self-hosted URL container document.getElementById("use-umami").addEventListener("change", (e) => { document.getElementById("umami-container").classList.toggle("hidden", !e.target.checked); }); document.getElementById("self-hosted").addEventListener("change", (e) => { document.getElementById("self-hosted-url-container").classList.toggle("hidden", !e.target.checked); }); // Toggle Auto Search for Bluesky Comments document.getElementById("show-comments-from-bluesky").addEventListener("change", (e) => { document.getElementById("auto-search-container").classList.toggle("hidden", !e.target.checked); }); document.getElementById("turn-on-auto-search").addEventListener("change", (e) => { document.getElementById("auto-search-extra-container").classList.toggle("hidden", !e.target.checked); }); // Toggle Giscus fields container document.getElementById("use-giscus").addEventListener("change", (e) => { document.getElementById("giscus-fields-container").classList.toggle("hidden", !e.target.checked); }); // Toggle Webmention fields container document.getElementById("use-webmentions").addEventListener("change", (e) => { document.getElementById("webmention-fields-container").classList.toggle("hidden", !e.target.checked); }); // Generate JSON document.getElementById("generate-json").addEventListener("click", async () => { const databaseId = document.getElementById("database-id").value.trim(); if (!databaseId) { showToast("Database ID is required!"); return; } // Helper function to handle font name space replacement const processSpaces = value => value .replace(/\\\\\\\\ /g, ' ') // Step 1: remove existing escapes .replace(/ /g, '\\\\ '); // Step 2: escape all spaces const config = { "database-id": databaseId, "data-source-id": document.getElementById("data-source-id").value.trim(), author: document.getElementById("author").value.trim(), tracking: { "google-analytics": { "use-ga": document.getElementById("use-ga").checked, "public-ga-tracking-id": document.getElementById("use-ga").checked ? document.getElementById("public-ga-tracking-id").value.trim() : "" }, umami: { "use-umami": document.getElementById("use-umami").checked, "data-website-id": document.getElementById("use-umami").checked ? document.getElementById("data-website-id").value.trim() : "", "self-hosted": document.getElementById("use-umami").checked ? document.getElementById("self-hosted").checked : false, "self-hosted-umami-url": document.getElementById("use-umami").checked && document.getElementById("self-hosted").checked ? document.getElementById("self-hosted-umami-url").value.trim() : "" } }, "google-search-console-html-tag": document.getElementById("google-search-console-html-tag").value.trim(), socials: { email: document.getElementById("social-email").value.trim(), github: document.getElementById("social-github").value.trim(), googlescholar: document.getElementById("social-googlescholar").value.trim(), semanticscholar: document.getElementById("social-semanticscholar").value.trim(), dblp: document.getElementById("social-dblp").value.trim(), substack: document.getElementById("social-substack").value.trim(), facebook: document.getElementById("social-facebook").value.trim(), twitter: document.getElementById("social-twitter").value.trim(), threads: document.getElementById("social-threads").value.trim(), instagram: document.getElementById("social-instagram").value.trim(), mastodon: document.getElementById("social-mastodon").value.trim(), bluesky: document.getElementById("social-bluesky").value.trim(), calendar: document.getElementById("social-calendar").value.trim(), kofi: document.getElementById("social-kofi").value.trim(), "buy-me-a-coffee": document.getElementById("social-buy-me-a-coffee").value.trim(), "this-github-repo": document.getElementById("social-this-github-repo").value.trim() }, "custom-domain": document.getElementById("custom-domain").value.trim(), "base-path": document.getElementById("base-path").value.trim(), giscus: { "data-repo": document.getElementById("use-giscus").checked ? document.getElementById("giscus-data-repo").value.trim() : "", "data-repo-id": document.getElementById("use-giscus").checked ? document.getElementById("giscus-data-repo-id").value.trim() : "", "data-category": document.getElementById("use-giscus").checked ? document.getElementById("giscus-data-category").value.trim() : "", "data-category-id": document.getElementById("use-giscus").checked ? document.getElementById("giscus-data-category-id").value.trim() : "", "data-mapping": document.getElementById("use-giscus").checked ? document.getElementById("giscus-data-mapping").value.trim() : "", "data-input-position": document.getElementById("use-giscus").checked ? document.getElementById("giscus-data-input-position").value.trim() : "", "data-reactions-enabled": document.getElementById("use-giscus").checked ? document.getElementById("giscus-data-reactions-enabled").checked : false }, "bluesky-comments": { "show-comments-from-bluesky": document.getElementById("show-comments-from-bluesky").checked, "auto-search-for-match": { "turn-on-auto-search": document.getElementById("turn-on-auto-search") ? document.getElementById("turn-on-auto-search").checked : false, author: document.getElementById("turn-on-auto-search") && document.getElementById("turn-on-auto-search").checked ? document.getElementById("auto-search-author").value.trim() : "", "echo-feed-emoji": document.getElementById("turn-on-auto-search") && document.getElementById("turn-on-auto-search").checked ? document.getElementById("echo-feed-emoji").value.trim() : "" } }, references: { "site-links-in-page": document.getElementById("site-links-in-page").checked, "external-links-in-page": document.getElementById("external-links-in-page").checked, "media-and-file-links-in-this-page": document.getElementById("media-and-file-links-in-this-page").checked, "links-to-this-page": document.getElementById("links-to-this-page").checked, popovers: document.getElementById("popovers").checked, footnotes: document.getElementById("footnotes").checked }, shortcodes: { "html-render": document.getElementById("html-render").value, "html-inject": document.getElementById("html-inject").value, "alt-text": { start: document.getElementById("alt-text-start").value, end: document.getElementById("alt-text-end").value }, "expressive-code": { start: document.getElementById("expressive-code-start").value, end: document.getElementById("expressive-code-end").value }, "shiki-transform": document.getElementById("shiki-transform").value, table: document.getElementById("table-shortcode").value }, "recent-posts-on-home-page": document.getElementById("recent-posts-on-home-page").checked, theme: { colors: { bg: { light: hexToRgbString(document.getElementById("bg-light-text").value.trim()), dark: hexToRgbString(document.getElementById("bg-dark-text").value.trim()) }, text: { light: hexToRgbString(document.getElementById("text-light-text").value.trim()), dark: hexToRgbString(document.getElementById("text-dark-text").value.trim()) }, link: { light: hexToRgbString(document.getElementById("link-light-text").value.trim()), dark: hexToRgbString(document.getElementById("link-dark-text").value.trim()) }, accent: { light: hexToRgbString(document.getElementById("accent-light-text").value.trim()), dark: hexToRgbString(document.getElementById("accent-dark-text").value.trim()) }, "accent-2": { light: hexToRgbString(document.getElementById("accent2-light-text").value.trim()), dark: hexToRgbString(document.getElementById("accent2-dark-text").value.trim()) }, quote: { light: hexToRgbString(document.getElementById("quote-light-text").value.trim()), dark: hexToRgbString(document.getElementById("quote-dark-text").value.trim()) } }, "fontfamily-google-fonts": { "combined-url": document.getElementById("combined-url").value.trim(), "sans-font-name": processSpaces(document.getElementById("sans-font-name").value.trim()), "serif-font-name": processSpaces(document.getElementById("serif-font-name").value.trim()), "mono-font-name": processSpaces(document.getElementById("mono-font-name").value.trim()) } }, webmention: { "webmention-api-key": document.getElementById("use-webmentions").checked ? document.getElementById("webmention-api-key").value.trim() : "", "webmention-link": document.getElementById("use-webmentions").checked ? document.getElementById("webmention-link").value.trim() : "" }, "number-of-posts-per-page": parseInt(document.getElementById("number-of-posts-per-page").value, 10) || 10, "enable-lightbox": document.getElementById("enable-lightbox").checked, "menu-pages-collection": document.getElementById("menu-pages-collection").value.trim(), "heading-blocks": document.getElementById("heading-blocks").value.split(",").map(s => s.trim()), "full-preview-collections": document.getElementById("full-preview-collections").value.split(",").map(s => s.trim()), "hide-underscore-slugs-in-lists": document.getElementById("hide-underscore-slugs-in-lists").checked, "home-page-slug": document.getElementById("home-page-slug").value.trim(), "all-footnotes-page-slug": document.getElementById("all-footnotes-page-slug").value.trim(), "og-setup": { columns: parseInt(document.getElementById("og-columns").value, 10) || 1, excerpt: document.getElementById("og-excerpt").checked, "title-font-url": document.getElementById("og-title-font-url").value.trim(), "footnote-font-url": document.getElementById("og-footnote-font-url").value.trim() }, "full-width-social-embeds": document.getElementById("full-width-social-embeds").checked, "optimize-images": document.getElementById("optimize-images").checked, redirects: {} }; // Gather redirects rows const redirects = {}; document.querySelectorAll("#redirects-container .redirect-row").forEach((row) => { const inputs = row.querySelectorAll('input\[type="text"\]'); if (inputs\[0\].value.trim() && inputs\[1\].value.trim()) { redirects\[inputs\[0\].value.trim()\] = inputs\[1\].value.trim(); } }); config\["redirects"\] = redirects; const jsonOutput = JSON.stringify(config); const formattedJSON = await prettier.format(jsonOutput, { parser: "json", plugins: \[prettierPluginBabel, prettierPluginEstree\], useTabs: true, printWidth: 100, semi: true, singleQuote: false, tabWidth: 2, }); document.getElementById("json-preview").textContent = formattedJSON; document.getElementById("copy-json").style.display = "inline-block"; }); // Copy JSON to clipboard document.getElementById("copy-json").addEventListener("click", () => { const jsonText = document.getElementById("json-preview").textContent; navigator.clipboard.writeText(jsonText).then(() => { showToast("JSON copied to clipboard!"); }); });