# Show HN: Hatamoto batteries-included CMS in Go

> Source: <https://hatamoto.dev/>
> Published: 2026-10-27 09:57:24+00:00

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.

AI crawler analyticsCounted on the server, where crawlers show up

One binary + PostgresNo PHP, Node or Redis

Plugins in a sandboxWebAssembly, only what you grant

Updates roll backSigned, checked, reverted on failure

No telemetryYour data stays in your Postgres

AI analytics, out of the box

See which AI crawlers read each page, and which ones are fakes.

Google 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.

Last 30 days46,191AI fetches across 16 of 19 known agents

Act now3,301spoofed fetches from bots pretending to be ClaudeBot, GPTBot and others

Act now11,456error responses served to AI crawlers, with the failing paths listed

Review863published pages no AI has fetched yet, so none can be cited

your-site.com/<admin>/ai-analytics/crawlers

Real data from a live Hatamoto site, last 30 days. Every figure above comes from these two screens.

Training

Model crawlers

Bulk crawls that feed future models. You can allow or block all of them in robots.txt from the same screen.

GPTBotClaudeBotCCBot

Search index

Answer-engine indexers

AI search tools answer from the indexes these bots build. A page they never fetch can't be cited.

OAI-SearchBotPerplexityBot

User fetch

Live assistant requests

A person asked an assistant a question and it opened your page to answer. This is the closest you get to a real reader.

ChatGPT-UserClaude-User

Checked, so fakes don't count

IP ranges and reverse DNS for nine operators. Bots using a fake user agent are flagged and left out of the totals.

Per page, in the editor

The editor shows which agents have fetched the post you're working on, and when they first did.

AI referrals

Visits from ChatGPT, Perplexity, Gemini and Claude, per assistant and per day.

No visitor profiles

Counted per day in your own Postgres. No cookies and no third-party scripts.

Everything else, out of the box

The plugin stack you'd assemble is already in core.

One content model, one settings screen, one update. No licence keys, no upsell banners, and no plugins to test against each other after every update.

Usually you add

In Hatamoto core

A page builderElementor, Divi

→

Visual builder.Click to select, double-click to type, right-click menus, copy between pages, and desktop, tablet and phone views.

An SEO pluginYoast, Rank Math

→

SEO and answer panel.Focus keyword, meta, canonicals and checks in the editor. JSON-LD generated from the content type.

A cache pluginWP Rocket, W3 Total Cache

→

In-memory page cache.Clears itself when you save, preloads from the sitemap. No Redis, no Varnish.

Redirects and 404sRedirection

→

Redirects with wildcards.A 404 log ranked by hits, and Explain a URL to see which rule answers.

A form pluginContact Form 7, Gravity Forms

→

Form builder.A hidden spam trap, timing and rate limits, with an optional captcha. Email, CSV export and a signed webhook.

An analytics scriptGoogle Analytics, which can't see AI crawlers

→

AI 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.

A security pluginWordfence

→

Sealed admin.A private admin address, the same 404 for every scanner, and attack tests in the codebase.

An API and automation layerWPGraphQL, Zapier

→

MCP server and REST API.Scoped keys per agent or script, on the same rules as the admin.

Custom fieldsACF, Pods

→

Your 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.

Plugins with full accessPHP that can read every file and table

→

Sandboxed WASM plugins.Each plugin runs in WebAssembly with memory and time limits, and only the permissions the owner ticks.

Sitemaps, schema, linksthree more plugins

→

One entity model.Sitemaps, feeds, llms.txt, internal links and structured data from the same record.

Usually a plugin per job, each with its own updates and settings.Here, one binary and one update.

Entities first

Every page starts as one typed record, and everything machines read is built from it.

You 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.

Emergency plumbingService

name

Emergency plumbing

slug

emergency-plumbing

area_served

Melbourne

hours

24/7

price_from

$149 call-out

faq

3 questions

Published · one record in Postgres

The page/services/emergency-plumbing

Emergency plumbing

24/7 call-outs across Melbourne. From $149.

Call now

JSON-LDin the page head

```
{ "@type": "Service", "name": "Emergency plumbing",
  "areaServed": "Melbourne",
  "provider": { "@id": "…/#organization" } }
```

Sitemap/sitemap.xml

```
<url><loc>https://your-site.com/services/emergency-plumbing</loc>
     <lastmod>2026-10-27</lastmod></url>
```

llms.txt and knowledge graph/llms.txt

```
- [Service](https://your-site.com/api/v1/public/knowledge-graph?schema=service):
  12 entities as JSON-LD.
```

Edit 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.

What comes built in

Everything you need to edit, rank and run a site.

編Edit

A visual builder for pages and a plain editor for posts.

Pages open in the builder and posts open in a simple writing view. Both save to the same record.

Double-click to format text on the page. Right-click to copy, wrap, duplicate or paste into another page.

Desktop, tablet and phone views, with show and hide per device.

Slash menu, autosave, revisions, and unsaved work kept on the server, so it follows you to another device.

Page

Hero

Heading

Text

Services

FAQ

HeadingClass

Anchor

Show on

実Get found

Search engines and AI get the same structured data.

Every 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.

JSON-LD for every entity, with FAQ, Person and Claim markup.

Real authors as people, with the same @id on every post they wrote.

Internal links that add themselves from rules you set once.

``` php
<!-- /services/emergency-plumbing, from the entity -->
{ "@type": "Service",
  "name": "Emergency plumbing",
  "areaServed": "Melbourne",
  "provider": { "@id": "…/#organization" } }
```

機Let agents publish

AI agents work through the same rules as your team.

An MCP server at /mcp and a CLI call the same services as the admin, so validation, sanitising, hooks and cache clearing always apply.

A scoped key per agent. A read key can't write.

Anything an agent creates starts as a draft. Agents can build pages in the builder, with the same checks.

Every call, allowed or refused, lands in the audit log and the version history.

速Run it simply

Deploy one file, with no build step.

The 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.

No PHP version to juggle, no Node build step, no npm.

Runs its own migrations on boot, with a supervisor that restarts it.

Any Linux box, or docker compose up.

Security

No /wp-admin for bots to find.

Every 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.

RequestAnswer

GET /wp-login.php404

GET /admin404

GET /.env404

GET /xmlrpc.php404

GET /k7qm-x2rf-9tdcyour admin

Public pages can't reach the admin

A script on a public page can't fetch, frame or drive the admin. Previews and the builder canvas run no page scripts at all.

Attack tests run on every change

A 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.

Plugins can't take the site down

Plugins are signed WebAssembly with their own Postgres schema and only the permissions you tick. A failing plugin is switched off, not fatal.

Updates that roll themselves back

Signed releases install in your maintenance window. If the site fails its checks for a minute afterwards, the previous version comes back.

Move from WordPress

Move from WordPress without losing a URL.

The 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.

01 · Scan

See what comes over

Counts, permalinks, SEO plugin, page builders, shortcodes and anything that won't convert. Nothing is written.

--scan-only

02 · Import

Content, media, menus, people

Posts, pages, categories, tags and menus. Authors become people with bylines. SEO titles carry over from Yoast, Rank Math, SEOPress or AIOSEO.

--url … or --wxr site.xml

03 · Audit

Every old URL checked

Each address from your old sitemap must answer with a page or a single 301 to one. Chains, 302s and 404s fail the run.

--audit-url https://new…

04 · Cut over

Re-sync, then switch

Run it again the night before. It updates what it imported, never duplicates, and leaves your Hatamoto edits alone.

--resume <run> --keep-edited

Comes over

Your permalink structure, copied into URL patterns

Site title, tagline, time zone, language and home page

YouTube and Vimeo embeds, galleries and featured images

Private posts and drafts, with an application password

Doesn't come over, and the scan lists it before you start

Page builder layouts: the text is kept, the page is listed to rebuild

Forms, comments and WooCommerce data

Shortcodes: removed from the body and reported

For developers

Build on it with JSON-LD, MCP and Go.

Pull structured data from any page, connect an agent over MCP, or write a WebAssembly plugin when core doesn't cover what you need.

ReadStructured 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.

GET /services/emergency-plumbingCopy

``` bash
$ curl -H 'Accept: application/ld+json' \
    https://your-site.com/services/emergency-plumbing
{
  "@context": "https://schema.org",
  "@type": "Service",
  "name": "Emergency plumbing",
  "url": "https://your-site.com/services/emergency-plumbing",
  "areaServed": "Melbourne",
  "provider": { "@id": "https://your-site.com/#organization" }
}
// mcp.json, in any MCP client
{
  "mcpServers": {
    "hatamoto": {
      "url": "https://your-site.com/mcp",
      "headers": {
        "Authorization": "Bearer nxk_…"
      }
    }
  }
}
package main

import (
	"hatamoto.dev/sdk"
	"hatamoto.dev/sdk/guest"
)

func init() {
	guest.OnAction(sdk.EntityPublished, func(ev sdk.EntityEvent) error {
		guest.Log(guest.LogInfo, "published "+ev.Slug)
		return nil
	})
	guest.OnFilter(sdk.JSONLDEnrich, func(d sdk.JSONLDDoc) (sdk.JSONLDDoc, error) {
		d.Doc["x-hello"] = "hi"
		return d, nil
	})
}

//go:wasmexport on_entity_published
func onEntityPublished() int32 { return guest.Run(sdk.EntityPublished.Export()) }

//go:wasmexport filter_jsonld_enrich
func filterJSONLDEnrich() int32 { return guest.Run(sdk.JSONLDEnrich.Export()) }

func main() {}
```

Validate any page with the MCP tool jsonld_validate.

Open source

Open source, including the code that runs this site.

Apache-2.0 on GitHub. hatamoto.dev runs the same code. There's no paid edition, no licence server and no telemetry.

Apache-2.0Use it for clients, fork it, sell it

0Telemetry. One update check, and you can turn it off

3 channelsCanary, edge and stable, signed

Any languagePlugins in Go or anything with an Extism PDK

Postgres and Hatamoto start, secrets are generated, migrations run.

```
git clone https://github.com/Zen-Labs-Corp/hatamoto
cd hatamoto && docker compose up
```

02

Grab your setup code and admin address

The setup code is printed once in the logs. Your private admin address is one command away.

```
docker compose logs hatamoto
docker compose exec hatamoto hatamoto admin-url
```

03

Import your WordPress site

Finish the wizard and pick Import from WordPress, or stay in the terminal.

``` python
hatamoto import wordpress --url https://your-site.com --scan-only
```

Questions

Common questions

Why can't my current analytics see AI crawlers?

Browser 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.

Will my rankings survive a move from WordPress?

The 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.

What about the plugins I rely on?

Most 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.

Where can I host it?

Anywhere 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.

Is it ready for a real site?

It 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.

Does it send any data home?

No telemetry. The only routine outbound call is an anonymous update check for a signed file, which you can switch off with HATAMOTO_UPDATES=off.

旗本

See who reads your site, without a plugin stack.

Open source and one Go binary. AI analytics start counting from the first request.
