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.
Minn\ namespace. A clean-room reimplementationStart 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.
/wp-admin/.in progressA strangler fig, not a big bang. Each phase is earned by suites, not declared.
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.
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.
A stable data contract
The posts, postmeta, options, and users schema has not meaningfully changed in twenty years. Backward compatibility is the actual product.
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.
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.
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.
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.
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.
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.
Hosts, backups, CLI.
The schema, the file layout, wp/v2, WP-CLI, feeds, sitemaps, cookies. Kinsta and CaptainCore cannot tell.
wp_*tables, untouchedwp-config.php- REST, WP-CLI, probes, login cookies
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.
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.
Indistinguishable at the seams.
- The database schema. Real
wp_*tables, read and written, tolerating serialized-PHP blobs forever. - File layout and
wp-config.phpshape, so backup and migration tooling just works. The engine boots from the file WordPress generated, untouched. wp/v2REST 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 throughwp-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.
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.
$wpdbmost of itadd_menu_page records. Nothing is rendered. /wp-admin/ redirects.doneblock.json registers and renders. The block and site editors themselves are dead ends: inert stubs, never screens.doneDefinition 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.
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.
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.
Implement
Write the engine from the fixture and the behavioral note. Interface names (routes, columns, keys, verbs) match exactly; every implementation line is original.
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.
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”.
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 → 403Two 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 readTerm 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.
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.
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.
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_headdefaults, and live body parity against the reference. Proven on a real site that still uses PHP templates. - The admin half.
wp/v2posts, 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.phpand.maintenancefiles hosts drop in. - WP-CLI on Minn, plugin commands and
wp evalincluded, and a one-commandminn install/ejectthat 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
.moand.l10n.phpfiles, 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.
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.
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.
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.
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.
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.