Headless WordPress theme - full dashboard, full REST API, minimal public face.
No plugins required. No build tools. No external dependencies.
Safari might try to automatically unzip the release folder, re-zip (right click and "compress") or adjust your Safari settings. Chrome, Firefox, etc should download as the drag-and-droppable zip file.
- WordPress 6.0+
- PHP 8.0+
- All registered post types are force-exposed via
show_in_rest(opt-out available via themainframe_expose_post_type_in_restfilter) - Every REST response includes
featured_media_url— a direct image URL, no second request needed featured_media_sizesfield — map of registered size names to URLs (thumbnail,medium,full, …); external URLs return{"full": url}author_infofield —{id, name, slug, avatar_url, description, url}on every post, no second request to/wp/v2/users/:idancestor_idsfield — ordered array of ancestor IDs (nearest-to-root) for hierarchical post types;[]for flat typescategories_infofield — array of{id, name, slug}objects for each assigned category/wp-json/mainframe/v1/siteendpoint — one-call site summary: name, description, URL, logo, and all nav menus with top-level items- Configurable
Access-Control-Allow-Originheader for cross-origin consuming apps
- Front page is a blank white canvas — optionally displays logo, headline, short message, and linktree-style link cards populated from nav menus
- Archive, search, author, and date routes always redirect to home
- Singular posts/pages redirect by default; overridable per-post via a meta box ("show content" / "redirect" / site default)
- Plugin-generated routes and custom rewrite rules are left untouched — only standard WordPress route types are redirected
- When a slug is configured,
/wp-login.phpis blocked and login is served at the custom slug (e.g./login) - All WordPress login/logout/lost-password URLs rewritten automatically
- No custom slug by default —
/wp-login.phpremains active until a slug is saved
- Irrelevant Customizer sections hidden (Static Front Page, Menu Locations, Patterns)
- Appearance > Patterns and Menus removed from admin nav (Menus managed in Customizer)
- Settings > Reading and Discussion removed from admin nav
- Block editor Discussion panel removed
- Block editor Preview button removed — the WP frontend is not the consuming app
- Classic editor Preview button removed via
preview_post_linkfilter - Sensible defaults set on theme activation (see below)
- Per-post "Featured Image URL" field in the block editor sidebar and classic editor
- Stores an external image URL that overrides the attached featured image in REST responses
- Preview shown in the native Featured Image panel; Remove button to clear
- FIFU compatibility: posts previously using the Featured Image from URL plugin will automatically display their existing images — no re-entry needed after removing FIFU
- Site-wide fallback URL used in
featured_media_urlREST responses when a post has no featured image of any kind - Configured in Appearance > Mainframe Settings — paste a URL or pick from the media library
- Never written to post meta; the post editor is unaffected
- Fires a non-blocking HTTP POST to a configured URL whenever a post is published or un-published
- JSON body:
{event, post_id, post_type, site_url}— consumed directly by Vercel, Netlify, Cloudflare Pages deploy hooks - Optional secret adds an
X-Mainframe-Signature: sha256=<hmac>header so the receiving service can verify authenticity - 10-second site-wide cooldown prevents flooding when multiple posts are saved at once
- Configured in Appearance > Mainframe Settings (REST API section)
- When "Discourage search engines" (
blog_public = 0) is enabled, the WordPress core XML sitemap is disabled X-Robots-Tag: noindex, nofollowis added to all public-facing page responses when the site is set to private- Both settings follow
blog_public— toggled on/off during Headless Quick Setup
- WordPress notifies admins of new releases in Appearance → Themes — identical to a wordpress.org theme, no plugin required
- Checks the GitHub Releases API (cached 12 hours) and compares against the installed version
- One-click update installs the
mainframe.zipattached to the latest release - "View version details" popup shows the release changelog pulled from GitHub
- Appearance > REST API Reference — a full, browseable reference page linked from a button in Mainframe Settings
- Introspects the live REST API at render time: any field added by a plugin or custom code appears automatically without any manual update
- Shows all
mainframe/v1endpoints with their response schemas (field / type / description) - For every
show_in_restpost type: extra fields (fromregister_rest_field) listed prominently with Mainframe or Custom source badges; WP core fields collapsed under a togglable disclosure - Object and array fields show their sub-property shapes inline
On first activation the theme runs in safe mode — all WordPress content is publicly accessible at its standard URLs. A persistent admin notice points to the Quick Setup card in Mainframe Settings.
The setup card lets you opt into headless defaults one checkbox at a time:
| Option | What it does |
|---|---|
| Redirect all public routes to home | Sets Default Route Behavior to Redirect |
| Discourage search engine indexing | Sets blog_public = 0 |
| Disable comments and pingbacks | Closes comments/pings on new posts |
| Flat upload folder structure | Disables year/month subfolders (skipped if folders exist) |
| Custom login URL slug | Sets a slug and blocks /wp-login.php |
All settings are reversible from Mainframe Settings after setup. Dismissing the admin notice does not apply any settings.
Appearance > Customize > Front Page
- Headline — displayed above the short message
- Short Message — basic HTML supported (links, bold, italic, line breaks); quicktags toolbar included
Appearance > Customize > Site Identity
- Logo, Site Title, Tagline (standard WordPress)
Appearance > Customize > Menus
- Create menus; top-level items appear as link cards on the front page in creation order
- Only Custom Links are supported as menu items (post/page/taxonomy types redirect home)
| Option | Description | Default |
|---|---|---|
| Redirect type | HTTP 301 or 302 for frontend redirects | 301 |
| 404 behavior | Return a real 404 page or redirect home | redirect |
| Login slug | URL slug for the login page | (empty — wp-login.php active) |
| CORS origin | Allowed origin for REST API requests (empty = *) |
(empty) |
| Default route behavior | What singular posts do by default | show |
| Default featured image | Fallback image URL for posts with no featured image | (empty) |
| Frontend URL | Public URL of the consuming frontend app; rewrites editor permalinks and adds frontend_link to REST responses |
(empty) |
| Deploy hook URL | POST target for publish/unpublish events | (empty) |
| Deploy hook secret | HMAC-SHA256 signing secret for deploy requests | (empty) |
A REST API Reference button in the page header links to the live reference page.
Each post/page has a Public Route Behavior meta box:
- Use site default — inherits from Mainframe Settings
- Show content — renders the singular template (useful if you want WP to serve some pages directly)
- Redirect to home — always redirects regardless of site default
The _mainframe_route_behavior meta is exposed in the REST API.
add_filter( 'mainframe_expose_post_type_in_rest', function ( $expose, $post_type ) {
if ( 'secret_type' === $post_type ) {
return false;
}
return $expose;
}, 10, 2 );add_filter( 'mainframe_redirect_code', fn() => 302 );The frontend_link REST field and the block editor permalink display both start from the WordPress permalink with the domain swapped, then pass the result through the mainframe_frontend_link filter. Use this when your frontend app uses a different path structure:
// Example: posts live at /blog/{slug} on the frontend.
add_filter( 'mainframe_frontend_link', function ( $url, $post_id, $post_type ) {
if ( 'post' === $post_type ) {
$slug = get_post_field( 'post_name', $post_id );
return 'https://www.yoursite.com/blog/' . $slug;
}
return $url;
}, 10, 3 );The URL passed to the filter has no trailing slash. Return values should also omit trailing slashes.
mainframe/
├── screenshot.jpg # Theme preview image
├── style.css # Theme header (no styles)
├── functions.php # Bootstrap loader + theme supports
├── front-page.php # Public-facing front page template
├── singular.php # Single post/page template (with safety redirect)
├── archive.php # Archive safety redirect
├── 404.php # 404 or redirect based on setting
├── index.php # Required WP fallback
├── assets/
│ └── js/
│ └── featured-image-url.js # Block editor Featured Image URL panel
└── inc/
├── cleanup.php # wp_head cleanup, XML-RPC disable, admin menu cleanup
├── login.php # Custom login URL slug
├── meta.php # Per-post route behavior + featured image URL meta
├── onboarding.php # First-run headless setup wizard
├── options.php # Mainframe Settings page + Customizer
├── redirects.php # Public frontend redirect logic
├── rest-reference.php # Live REST API Reference admin page
├── rest.php # REST API exposure + CORS + all extra REST fields + site endpoint + deploy webhook
└── updater.php # GitHub Releases auto-updater
GPL-2.0-or-later — same as WordPress.