{"slug": "show-hn-hatamoto-batteries-included-cms-in-go", "title": "Show HN: Hatamoto batteries-included CMS in Go", "summary": "A developer released Hatamoto, a batteries-included content management system written in Go that ships as a single binary alongside Postgres with no PHP, Node or Redis dependencies. Hatamoto bundles a page builder, SEO, caching, forms, redirects, WebAssembly-sandboxed plugins and an MCP server, and adds server-side AI crawler analytics that verify each fetch against operators' published IP ranges and reverse DNS; one live site logged 46,191 AI fetches across 16 of 19 known agents in 30 days, alongside 3,301 spoofed fetches and 11,456 error responses served to AI crawlers. The server-side counting matters because crawlers do not run JavaScript, so script-based analytics such as Google Analytics cannot see them.", "body_md": "A page builder, SEO, caching, forms, redirects, AI analytics and an MCP server come built in. It runs as one Go binary next to Postgres, with no PHP, Node or plugins to keep updated.\n\nAI crawler analyticsCounted on the server, where crawlers show up\n\nOne binary + PostgresNo PHP, Node or Redis\n\nPlugins in a sandboxWebAssembly, only what you grant\n\nUpdates roll backSigned, checked, reverted on failure\n\nNo telemetryYour data stays in your Postgres\n\nAI analytics, out of the box\n\nSee which AI crawlers read each page, and which ones are fakes.\n\nGoogle Analytics can't see AI crawlers: they don't run JavaScript. Hatamoto counts them on the server instead. Each fetch is checked against the operator's published IP ranges and reverse DNS, grouped by why the bot came, and tied to the page it read.\n\nLast 30 days46,191AI fetches across 16 of 19 known agents\n\nAct now3,301spoofed fetches from bots pretending to be ClaudeBot, GPTBot and others\n\nAct now11,456error responses served to AI crawlers, with the failing paths listed\n\nReview863published pages no AI has fetched yet, so none can be cited\n\nyour-site.com/<admin>/ai-analytics/crawlers\n\nReal data from a live Hatamoto site, last 30 days. Every figure above comes from these two screens.\n\nTraining\n\nModel crawlers\n\nBulk crawls that feed future models. You can allow or block all of them in robots.txt from the same screen.\n\nGPTBotClaudeBotCCBot\n\nSearch index\n\nAnswer-engine indexers\n\nAI search tools answer from the indexes these bots build. A page they never fetch can't be cited.\n\nOAI-SearchBotPerplexityBot\n\nUser fetch\n\nLive assistant requests\n\nA person asked an assistant a question and it opened your page to answer. This is the closest you get to a real reader.\n\nChatGPT-UserClaude-User\n\nChecked, so fakes don't count\n\nIP ranges and reverse DNS for nine operators. Bots using a fake user agent are flagged and left out of the totals.\n\nPer page, in the editor\n\nThe editor shows which agents have fetched the post you're working on, and when they first did.\n\nAI referrals\n\nVisits from ChatGPT, Perplexity, Gemini and Claude, per assistant and per day.\n\nNo visitor profiles\n\nCounted per day in your own Postgres. No cookies and no third-party scripts.\n\nEverything else, out of the box\n\nThe plugin stack you'd assemble is already in core.\n\nOne content model, one settings screen, one update. No licence keys, no upsell banners, and no plugins to test against each other after every update.\n\nUsually you add\n\nIn Hatamoto core\n\nA page builderElementor, Divi\n\n→\n\nVisual builder.Click to select, double-click to type, right-click menus, copy between pages, and desktop, tablet and phone views.\n\nAn SEO pluginYoast, Rank Math\n\n→\n\nSEO and answer panel.Focus keyword, meta, canonicals and checks in the editor. JSON-LD generated from the content type.\n\nA cache pluginWP Rocket, W3 Total Cache\n\n→\n\nIn-memory page cache.Clears itself when you save, preloads from the sitemap. No Redis, no Varnish.\n\nRedirects and 404sRedirection\n\n→\n\nRedirects with wildcards.A 404 log ranked by hits, and Explain a URL to see which rule answers.\n\nA form pluginContact Form 7, Gravity Forms\n\n→\n\nForm builder.A hidden spam trap, timing and rate limits, with an optional captcha. Email, CSV export and a signed webhook.\n\nAn analytics scriptGoogle Analytics, which can't see AI crawlers\n\n→\n\nAI crawler analytics.Crawlers don't run JavaScript, so a script never sees them. Hatamoto logs every fetch on the server: which agent, which page, and whether it was really them.\n\nA security pluginWordfence\n\n→\n\nSealed admin.A private admin address, the same 404 for every scanner, and attack tests in the codebase.\n\nAn API and automation layerWPGraphQL, Zapier\n\n→\n\nMCP server and REST API.Scoped keys per agent or script, on the same rules as the admin.\n\nCustom fieldsACF, Pods\n\n→\n\nYour own content types.Types with typed fields, a schema.org type and an address pattern, built in the admin. Every page is a record before it is HTML.\n\nPlugins with full accessPHP that can read every file and table\n\n→\n\nSandboxed WASM plugins.Each plugin runs in WebAssembly with memory and time limits, and only the permissions the owner ticks.\n\nSitemaps, schema, linksthree more plugins\n\n→\n\nOne entity model.Sitemaps, feeds, llms.txt, internal links and structured data from the same record.\n\nUsually a plugin per job, each with its own updates and settings.Here, one binary and one update.\n\nEntities first\n\nEvery page starts as one typed record, and everything machines read is built from it.\n\nYou fill in the fields once. The page, its JSON-LD, its sitemap entry and the knowledge-graph feed all come from that record, so they always agree.\n\nEmergency plumbingService\n\nname\n\nEmergency plumbing\n\nslug\n\nemergency-plumbing\n\narea_served\n\nMelbourne\n\nhours\n\n24/7\n\nprice_from\n\n$149 call-out\n\nfaq\n\n3 questions\n\nPublished · one record in Postgres\n\nThe page/services/emergency-plumbing\n\nEmergency plumbing\n\n24/7 call-outs across Melbourne. From $149.\n\nCall now\n\nJSON-LDin the page head\n\n```\n{ \"@type\": \"Service\", \"name\": \"Emergency plumbing\",\n  \"areaServed\": \"Melbourne\",\n  \"provider\": { \"@id\": \"…/#organization\" } }\n```\n\nSitemap/sitemap.xml\n\n```\n<url><loc>https://your-site.com/services/emergency-plumbing</loc>\n     <lastmod>2026-10-27</lastmod></url>\n```\n\nllms.txt and knowledge graph/llms.txt\n\n```\n- [Service](https://your-site.com/api/v1/public/knowledge-graph?schema=service):\n  12 entities as JSON-LD.\n```\n\nEdit the record and every output follows on the next request. Switch off Include in /llms.txt and the entity leaves llms.txt and the feed together.\n\nWhat comes built in\n\nEverything you need to edit, rank and run a site.\n\n編Edit\n\nA visual builder for pages and a plain editor for posts.\n\nPages open in the builder and posts open in a simple writing view. Both save to the same record.\n\nDouble-click to format text on the page. Right-click to copy, wrap, duplicate or paste into another page.\n\nDesktop, tablet and phone views, with show and hide per device.\n\nSlash menu, autosave, revisions, and unsaved work kept on the server, so it follows you to another device.\n\nPage\n\nHero\n\nHeading\n\nText\n\nServices\n\nFAQ\n\nHeadingClass\n\nAnchor\n\nShow on\n\n実Get found\n\nSearch engines and AI get the same structured data.\n\nEvery page is a typed entity with a schema.org type. The page, its structured data, the sitemap entry and the llms.txt summary come from that one record, so they can't contradict each other.\n\nJSON-LD for every entity, with FAQ, Person and Claim markup.\n\nReal authors as people, with the same @id on every post they wrote.\n\nInternal links that add themselves from rules you set once.\n\n``` php\n<!-- /services/emergency-plumbing, from the entity -->\n{ \"@type\": \"Service\",\n  \"name\": \"Emergency plumbing\",\n  \"areaServed\": \"Melbourne\",\n  \"provider\": { \"@id\": \"…/#organization\" } }\n```\n\n機Let agents publish\n\nAI agents work through the same rules as your team.\n\nAn MCP server at /mcp and a CLI call the same services as the admin, so validation, sanitising, hooks and cache clearing always apply.\n\nA scoped key per agent. A read key can't write.\n\nAnything an agent creates starts as a draft. Agents can build pages in the builder, with the same checks.\n\nEvery call, allowed or refused, lands in the audit log and the version history.\n\n速Run it simply\n\nDeploy one file, with no build step.\n\nThe site, the admin, the API and the MCP server are one Go binary on one port, next to Postgres 16. Pages render on the server and are served from memory in milliseconds.\n\nNo PHP version to juggle, no Node build step, no npm.\n\nRuns its own migrations on boot, with a supervisor that restarts it.\n\nAny Linux box, or docker compose up.\n\nSecurity\n\nNo /wp-admin for bots to find.\n\nEvery Hatamoto install gets its own private admin address. Every other path, including the ones scanners try first, answers with the same plain 404 as a random URL. Public pages don't reveal what software runs the site.\n\nRequestAnswer\n\nGET /wp-login.php404\n\nGET /admin404\n\nGET /.env404\n\nGET /xmlrpc.php404\n\nGET /k7qm-x2rf-9tdcyour admin\n\nPublic pages can't reach the admin\n\nA script on a public page can't fetch, frame or drive the admin. Previews and the builder canvas run no page scripts at all.\n\nAttack tests run on every change\n\nA CI job starts the real binary and runs 22 attacks against the admin from a public page. Each has a control, so a silent pass fails the build.\n\nPlugins can't take the site down\n\nPlugins are signed WebAssembly with their own Postgres schema and only the permissions you tick. A failing plugin is switched off, not fatal.\n\nUpdates that roll themselves back\n\nSigned releases install in your maintenance window. If the site fails its checks for a minute afterwards, the previous version comes back.\n\nMove from WordPress\n\nMove from WordPress without losing a URL.\n\nThe importer reads your site through the REST API or a WXR export, from the admin wizard or the command line. Rehearse it as often as you like, and cut over when the audit is clean.\n\n01 · Scan\n\nSee what comes over\n\nCounts, permalinks, SEO plugin, page builders, shortcodes and anything that won't convert. Nothing is written.\n\n--scan-only\n\n02 · Import\n\nContent, media, menus, people\n\nPosts, pages, categories, tags and menus. Authors become people with bylines. SEO titles carry over from Yoast, Rank Math, SEOPress or AIOSEO.\n\n--url … or --wxr site.xml\n\n03 · Audit\n\nEvery old URL checked\n\nEach address from your old sitemap must answer with a page or a single 301 to one. Chains, 302s and 404s fail the run.\n\n--audit-url https://new…\n\n04 · Cut over\n\nRe-sync, then switch\n\nRun it again the night before. It updates what it imported, never duplicates, and leaves your Hatamoto edits alone.\n\n--resume <run> --keep-edited\n\nComes over\n\nYour permalink structure, copied into URL patterns\n\nSite title, tagline, time zone, language and home page\n\nYouTube and Vimeo embeds, galleries and featured images\n\nPrivate posts and drafts, with an application password\n\nDoesn't come over, and the scan lists it before you start\n\nPage builder layouts: the text is kept, the page is listed to rebuild\n\nForms, comments and WooCommerce data\n\nShortcodes: removed from the body and reported\n\nFor developers\n\nBuild on it with JSON-LD, MCP and Go.\n\nPull structured data from any page, connect an agent over MCP, or write a WebAssembly plugin when core doesn't cover what you need.\n\nReadStructured data, on requestEvery page carries its JSON-LD in the head. Ask for application/ld+json and you get only the data.ConnectAn MCP server at /mcpPoint any MCP client at your site with a scoped key. Agents list, draft, edit and publish through the same rules as the admin.ExtendPlugins in WebAssemblyHook into publishing, JSON-LD, forms and rendering with the Go SDK. A plugin runs in a sandbox with only the permissions the owner ticks.\n\nGET /services/emergency-plumbingCopy\n\n``` bash\n$ curl -H 'Accept: application/ld+json' \\\n    https://your-site.com/services/emergency-plumbing\n{\n  \"@context\": \"https://schema.org\",\n  \"@type\": \"Service\",\n  \"name\": \"Emergency plumbing\",\n  \"url\": \"https://your-site.com/services/emergency-plumbing\",\n  \"areaServed\": \"Melbourne\",\n  \"provider\": { \"@id\": \"https://your-site.com/#organization\" }\n}\n// mcp.json, in any MCP client\n{\n  \"mcpServers\": {\n    \"hatamoto\": {\n      \"url\": \"https://your-site.com/mcp\",\n      \"headers\": {\n        \"Authorization\": \"Bearer nxk_…\"\n      }\n    }\n  }\n}\npackage main\n\nimport (\n\t\"hatamoto.dev/sdk\"\n\t\"hatamoto.dev/sdk/guest\"\n)\n\nfunc init() {\n\tguest.OnAction(sdk.EntityPublished, func(ev sdk.EntityEvent) error {\n\t\tguest.Log(guest.LogInfo, \"published \"+ev.Slug)\n\t\treturn nil\n\t})\n\tguest.OnFilter(sdk.JSONLDEnrich, func(d sdk.JSONLDDoc) (sdk.JSONLDDoc, error) {\n\t\td.Doc[\"x-hello\"] = \"hi\"\n\t\treturn d, nil\n\t})\n}\n\n//go:wasmexport on_entity_published\nfunc onEntityPublished() int32 { return guest.Run(sdk.EntityPublished.Export()) }\n\n//go:wasmexport filter_jsonld_enrich\nfunc filterJSONLDEnrich() int32 { return guest.Run(sdk.JSONLDEnrich.Export()) }\n\nfunc main() {}\n```\n\nValidate any page with the MCP tool jsonld_validate.\n\nOpen source\n\nOpen source, including the code that runs this site.\n\nApache-2.0 on GitHub. hatamoto.dev runs the same code. There's no paid edition, no licence server and no telemetry.\n\nApache-2.0Use it for clients, fork it, sell it\n\n0Telemetry. One update check, and you can turn it off\n\n3 channelsCanary, edge and stable, signed\n\nAny languagePlugins in Go or anything with an Extism PDK\n\nPostgres and Hatamoto start, secrets are generated, migrations run.\n\n```\ngit clone https://github.com/Zen-Labs-Corp/hatamoto\ncd hatamoto && docker compose up\n```\n\n02\n\nGrab your setup code and admin address\n\nThe setup code is printed once in the logs. Your private admin address is one command away.\n\n```\ndocker compose logs hatamoto\ndocker compose exec hatamoto hatamoto admin-url\n```\n\n03\n\nImport your WordPress site\n\nFinish the wizard and pick Import from WordPress, or stay in the terminal.\n\n``` python\nhatamoto import wordpress --url https://your-site.com --scan-only\n```\n\nQuestions\n\nCommon questions\n\nWhy can't my current analytics see AI crawlers?\n\nBrowser analytics only count visitors that run JavaScript, and AI crawlers don't. Hatamoto sees every request on the server, so it counts the crawler fetch itself, checks the bot is who it claims to be, and links the hit to the page. Counting crawlers needs no script, and no visitor profiles are stored.\n\nWill my rankings survive a move from WordPress?\n\nThe importer copies your permalink structure, carries SEO titles and descriptions over, and audits every URL in your old sitemap. Each one must answer with a page or a single 301 before the run passes. That's the part of a migration that usually costs rankings.\n\nWhat about the plugins I rely on?\n\nMost sites run plugins for jobs Hatamoto does in core: SEO, caching, redirects, forms, sitemaps, analytics and security. For the rest there are sandboxed plugins, including first-party Search Console and Tag Manager, and a Go SDK to write your own. WooCommerce stores aren't a fit today.\n\nWhere can I host it?\n\nAnywhere that runs a Linux binary and Postgres 16: a small VPS, your own server or Docker. The binary serves the site, admin, API and MCP on one port, and the docs include a Caddy config for HTTPS.\n\nIs it ready for a real site?\n\nIt runs hatamoto.dev in production. Version 1.0 rolls out through canary, edge and stable channels, every release is signed, and an update that fails its health checks reverts on its own. The plugin SDK is still v0; what stays stable is written down in STABILITY.md.\n\nDoes it send any data home?\n\nNo telemetry. The only routine outbound call is an anonymous update check for a signed file, which you can switch off with HATAMOTO_UPDATES=off.\n\n旗本\n\nSee who reads your site, without a plugin stack.\n\nOpen source and one Go binary. AI analytics start counting from the first request.", "url": "https://wpnews.pro/news/show-hn-hatamoto-batteries-included-cms-in-go", "canonical_source": "https://hatamoto.dev/", "published_at": "2026-10-27 09:57:24+00:00", "updated_at": "2026-10-27 10:11:52.965701+00:00", "lang": "en", "topics": ["ai-crawlers", "generative-engine-optimization", "agent-protocols", "developer-tools", "ai-search"], "entities": ["Hatamoto", "Go", "Postgres", "GPTBot", "ClaudeBot", "CCBot", "OAI-SearchBot", "PerplexityBot"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-hatamoto-batteries-included-cms-in-go", "markdown": "https://wpnews.pro/news/show-hn-hatamoto-batteries-included-cms-in-go.md", "text": "https://wpnews.pro/news/show-hn-hatamoto-batteries-included-cms-in-go.txt", "jsonld": "https://wpnews.pro/news/show-hn-hatamoto-batteries-included-cms-in-go.jsonld"}}