Research on markata-go, the Go rewrite of the Python markata static site generator, to support migrating pype.dev. All sources are primary: the GitHub repo (README, docs/, spec/, releases, issues, commit history), pkg.go.dev, and Waylon’s own sites (waylonwalker.com now runs on markata-go; go.waylonwalker.com is the deployment).
Repo state as of research date: v0.13.0, “Beta (0.x) - API may change between minor versions”, created 2026-01-22, 2 stars, 51 open issues, essentially solo-maintained by WaylonWalker (853 contributions) + dependabot. Source, pkg.go.dev
Important caveat: the project is “open source but not open contribution” — PRs are not reviewed or merged; bugs/feature requests go through issues only. CONTRIBUTING.md
1. Installation + CLI #
Install options #
# One-liner (Linux/macOS)
curl -sSL https://raw.githubusercontent.com/WaylonWalker/markata-go/main/install.sh | bash
# (also published at https://waylonwalker.github.io/markata-go/install.sh)
# jpillora installer
curl -sL https://i.jpillora.com/WaylonWalker/markata-go | bash
# eget
eget WaylonWalker/markata-go
# mise (from GitHub releases)
mise use -g github:WaylonWalker/markata-go
# Go install (Go 1.22+; v0.13.0 toolchain reports Go 1.26)
go install github.com/WaylonWalker/markata-go/cmd/markata-go@latest
# or pinned: ...@v0.13.0
Distributed as a single dependency-free binary. markata-go update is a built-in self-update command. v0.5.0 release notes
CLI commands #
Global flags: -c/--config, -m/--merge-config (repeatable), -o/--output, -q/--quiet, -v/--verbose, --color/--no-color, --log-format, --no-input.
| Command | What it does |
|---|---|
build |
Full build. Flags: --clean, --dry-run, --fast (skip minify/CSS purge/Tailwind/Pagefind), --benchmark-json, --benchmark-detailed |
serve |
Dev server + live reload. -p/--port, -H/--host, --no-reload, --no-watch, --open, --fast |
new [title] |
Create post. --dir, --draft, --tags; interactive mode via charmbracelet/huh. Built-in content templates: post, page, docs, article, note, photo, video, link, quote, guide, inline, contact, author |
init |
Scaffold a new site |
config show/get/set/validate/init |
Resolved config in YAML/JSON/TOML; dot-notation get (feeds.defaults.items_per_page); set preserves formatting |
migrate |
Python-markata migration tool (see §2) |
tui |
k9s-style interactive TUI: posts/tags/feeds views, / filter expressions |
lsp / lsp setup --editor helix |
Language server: wikilink completion, frontmatter completion/hover, diagnostics |
list |
List posts/tags/feeds (`–format table |
search |
Content search: --filter, --fields, --fuzzy, --limit |
lint |
Frontmatter/content checks with fixers (incl. natural-language datetime fixing) |
explain <topic> |
Built-in docs, e.g. markata-go explain feeds |
benchmark |
Build benchmarks + pprof |
import |
Import external content (PESOS pattern) |
encryption |
check, generate-password, encrypt-posts subcommands |
aesthetic |
Theme/aesthetic tooling |
agent |
Install/doctor bundled agent-skill integrations (agent doctor drift-checks installed skill) |
blogroll |
Blogroll commands incl. blogroll update |
version |
--short supported |
2. Config format #
File discovery #
markata-go does not auto-read markata.toml. It looks for (first found wins): ./markata-go.toml, ./markata-go.yaml, ./markata-go.yml, ./markata-go.json, .markata-go.*, ~/.config/markata-go/config.toml, or --config/--merge-config. configuration.md, cli.md
All config is namespaced under [markata-go] (also accepts YAML/JSON). Env overrides use MARKATA_GO_ prefix (e.g. MARKATA_GO_OUTPUT_DIR=dist). Config include = [...] supports splitting config across files; [[markata-go.feeds]] merges by slug. configuration.md
Core schema ([markata-go]) #
output_dir (default "output"), url, title, description, author, license, assets_dir (default "static"), templates_dir (default "templates"), hooks (default ["default"]), disabled_hooks, concurrency (0=auto). PWA-ish fields also present in the schema: icon, lang, theme_color, background_color, start_url, display, short_name, site_name, repo_url, repo_branch, twitter_card, twitter_handle/twitter_site, rss_description, site_version. configuration.md + this repo’s own markata-go.toml which already carries them.
Other sections seen in docs: [markata-go.glob] (patterns default ["pages/**/*.md","posts/**/*.md"], use_gitignore, slug_mode, slug_rules), [markata-go.markdown] + [markata-go.markdown.highlight], [markata-go.wikilinks] (warn_broken, strict_wikilinks under [markata-go]), [markata-go.theme], [markata-go.feed_defaults], [[markata-go.feeds]], [[markata-go.nav]] (label, url, external), [markata-go.components.nav], [markata-go.sidebar]/[[markata-go.sidebar.nav]], [markata-go.encryption], [embeds], [markata-go.head] + [[markata-go.head.alternate_feeds]], [markata-go.blogroll], [content_templates]. configuration.md
Migration tooling — markata-go migrate #
markata-go migrate # full analysis
markata-go migrate --dry-run
markata-go migrate config -i markata.toml -o markata-go.toml # also accepts pyproject.toml
markata-go migrate filter "templateKey in ['blog', 'til']" # check a filter expression
markata-go migrate templates [path] # check pongo2 compat
Exit codes: 0 clean, 1 warnings, 2 incompatibilities, 3 error. There is a docs/guides/migration.md in the repo.
Known bug: the migrate tool has emitted invalid TOML (nested [markata-go.description.description] tables from a [markata.auto_description.description]-style Python config) — WaylonWalker/waylonwalker.com issue #20, supposedly fixed by fix(migrate): generate valid TOML for string fields (#208) in v0.5.0. Always run markata-go config validate after migrating.
This repo’s markata-go.toml already exists and appears to be migrate-tool output — feed filter strings carried over verbatim, page_size preserved.
3. Markdown features #
Engine: goldmark (deps include yuin/goldmark, goldmark-highlighting, goldmark-emoji, goldmark-figure, goldmark-anchor, CJK ext, abhg.dev/goldmark/*). pkg.go.dev deps, v0.10.0 changelog
Config: [markata-go.markdown] extensions = ["tables","strikethrough","autolinks","tasklist"]; spec also lists typographer (smart quotes, default true), definition_list, footnote. configuration.md, spec/CONTENT.md
- Admonitions
!!!: supported via a goldmark extension in the render stage. Verified against source: types are a hardcoded whitelist —note, info, tip, hint, success, warning, caution, important, danger, error, bug, example, quote, abstract, aside, seealso, reminder, attention, todo, settings, vsplit, chat, chat-reply(admonitionTypesmap inpkg/plugins/admonitions.go). Unknown types are NOT parsed — they render as literal paragraphs.!!! scripture/!!! background-thoughtdo not work. Confirmed live:???+ successin this repo rendered<details class="admonition success" open>while!!! scripturerendered literal text. No config option for custom types exists. plugins.md, markdown.md,pkg/plugins/admonitions.go:17 - Collapsible
???/???+: supported (???collapsed,???+open), renders native<details>/<summary>. markdown.md - Wikilinks:
[[slug]],[[slug|text]],[[slug#fragment]],[[slug#frag|text]]; case-insensitive slug lookup, thenaliasesfrontmatter (synonyms:alias,handle,handles); unresolved → rendered literal +wikilink_warningsinpost.Extra;strict_wikilinks = truefails the build.[[ slug ]]WITH SPACES WORKS —wikilinks.go:151doesstrings.TrimSpace(groups[1]), verified against this repo’s build output (spaced daily-note links resolve with full hover-preview data attributes). plugins.md,pkg/plugins/wikilinks.go:151 - Attrs
{#id .class}: supported — spec shows{.shadow .bordered},*em*{.accent},`code`{.inline-code},[[link]]{.wikilink-pill},{#hero-image}. spec/CONTENT.md - Code highlighting: goldmark-highlighting (chroma);
[markata-go.markdown.highlight] theme = "dracula",line_numbers. markdown.md - Tables: via
tablesextension. Typographer/smart quotes:typographer = truedefault in spec. configuration.md, spec/CONTENT.md - Heading anchors/permalinks:
heading_anchorsplugin +goldmark-anchor; TOC plugin (toc,post.Extra.toc_html). plugins.md - Markdown containers: generic
::: namefenced divs with{#id .class data-attr}— a possible escape hatch for admonition-like custom blocks (no title/collapse semantics). markdown-containers.md - Extras present: mermaid (incl. lightbox/pan-zoom, CLI/Chromium pre-render), chartjs fenced blocks,
contribution-graphfenced block, csv_fence, glossary, webawesome components, Obsidian-style<!-- embed not found: attachment --> ![[attachment]]embeds, oEmbed external link cards. plugins.md, v0.7.0
4. Frontmatter #
frontmatter.md, plugins.md load section, spec/DATA_MODEL.md
Built-in fields: title, slug, date (+ modified, and date aliases for cross-SSG compat), published (default false), draft (never rendered), skip, tags, description (auto-generated if absent), template (default post.html; also object form {default:, card:, feed:}), aliases, authors/author/writer, image/video/cover_image/thumbnail/og_image/hero_video, private, jinja (bool gate for jinja_md), config_overrides, css_class. Unknown fields land in Extra and are filterable (Extra.series == 'X').
publishedsemantics match Python markata:false= rendered but excluded from feeds/sitemap/RSS (“shadow page”);true= included.draft: true= not rendered at all.templateKey: still a real field — filters liketemplateKey in ['blog-post']are used in migrate docs, andencryption.private_tagsmatches against templateKey as well as tags. cli.md migrate section, v0.8.0 changelog- Slugs: auto-generated from filename by default (
flatmode): lowercase, spaces→hyphens, strip special chars, collapse hyphens.[markata-go.glob] slug_mode = "flat" | "path"plus per-prefix[[markata-go.glob.slug_rules]](longest prefix wins) — the “hybrid” v0.9.0 capability.index.md/README.mdmap to directory root;slug: ""or/= homepage; frontmatterslugalways wins; nestedslug: "a/b"allowed; leading/trailing slashes stripped. configuration.md, spec/DATA_MODEL.md - Dates: accepts RFC3339,
YYYY-MM-DD HH:MM:SS,YYYY-MM-DD, US01/15/2024, European15-01-2024,January 15, 2024,Jan 15, 2024. plugins.md
5. Feeds + filter language #
Filters are Python-like expressions — the same syntax family as Python markata. This site’s existing filters port verbatim. frontmatter.md, spec/DATA_MODEL.md
filter = "published == True"
filter = "draft == False"
filter = "'go' in tags"
filter = "date >= '2024-01-01' and date < '2025-01-01'"
filter = "slug.startswith('tutorials/')"
filter = "template == 'tutorial.html'"
filter = "Extra.series == 'Web Scraping with Go'"
filter = "published == True and ('go' in tags or 'python' in tags)"
filter = "not draft"
Operators: == != > >= < <= in and or not; string methods startswith, lower().contains(...); built-ins today, now, True, False, None; date(2024,1,1) constructor in spec. (Python markata extras timedelta/parse not confirmed — check migrate filter.) Note the TUI/CLI search uses a slightly different dialect (tags contains "go", title startswith "How").
Feed config #
[markata-go.feed_defaults]
items_per_page = 10
orphan_threshold = 3
[markata-go.feed_defaults.formats]
html = true
rss = true
atom = false
json = false
[markata-go.feed_defaults.templates]
html = "feed.html"
card = "card.html"
[[markata-go.feeds]]
slug = "blog" # slug = "" → homepage index.html
title = "Blog"
description = "All blog posts"
filter = "published == True"
sort = "date" # or "Extra.series_order" etc.
reverse = true
items_per_page = 10
[markata-go.feeds.formats]
html = true
rss = true
atom = true
json = true
Formats per feed: html → /{slug}/index.html (paginated), rss → /{slug}/rss.xml, atom → atom.xml, json → feed.json, markdown → index.md, text → index.txt, plus a sitemap per-feed format. Pagination via items_per_page/page_size, orphan_threshold. auto_feeds generates per-tag feeds automatically. pkg.go.dev, v0.10.0 changelog
6. Templates #
Engine: pongo2 (“Jinja2-like”) — {{ }}, {% %} syntax, |safe etc. pkg.go.dev
Lookup order for post.template: project templates/ dir → theme templates → default theme fallback. Missing template → raw article_html. frontmatter.md, plugins.md
Template context: post, body (= post.ArticleHTML), config, core; in jinja_md content templates also posts, filter, map, today, now. Field access is Go-style exported names in examples (post.Title, post.Date.Format, post.Extra.toc_html) — but template-variable casing has been a moving target (documented in agent-skill commit c7923e3); pongo2 generally resolves case-insensitively. Custom filters: media_url, is_video, length/first/last/sort/reverse/selectattr/map/join/default/upper/lower/title, forloop vars. Template helpers render_feed/feed_posts exist. plugins.md, frontmatter.md media fields, spec/CONTENT.md
{% if post.Extra.toc_html %}<aside>{{ post.Extra.toc_html|safe }}</aside>{% endif %}
{% for p in filter("published == true")[:5] %}- {{ p.Title }}{% endfor %}
{% with post.image|media_url:post.video as m %}{% if m|is_video %}<video src="{{ m }}">{% endif %}{% endwith %}
jinja_md plugin = jinja-in-markdown equivalent; gated per-post by jinja: true frontmatter. plugins.md, spec/CONTENT.md
Migration note: markata-go migrate templates checks pongo2 compat, but Jinja2↔pongo2 differences (e.g. {% with %}, macro support gaps, different autoescape behavior) mean the 30 templates in templates/ need manual review. Verified live: templates/base_head.html {% include "head.html" %} fails (file absent — Python markata may have supplied it) and poisons the whole project-template chain → 697 parse errors → every page falls back to built-in theme.
7. Feature parity vs this site’s Python plugins #
| Python markata feature/plugin | markata-go equivalent | Source |
|---|---|---|
plugins.password_protection (client-side decrypt) |
encryption plugin + CLI. [markata-go.encryption], private_tags map tag/templateKey→key name, keys from env vars, enforce_strength, min_password_length, default_key; markata-go encryption check/generate-password/encrypt-posts; private: true frontmatter + robots.txt; encrypted cards on feed pages; plaintext-leak sealing. Configured in this repo’s markata-go.toml (protected-post = "default", key via MARKATA_GO_ENCRYPTION_KEY_DEFAULT); both protected posts verified encrypted in markout-go. |
v0.7.0, v0.8.0, encryption.md |
plugins.wikilinks_hover (disabled here) |
wikilink_hover — built-in, enabled by default since v0.7.0; renders data-title/data-description/data-date on .wikilink anchors. Verified in this site’s build output. |
v0.7.0 |
plugins.md_video (![]()→<video>) |
Built-in md_video plugin (same name) exists in markata-go; also youtube plugin (URL-on-own-line → lite/nocookie embed), embeds (oEmbed, Obsidian ` |
|
![[file]]), is_video/media_url` filters. |
plugins.md | |
plugins.analytics (disabled here) |
stats plugin (post/feed/site stats), contribution-graph fenced block, chartjs blocks; analytics guide at docs/guides/analytics.md. |
plugins.md |
plugins.build_thoughts (custom: builds thoughts feed from external API) |
No equivalent. import command only does RSS/Atom/JSONFeed — thoughts API has no feed endpoint (verified: /feed.json → 404). Port as pre-build script generating pages/thoughts/*.md (fix scripts/sync-thoughts.py — wrong output dir, no frontmatter), or keep calling it before builds. |
this repo plugins/build_thoughts.py, scripts/sync-thoughts.py |
markata.plugins.prevnext (disabled here) |
prevnext built-in (Collect stage). |
plugins.md |
markata.plugins.publish_source (enabled here — serves .md/.txt sources) |
Feed markdown/text output formats cover per-feed sources; per-post .md source publishing unconfirmed. |
pkg.go.dev feed formats |
markata.plugins.service_worker (disabled here) |
Not in built-in plugin list. Probably absent. | plugins.md |
markata.plugins.manifest / PWA fields |
PWA config keys exist (theme_color, start_url, display, short_name…); a dedicated manifest.json writer not confirmed in plugin list. |
this repo’s markata-go.toml, configuration.md |
| RSS | rss/atom/jsonfeed write-stage plugins + per-feed formats + head alternate_feeds |
plugins.md |
| Sitemap | sitemap plugin + per-feed sitemap format |
plugins.md |
| 404 page | Built-in 404 mechanism w/ slug suggestions + live-search fallback (v0.7.0); didyoumean-style suggestions |
v0.7.0 |
| Covers / OG images | structured_data (JSON-LD/OG/Twitter), og_image frontmatter, themed OG card templates, og_image_service screenshot-based OG images |
plugins.md, v0.7.0 |
| jinja_md | jinja_md built-in (jinja: true frontmatter) |
plugins.md |
| redirects | redirects write plugin |
plugins.md |
markata summary TUI counts |
markata-go tui + list/search cover similar ground |
cli.md |
heading_link permalinks |
heading_anchors built-in |
plugins.md |
| Search | pagefind built-in (skipped by --fast); optional bleve/searchcraft work in flight |
plugins.md |
| Bundled agent skill for site repos | markata-go agent install ships a markata-go-site skill (SKILL.md + topics/reference/examples) |
spec/spec/AGENTS.md, markata-go agent --help |
| Extras not in Python version | tailwind CDN automation, cdn_assets vendoring, mermaid, chartjs, glossary, series, webmentions, blogroll/reader, garden view w/ knowledge graph, mentions, link_collector/link graph, keyboard nav, view transitions, random_post, image optimization, LSP, serve control center | plugins.md, release notes |
8. Extensibility #
- Built-in plugin set is large (30+) and configurable via
hooks/disabled_hooks(hooks = ["default", "python_docs"]style). plugins.md - True external plugins: Go-only and effectively compile-time.
pkg/plugins/registry.goregisters built-ins. The practical paths: fork + recompile (we already build from source viago install), or template/jinja_md + config for most needs. There’s adocs/guides/plugin-development.mdand an agent-skill topicplugin-creation.md. spec/PLUGINS.md, CONTRIBUTING.md - Policy: PRs not accepted — so a needed feature means a fork or an issue request. CONTRIBUTING.md
- Escape hatches:
jinja_mdfor computed content,config_overridesper post,importcommand, feedmarkdown/textoutputs,::: namecontainers, env-var-driven scripts pre-build.
9. Development pace & maturity #
- Repo created 2026-01-22; releases: v0.4.0 (2026-01-24), v0.5.0 (~01-25), v0.6.0 (01-31), v0.7.0 (02-19), v0.8.0 (02-21), v0.9.0, v0.10.0 (04-20), … v0.13.0 (2026-10-02) — roughly monthly minors, hundreds of commits each (v0.9→v0.10 = 772 commits). pkg.go.dev versions, releases
- Self-described “Beta (0.x) — API may change between minor versions”; release notes explicitly warn to smoke-test custom templates/themes on upgrade. README, v0.9.0 notes
- Waylon dogfoods heavily: waylonwalker.com homepage says “Building markata-go, the engine behind this site”;
go.waylonwalker.comis live; repoWaylonWalker/waylonwalker.comhasmarkata-go.toml+ splitconfig/*.toml; he runs a “builder admin” for live builds and edits viamd.waylonwalker.com. homepage, waylonwalker.com repo, fast-mode post - Community: 2 stars, 2 forks, 51 open issues, 2 contributors — solo project, not open to PRs. repo
- Ecosystem extras already in flight: LSP (
markata-go lsp), TUI, agent-skill bundle, helm/k8s deploy docs, builder-admin service, local admin CMS issue (#986). cli.md, issues
10. Known gotchas (issues + observed) #
markata-go migratecan emit invalid TOML for nested Python config sections (auto_description) — validate withconfig validate. waylonwalker.com#20- Slug mode changes URLs:
path/hybridmodes change URL structure — “review links and redirects before switching”. v0.9.0 notes - Tailwind managed preflight off by default since v0.9.0 — sites depending on it must re-enable. v0.9.0 notes
- 0.x breaking changes are routine — pin the version (
eget/mise pin, orgo install …@vX.Y.Z), smoke-test templates on upgrade. README - Encryption pitfalls were real (plaintext leaks, missing decryption assets on feed pages) — fixed in v0.8.0; keep the version ≥0.8 and read
docs/guides/encryption.mdbefore relying on it. v0.8.0 - Optional theme assets (lite-yt CSS/JS, GLightbox, mermaid, webmentions, encryption) loaded site-wide rather than per-need — cosmetic/perf, open issue #980.
- pongo2 ≠ Jinja2:
migrate templatesis a checker, not a rewriter — expect manual edits to custom templates. - Case sensitivity in templates (
post.Titlevspost.title) has churned — check the agent-skilltemplate-contextreference and test. - Private posts stay out of
auto_feedspublic tag feeds — intended behavior, don’t be surprised if private-tagged posts are missing from tag pages. PR #979 - Env var for encryption keys is
MARKATA_GO_ENCRYPTION_KEY_DEFAULT(not_DEFAULT_KEYas the spec table says) — verified by build error message.
MIGRATION RISK SUMMARY (for pype.dev) #
Ports cleanly (verified in local markout-go build):
- All feed
filterstrings — same Python-like expression language; all 17 feeds generated (192 output dirs incl. archive/variants). published/draftfrontmatter semantics,tags,templateKeyfiltering,YYYY-MM-DD HH:MM:SSdates.!!!/???/???+admonitions for whitelisted types,[[slug]]and[[ slug ]]spaced wikilinks (resolve + hover previews), GFM tables, typographer, heading anchors, syntax highlighting.- Nav, RSS/Atom/JSON per feed, sitemap, 404, prevnext, OG/structured data, pagefind search, tailwind automation.
- Encryption:
templateKey: protected-postposts encrypted viaprivate_tagsmap +MARKATA_GO_ENCRYPTION_KEY_DEFAULT.
Needs custom work:
- Custom admonition types (
scripture×70,background-thought×14,source×2) — hardcoded whitelist, no PRs accepted → file upstream issue and/or codemod to a supported type (e.g.quote) with matching title. plugins/build_thoughts.py— pre-build script writingpages/thoughts/*.md.- 30 Jinja templates — every
markata.config.*/post.*var reference needs rewriting to the go context; thehead.htmlinclude alone poisons the chain. Recommendation: adopt the default theme + palette/aesthetic CSS instead of porting. - Admonition typos in content:
waring,Scripture,Exodusds(were silently unstyled under Python markata too).
No equivalent (probably):
- Custom Python plugins generally — no Python plugin runtime.
service_workerplugin; per-postpublish_source(feed-level markdown/text exists); PWA manifest writer unconfirmed.- Python-specific filter helpers (
timedelta,parse) — verify inmigrate filter.