pre-alpha·MIT licensed·100% of the API accounted for

A modern PHP engine.
Built from scratch.

Minn fluently speaks with WordPress sites: the same database, the same file layout, the same REST API and WP-CLI verbs. Hosting tools, backups, migrations, and plugins keep working. The code underneath is new, small, and yours to read.

Reads a WordPress database as-is Untouched wp-config.php wp/v2 REST and WP-CLI Minn Admin boots unmodified Diffed live against real WordPress
tests/run-all.sh  ·  engine vs reference, same database
Minn the engine
GET/wp-json/wp/v2/posts/1?context=edit200 · identical
POST/wp-login.php302 · cookie accepted by both
GET/sample-page/200 · body identical
GET/feed/200 · byte for byte
GET/wp-sitemap.xml200 · identical
CLIwp option get siteurlidentical
WordPress the reference, same DB
GET/wp-json/wp/v2/posts/1?context=editthe oracle
POST/wp-login.phpthe oracle
GET/sample-page/the oracle
GET/feed/the oracle
GET/wp-sitemap.xmlthe oracle
CLIwp option get siteurlthe oracle
25,000+ checks green. Every surface is pinned by a captured fixture and diffed live, request by request, against a running WordPress on the same database. Any divergence is a finding.
626
classes in the Minn\ namespace. A clean-room reimplementation
Explore the classes
25,000+
checks across 91 suites, most of them diffed against real WordPress on the same database
Explore the suites
0
Composer packages. Strict-typed PHP, no framework, no build step
1
admin, already built. Minn Admin boots on the engine unmodified
Already on WordPress?

Start with Minn Admin today. Installing Minn comes later.

Minn Admin is a plugin for WordPress sites: a reimagined admin served at /minn-admin/ on the site you have right now. On WordPress software, classic wp-admin stays as a fallback. On Minn there is no wp-admin: Minn Admin is the admin. Everything you learn there carries over unchanged.

  • Install it like any plugin. Free, MIT licensed, no build step, no Pro tier.
  • Live in it. Content, media, comments, users, settings, and more than a hundred plugin adapters that appear on their own.
  • Nothing to relearn. When Minn is ready for your site, the admin you already use comes with it.
The sequence
Phase 0Minn Admin is the daily admin on WordPress sites. Classic wp-admin stays as a fallback. A complete, REST-pure admin running against real sites.shipped
Phase 1The engine serves greenfield sites. Front end plus REST, Minn Admin as the only admin, fleet tooling that operates the site the way it operates a WordPress site.in progress
Phase 2The plugin runtime. Plugins load unmodified, layer by layer. Front end and data; no /wp-admin/.in progress
Phase 3Migration for the tractable slice. Brochure, forms, and content sites, with the caveats enforced by the tooling itself.earned later

A strangler fig, not a big bang. Each phase is earned by suites, not declared.

The thesis

WordPress is not only software.
It is a coordination standard.

WordPress software remains the dominant web platform because customers, hosts, developers, and now AI agents all coordinate there. The portable unit of site, the frozen schema, the REST API, and the labor pool are the product. The PHP is one implementation of those contracts. Here is what that coordination actually consists of, ranked by how much it matters.

1

A portable unit of site

Files plus a database dump, runnable on any PHP host on earth. Backup, migration, staging, and whole fleet-management stacks exist because the unit is standard.

2

A stable data contract

The posts, postmeta, options, and users schema has not meaningfully changed in twenty years. Backward compatibility is the actual product.

3

An operational surface

WP-CLI, the REST API, the file layout, the config conventions. The layer hosts automate against; the layer that makes hosting WordPress sites an industry.

4

A labor pool

Any freelancer can inherit any WordPress site cold. Choosing a WordPress site is choosing to never be hostage to whoever built it. The customer-control argument is the strongest one.

5

The plugin ecosystem

The weakest leg now, and weakening: AI erodes the long tail. What it does not erode is what a top-tier plugin really is, a maintenance liability transferred to a vendor who ships security releases.

The AI shift sharpens the thesis. When anyone can generate anything on any platform, the rational customer targets the platform where generated work stays portable, inheritable, and operable after the developer leaves. Today that is a WordPress site, because of legs one through four. The question is whether those legs require the WordPress codebase at all.

The graveyard lesson

Every challenger replaced the software and abandoned the contracts.

Ghost, Craft, Statamic, October: none of them dented WordPress, and they all failed the same way. Each replaced the code, which was never the moat, and walked away from the contracts, which were. A clean break means starting over on portability, tooling, hosting support, and the labor pool at once. No product is good enough to win four moats simultaneously.

The correct precedent

You do not ask the world to move. You inherit it through compatibility.

Not “a better CMS”. Nginx against Apache. MariaDB against MySQL. FrankenPHP against PHP-FPM. A from-scratch engine that speaks the incumbent’s interface so precisely that the surrounding infrastructure cannot tell. That is the play.

nginx : apache mariadb : mysql frankenphp : php-fpm minn : wordpress
The play

Two tiers of compatibility, with a hard line between them.

WordPress speaks to three audiences. Minn answers two. The operational contract is sacred. The runtime plugins call into is reimplemented, clean-room, the way Wine speaks Win32. Humans never go through /wp-admin/: Minn Admin is the only UI.

Audience 1 · toolingSpeak

Hosts, backups, CLI.

The schema, the file layout, wp/v2, WP-CLI, feeds, sitemaps, cookies. Kinsta and CaptainCore cannot tell.

  • wp_* tables, untouched wp-config.php
  • REST, WP-CLI, probes, login cookies
Audience 2 · plugin PHPSpeak

Unmodified plugins load.

Hooks, options, WP_Query, $wpdb. A site owner does not want ported plugins. They want their plugins.

  • Clean-room runtime, symbol-gated
  • Front end and data. Not a settings screen.
Audience 3 · humansMute

No /wp-admin/.

List tables, meta boxes, the dashboard, the Customizer, Gutenberg-as-an-app: not a Minn surface. Minn Admin is the only UI.

  • /wp-admin/ 302s to /minn-admin/
  • Plugin settings screens are not coming

Browse the lexicon for every family: Speak, Hear, and Mute, sourced from the markdown agents read.

Tier 1 · the operational contract

Indistinguishable at the seams.

  • The database schema. Real wp_* tables, read and written, tolerating serialized-PHP blobs forever.
  • File layout and wp-config.php shape, so backup and migration tooling just works. The engine boots from the file WordPress generated, untouched.
  • wp/v2 REST core routes: posts, pages, media, users, terms, comments, settings, revisions, search, plugins, themes, menus, templates, navigation, global styles, block types, widgets, and batch requests.
  • WP-CLI: the ops verbs (option, user, plugin, theme, db, search-replace) answered by the engine through wp-cli.yml, and every other command, a plugin’s own included, run on the engine’s runtime.
  • Permalinks, feeds, sitemaps, block rendering, block themes. Login, sessions, cookies, and nonces the hosting layer probes, cross-accepted in both directions.
Tier 2 · the plugin runtime, reimplemented

Plugins just work. Layer by layer.

A site owner does not want ported plugins, they want their plugins. So the engine implements the functions plugin code calls: the hook engine, the content and options API, queries, translations, mail and HTTP, admin registrations (not screens). Loaded from active_plugins, unmodified. Every function’s signature is checked against an inventory reflected from the reference, and probe batteries compare behavior with WordPress row by row. The coverage page measures how far it reaches, commit by commit.

E0Inventory: 3,903 functions, 394 classes, 2,940 hooks, reflected from the referencedone
E1Hook engine and plugin lifecycledone
E2Content, options, queries, media, cron, HTTP, mail, comments, REST, blocks, translations, the HTML API, $wpdbmost of it
E3Admin registrations, not screens. add_menu_page records. Nothing is rendered. /wp-admin/ redirects.done
E4Editors: a plugin’s block.json registers and renders. The block and site editors themselves are dead ends: inert stubs, never screens.done
E5Every remaining symbol, one domain at a time, until the whole API is accounted for and proven. XML-RPC and the Customizer are dead ends: inert stubs, so nothing fatals.in progress

Definition of done for v0.1.0

Every function, class, and method in the WordPress PHP API is accounted for. Each one is either implemented and proven against WordPress by a probe, or marked in the mappings as a dead end Minn leaves behind on purpose: wp-admin screens, the block and site editors, the Customizer, XML-RPC, and anything too old to carry forward. A dead end still answers with an inert stub, so a plugin that calls it does not fatal. On top of that, Kinsta, CaptainCore, Disembark, and UpdraftPlus operate the site the way they operate a WordPress site, Minn Admin boots unmodified, and the engine makes no requests to wordpress.org.

See where it stands today →

How it is built

Capture from the oracle. Implement from the capture. Never from the source.

A parked copy of WordPress runs against the same database as the engine. It is the oracle: every behavior is observed there, recorded as a fixture, implemented from that record, and then diffed live, request by request. WordPress source is never opened while engine code is written.

1

Capture

Boot the reference on the shared database. Curl the exact response, run the exact CLI verb, record it under contracts/fixtures/. Observed behavior is data.

2

Implement

Write the engine from the fixture and the behavioral note. Interface names (routes, columns, keys, verbs) match exactly; every implementation line is original.

3

Prove two ways

A fixture suite pins the captured shape and runs anywhere. A live parity suite diffs the engine against the running reference. Any divergence is a finding, not a flake.

4

Record the contract

Every oracle-learned fact and every honest gap lands in contracts/*.md, machine-checkable and agent-legible. The contract is the product.

# what a milestone looks like
$ cd wp-reference && php -S 127.0.0.1:8123 router.php   # the oracle
$ php tests/theme.test.php
  ok   / matches fixture
  ok   / matches reference
  ok   /hello-world/ matches fixture
  ok   /hello-world/ matches reference
  …
  30 passed, 0 failed

$ php tests/rest-parity.test.php
  ok  posts list
  ok  posts _fields id,title,status
  ok  types _fields quirk strips to []
  …
  43 passed, 0 failed

Agent-first is a build method, not a feature

Nobody had done the ops-compatible rewrite because it is an enormous amount of boring, well-specified work. That is precisely what coding agents do well when gated by suites. Every unit ships with a behavioral suite; the suites, not the prose, are ground truth.

Security by declaration

A route’s capability requirement is metadata on the route, mechanically auditable and diffable across releases. Prepared statements only. No unserialize() of untrusted data, ever: serialized blobs are read by tolerant parsers, never executed.

Agent legibility answers the turnover argument

The take-over story for a customer stops being “find another WordPress freelancer” and becomes “point any coding agent at the contracts”.

What the oracle caught

Facts no documentation would have surfaced.

Each one is pinned by a suite that diffs Minn against the running reference, so it cannot quietly drift back.

$wp$ hash → substr($hash, -4)

The cookie key hides in the last four characters.

Modern $wp$ bcrypt hashes sign the auth cookie with the hash’s final four characters, not the old phpass window. The cookie parsed, the session was live, the nonce verified; only the HMAC failed. Brute-forcing the window found it.

bad cookie → 401 · bad nonce → 403

Two failures, two status codes.

A bad cookie is rest_not_logged_in; a bad nonce is rest_cookie_invalid_nonce. Sessions cross over byte for byte: one WordPress minted works on Minn, and one Minn minted works on WordPress.

perm=editable → own posts only

“Editable” means yours, even for admins.

Ask for more than one post type with the editable filter and every status, published included, narrows to the caller’s own posts. Admins too. Minn Admin’s activity feed was built on that behavior, so the engine keeps it.

/wp/v2/types?_fields=name → []

Ask types for one field, get nothing.

On wp/v2/types the payload is a keyed object and _fields filters it whole, so every value yields an empty array. Minn reproduces the quirk by construction, not by special case.

term_taxonomy.count → trusted on read

Term counts are stored, never recounted on read.

WordPress serves each term’s published total exactly as stored. Every write that changes it (publish, unpublish, trash, delete) has to recount, or a stale number leaks into every list response.

6'2" → 6’2″

Feet get a quote mark, inches get a prime.

Texturize turns the apostrophe after a digit into a curly closing quote, but the double quote after a digit into a double prime. Typographically it is half right, and Minn matches it byte for byte anyway.

The license question

Can a WordPress-compatible engine be MIT? Yes.

GPL obligations attach to distributing WordPress’s code or derivative works of it. Minn distributes neither. Compatibility is not derivation. Copyright never protects a procedure, system, or method of operation (17 U.S.C. §102(b)); interfaces, schemas, and protocols are exactly that, and where a compatible route must be named /wp/v2/posts, the expression merges with the idea.

  • Google v. Oracle (2021): reimplementing an API surface for a new platform was fair use.
  • Lotus v. Borland, Sega v. Accolade, Sony v. Connectix: command hierarchies are methods of operation; reverse engineering for compatibility is fair use.
  • In practice: Wine reimplements Win32, Samba reimplements SMB, FerretDB speaks MongoDB under Apache 2.0, and WP-CLI itself is MIT at the heart of the GPL ecosystem.
  • Fork versus reimplementation is the whole question. ClassicPress kept the contracts and stayed a fork: the license came with the tree, so it is GPL forever. A reimplementation written from specifications of behavior is a new work, and its author chooses the license.
The traps, named

What would actually cause a problem.

  • Copied code, including mechanically transformed code. Minn’s predecessor generated its build from WordPress’s own source; that output was a derivative work and could never have been MIT. This is the cautionary tale the whole method is built around.
  • Bundled GPL assets. No core JavaScript, no block-library CSS, no bundled themes, no Dashicons ship in the engine. The block stylesheet you are looking at is Minn’s own.
  • Trademark, not copyright, is the live wire. “WordPress” appears only to identify the software and sites Minn is compatible with, never in the project name or domain.

The hygiene program: spec-first development from fixtures captured on a live instance, implementation from the spec and never the source, a similarity gate against the WordPress tree, tracked provenance for every file, and an hour with an open-source-savvy IP lawyer before the runtime milestone ships. This page is analysis, not legal advice.

Status · pre-alpha

What works today, and what does not.

The engine boots from an unmodified wp-config.php, serves a database WordPress software created, and is written from scratch with only the code it needs to be compatible with WordPress sites. A real site (a client site cloned with the engine swapped in) renders at parity with its parked WordPress software, element by element, in a browser.

Compare the code size: WordPress core next to Minn with Minn Admin, measured from the release zip the same way on both sides and refreshed at each release. Coverage measures how much of the WordPress interface the engine implements, commit by commit. How the PHP reads puts the WordPress idiom next to the Minn shape of the same job.

Working, pinned by suites

  • A visitor can read the site. Permalinks, hierarchies, archives, search, pagination, redirects, 404s: 107 URL shapes at parity.
  • Block themes render as data. Templates, parts, patterns, site-editor overrides, theme.json to CSS with presets byte-identical, and a geometry suite that measures every visible element against the reference in Chrome.
  • Classic PHP themes run. Template hierarchy, wp_head defaults, and live body parity against the reference. Proven on a real site that still uses PHP templates.
  • The admin half. wp/v2 posts, pages, media, users, terms, comments, settings, revisions, autosaves, locks, search, plugins, themes, menus, templates, navigation, global styles, widgets. Minn Admin boots and every view answers.
  • Auth, both directions. Cookies, sessions, nonces, capabilities, wp-login.php, registration, password reset, sign-in throttling, application passwords.
  • The probe surface. Feeds byte for byte, sitemaps, robots, cron, mail, the REST index, the version file tooling parses, and the object-cache.php, db.php, advanced-cache.php and .maintenance files hosts drop in.
  • WP-CLI on Minn, plugin commands and wp eval included, and a one-command minn install / eject that swaps a WordPress webroot over and back, byte for byte.
  • Plugins load as code. On a production site cloned onto the engine, all 25 plugins and the theme load unmodified; on a second clone, all 48 plugins and the theme. 97% of the calls those plugins make into WordPress functions reach one Minn implements (coverage).
  • The round trip. A copy of a real site does the same day of work on WordPress and on Minn, every row each leaves behind is compared, and Minn’s result is handed back to WordPress, which keeps going.
  • Translations. Plugin, theme and core text domains load from .mo and .l10n.php files, scripts get their JSON translations, and a request can switch locale.
  • The source is public. MIT, at github.com/austinginder/minn-engine, with every commit from the first.

Not yet, said plainly

  • No /wp-admin/. Plugin settings screens are not coming. If a plugin only has a wp-admin page, Minn Admin covers it with an adapter, or that page does not exist on the engine.
  • No block editor globals. Editor-side plugin scripts have nothing to attach to.
  • Commerce is limited support. A WooCommerce store’s shop, cart, and checkout run on the engine, and a guest checkout matches WordPress step by step. Running the store is another matter: store management lives in Minn Admin, so payment gateways, shipping and tax extensions, and other commerce plugins that only have wp-admin screens each need a Minn Admin adapter first.
  • The Customizer and XML-RPC are dead ends by design. Multisite and the rest of the long tail are counted in the coverage report until they reach 100%.
  • Some of the engine’s own words are English only. Text domains load, but a few labels the block renderer writes itself (the search button, the query pagination links) are not translated yet. The app already speaks your language.
HARD PART · 01

Serialized PHP is forever

Reading real WordPress databases means tolerating serialized blobs in options and postmeta indefinitely. Greenfield sites keep it out of new data; migrated sites drag the long tail in.

HARD PART · 02

The runtime is the long road

Front-end plugins work once hooks and the content API exist. A plugin’s own settings screen is never hosted: Minn Admin adapters own that UI. Editor plugins need the editor globals. “Any plugin” means front end and data, reached layer by layer, not declared.

HARD PART · 03

Commerce is limited support

A store’s front end is plugin code, and plugin code is what the engine runs. Running a store is screens: orders, refunds, gateways, shipping, and tax all live in wp-admin today. Each one has to reach Minn Admin as an adapter before a store can move. Brochure, forms, and content sites come first.

HARD PART · 04

Bus factor

The customer-control argument only transfers once someone other than the founder can maintain the engine. MIT, machine-readable contracts, and exhaustive suites are the mitigation.

Fair questions

Things people ask first.

What is Minn, and what is Minn Engine?

Minn is the project: this site, what you install, the product. Minn Engine is the developer name for the runtime, the way people say WordPress core. Minn Admin is the interface, a plugin that already runs on WordPress and on Minn.

Is this a fork of WordPress?

No. Minn is written completely from scratch, with only the minimal amount of code needed to be compatible with WordPress sites. That is what makes the MIT license possible; a fork would be GPL forever.

Will my plugins work?

Increasingly, on the front end and in data. The engine reimplements the runtime plugins call into, clean-room, and loads them unmodified behind a symbol gate: a plugin that calls a function the runtime lacks is skipped with the reason rather than fataled. A plugin’s own wp-admin settings screen is not hosted. Minn Admin covers that job with an adapter, or that page does not exist on the engine.

Will my host notice?

That is the definition of done, and the point. The webroot keeps index.php, wp-config.php, wp-settings.php, wp-includes/version.php, and wp-content/ exactly where tooling expects them, and wp/v2, feeds, sitemaps, and wp-cron.php answer the way monitors and backup tools probe them. /wp-admin/ redirects to Minn Admin: there is no wp-admin UI. Real fleet tooling is run against a real site until those tools operate it the same way they operate a WordPress site.

Can I go back?

Yes, in one command. minn eject restores the parked WordPress files byte for byte. The database stays yours: a round-trip suite gives WordPress and Minn the same day of work on a copy of a real site and compares every row each leaves behind. Beyond Minn’s own bookkeeping, kept in minn_* options WordPress never reads, each difference it allows is listed and explained, then WordPress takes Minn’s result and keeps going.

Why PHP?

Because the moat being inherited is PHP hosting. Modern PHP on FrankenPHP-class runtimes is fast, and the infrastructure already speaks it. Modern PHP, 8.3 minimum, no framework, no build step, no dependencies.

Where is the admin?

Minn Admin, a plugin for WordPress sites, is the interface. It boots on the engine unmodified because it never touched wp-admin internals in the first place. On WordPress software, classic wp-admin stays as a fallback. On the engine there is no wp-admin: Minn Admin is the admin. It speaks wp/v2 plus its own namespace, and it works on the WordPress site you have today. Start there.

Where is the source?

The engine is public at github.com/austinginder/minn-engine, MIT licensed, with its whole history. Minn Admin’s source is public too, and its contracts are the same ones the engine serves.

A second engine for WordPress sites.

Try Minn Admin on the site you have today. When 100% of what you do happens in /minn-admin/, consider an engine swap with Minn.

Where Minn stands

100% accounted for. 100% proven.

Every function, class, and method in the PHP API of WordPress 7.1.3, counted at commit 1bdf3cb8 on October 13, 2026. A symbol is accounted for when Minn implements it or the mappings mark it a dead end: something Minn leaves behind on purpose and answers with an inert stub, so a plugin that calls it does not fatal. It is proven when a probe checks its behavior against WordPress. Version 0.1.0 ships when both reach 100%.

Accounted for100%
Proven against WordPress100%
KindTotalImplementedDead endStill owedProven
Functions3,9142,96295202,962 of 2,962
Classes3802391410239 of 239
Methods4,0752,3871,68802,387 of 2,387

Dead ends, by design

  • wp-admin screens and includes1,798
  • The block and site editors189
  • The Customizer465
  • XML-RPC and pingbacks197
  • Left out on purpose114
  • Answered Minn’s own way18

Still owed, by area

Nothing: every symbol is implemented or a dead end.

Maturing toward 0.1.0

What the engine is held to beyond the API: the measures from its audit of October 12, 2026, read from the repository at the same commit.

MeasureNowTarget
Met: Security findings fixed, each pinned by a check30 of 30all 30
Met: Pages that run no more SQL than WordPressfront page: 27 SELECTs, WordPress 385 of 5all 5
Met: Hooks accounted for2,543 hooks in WordPress 7.1.3100%100%
Met: Hooks proven against WordPress, weighted by plugin installs96%95%
Met: Engine lines the checks runmeasured with unit cases, probes, page and REST requests and every suite, October 13, 202687%85%
Met: Methods nothing calls00
Met: Leanness ratchets that only fall18 fell; most, Public methods without a docblock: 933 to 023 of 23none raised
Met: Full run green25,709 checks passed, October 13, 202691 of 91 suitesevery suite
Not yet: Open items in the runtime notes1 waiting on a decision10

The leanness ratchets

Counts the style suite holds the engine to, each a ceiling that may only fall: lowered when the code drops below it, never raised. Each starts at the count when the ratchet was added.

RatchetNowStarted
Met: Lifecycle branches (live() calls)2020
Met: Option reads beside the store151159
Met: Raw option writes77
Met: Raw meta writes910
Met: Hand-built post rows88
Met: Hand-built REST links7777
Met: htmlspecialchars outside Html1314
Met: Unfiltered permalink callers3031
Met: Runtime::booted() branches73160
Met: Calls into WordPress-named functions1,0181,406
Met: Methods over eighty lines022
Met: Classes over six hundred lines04
Met: Public methods without a docblock0933
Met: Boolean parameters77147
Met: Constructors with six or more parameters2147
Met: array|PostRecord bridges458
Met: Bracket reads of post columns205572
Met: Bracket reads of user columns70132
Met: Bracket reads of comment columns57118
Met: Raw json_encode and json_decode calls11
Met: Routes without a policy6157
Met: Facade functions over fifteen lines1216
Met: wordpress.org request addresses013

Measured by the engine at commit 1bdf3cb8 on October 13, 2026. Coverage, commit by commit · compat.json

The engine, class by class

626 classes. 78,424 lines of PHP.

Every class in the engine, read from its code by reflection, in 26 namespaces. None of it is WordPress code: Minn is written from the behavior a running WordPress shows, never from its source, which is why all of it can be MIT. Select a namespace or a size to narrow the list, or search it.

88lines in the median class
589lines in the largest, Html\Tags. The style suite fails any class over 600
Allfinal, an enum, or an interface. Nothing waits to be subclassed
234readonly value objects, built once and never changed

Lines by namespace classes beside each name

Classes by size lines per class

Every class

Loading the classes…

    Read from the engine’s code, regenerated whenever it changes (its style suite fails while this is stale). The full API reference · classes.json

    The proof, suite by suite

    25,709 checks. 91 suites.

    From the full run of October 13, 2026: every suite the engine has, counted one assertion at a time. Most compare Minn with WordPress itself, request by request on the same database. Select a group to narrow the list, then open a suite to read its source.

    Where the checks come from

    Every suite

    Loading the suites…

      The suites live in tests/ in the engine’s public repository. The suites on GitHub · stats.json