If you're coming from WordPress
Flexweg CMS borrows a lot of WordPress's vocabulary and patterns — posts, pages, categories, tags, menus, themes, plugins, hooks, filters, actions, must-use plugins, settings, media library. If you've used WP, the admin will feel familiar.
But the underlying model is fundamentally different. This page calls out the differences so you don't trip on assumptions.
What's the same
| Concept | WordPress | Flexweg CMS |
|---|---|---|
| Posts vs Pages | ✓ | ✓ — same distinction |
| Categories + Tags | ✓ | ✓ — both available |
| Block editor | Gutenberg | Tiptap (similar UX, different internals) |
| Themes | ✓ | ✓ — pluggable, switchable |
| Plugins | ✓ | ✓ — register filters/actions |
| Must-use plugins | ✓ | ✓ — mu-plugins/ folder |
| Hooks (filters/actions) | add_filter, add_action | pluginApi.addFilter, addAction |
| Menus | ✓ | ✓ — header + footer, hierarchical |
| Media library | ✓ | ✓ — with auto-resized variants |
| Multi-user | ✓ | ✓ — admin / editor roles |
| i18n | ✓ | ✓ — admin in 7 languages, content per-site |
| RSS feeds | Built-in | Plugin (flexweg-rss) |
| Sitemaps | Built-in (since WP 5.5) | Plugin (flexweg-sitemaps) |
What's different
Publishing is explicit
In WordPress, every page request hits the database, runs PHP, and renders HTML on the fly. Caching plugins layer on top to make it faster, but the underlying model is dynamic.
In Flexweg CMS, the public site is fully static. Clicking Publish generates HTML files and uploads them to Flexweg. Visitors download those files directly — no PHP, no MySQL, no admin code running.
Implications:
- Changes don't appear instantly on the public site. You publish, the admin generates files, the new content is live.
- Listing pages need cascade regeneration. When you publish a post, the home page and category archive that include it must also be re-rendered. The admin does this automatically.
- Slug changes mean URL changes and the admin deletes the old file. WordPress would have served both via redirects; Flexweg CMS expects you to commit to a slug.
No PHP
There's no wp-content, no themes/twentytwenty/functions.php, no wp-config.php, no theme template hierarchy, no the_loop(). Themes are React components that produce HTML strings; plugins are JavaScript that registers callbacks.
If you've written a WP theme: instead of single.php you write SingleTemplate.tsx. Instead of the_content() you receive bodyHtml as a prop. Instead of wp_head() you emit a <meta name="x-cms-head-extra" /> sentinel that plugins fill in.
If you've written a WP plugin: instead of add_filter('the_content', $cb) you write pluginApi.addFilter('post.html.body', cb). The mental model is the same; the API surface is different.
See:
No MySQL
Content lives in Firebase Firestore, a document database. Posts, pages, terms, media metadata, settings, plugin configs — everything is in there.
Implications:
- No SQL queries. The admin uses Firestore SDK methods (
getDoc,setDoc,query,where). - No
$wpdb. Plugins read content via the runtime API:fetchAllPosts(),getDb(), etc. - Schema-less but the project enforces a TypeScript schema (see Firestore data model).
- Backups via Firestore export (or third-party tools), not
mysqldump.
No wp-content/plugins/ upload
WordPress lets you drop a plugin folder into wp-content/plugins/ and activate it. Flexweg CMS plugins are TypeScript modules that ship as pre-compiled JavaScript bundles (ZIP files). You install them via the admin's Install plugin button or by dropping the unzipped folder into /admin/plugins/<id>/ on Flexweg manually.
Built-in plugins are bundled with the admin's build pipeline (scripts/build-bundled-externals.mjs). You don't deal with that directly — it just runs when you npm run build.
See Installing external plugins.
Authentication via Firebase
WordPress has its own user table and login form. Flexweg CMS delegates to Firebase Authentication — you create users in the Firebase Console (Email/Password provider) and they log in via the admin's standard email + password form. The admin never sees raw passwords; Firebase handles authentication.
That means:
- Password resets via Firebase Console → Authentication → Users
- 2FA is supported via Firebase's MFA flow (configure in the Firebase Console)
- No "lost password" email is sent by the admin — that's a Firebase concern
Roles are simpler
WP has 6 default roles (Subscriber → Administrator) plus capabilities. Flexweg CMS has two roles: admin and editor. Admins can manage other users; editors can edit content.
There's also a bootstrap admin convention — the email pinned in the Firestore rules (typically your own email) is treated as admin even without a Firestore user record. This solves the chicken-and-egg of the first login. See Users and roles.
Menus are dynamic, not baked
In WordPress, your menu changes are baked into the HTML of every page on the next page render. In Flexweg CMS, menus live in a tiny /menu.json file at the site root. Every published page includes a <script> that fetches that JSON on load and fills in the menu containers.
So changing a menu doesn't require re-publishing every page — the change is live the moment you save in the admin. See Menus.
No comments engine
WordPress ships with a comments table and wp_list_comments() template tag. Flexweg CMS has no native comments — the public site is static, so there's no backend to receive comment POSTs.
Add comments via a third-party service (Disqus, Giscus, Cusdis, …) by injecting their script through the flexweg-custom-code plugin's body-end zone.
Media library: variants, not originals
WordPress keeps the original upload + auto-generated thumbnails (thumbnail, medium, large, full). Flexweg CMS keeps only the resized variants declared by the active theme (e.g. small, medium, large, plus admin-thumb and admin-preview for the library UI). The original is discarded after upload.
Implications:
- You can't get the original back — keep your high-res source files elsewhere if you need them.
- Switching themes to one with a larger variant doesn't regenerate the missing format. The
pickFormat(view, "huge")helper falls back to the next-largest available variant (no broken images). - Storage is smaller — typically 20-30% of what WordPress would use for the same library.
Search is a plugin
WordPress has built-in search (?s=foo). Flexweg CMS has the flexweg-search plugin which generates a search-index.json file at publish time and ships a search modal that loads it client-side. Works great up to ~5000 posts; for bigger sites integrate Algolia or similar via custom code.
Multi-language admin out of the box
Flexweg CMS ships with the admin UI translated to 7 languages (English, French, German, Spanish, Dutch, Portuguese, Korean) — each user picks their own preferred language from the topbar. WordPress requires plugins (Polylang, WPML) for that.
Public site content is mono-language per site. If you need multi-language content, run two Flexweg sites (one per language) sharing the same Firebase project.
Updates are manual
WordPress has built-in core/plugin/theme updates via the admin. Flexweg CMS doesn't — you build the new admin yourself (or download a release), upload the new dist/admin/ to Flexweg, and the new code is live.
User state (installed plugins/themes, content, settings) survives the upgrade because it's all in Firestore. See Updating the admin.
Translation table for WP folks
| WordPress | Flexweg CMS |
|---|---|
wp_head() | <meta name="x-cms-head-extra" /> sentinel + page.head.extra filter |
wp_footer() | <script type="application/x-cms-body-end" /> sentinel + page.body.end filter |
the_content() | <div dangerouslySetInnerHTML={{ __html: bodyHtml }} /> in your theme template |
register_taxonomy() | Built-in: category + tag (no custom taxonomies in v1) |
register_post_type() | Built-in: post + page (no custom post types in v1) |
add_action('init', cb) | n/a — just register your filters/actions in the plugin's register(api) callback |
is_single(), is_page(), is_category() | The publisher passes the right template based on type — no conditional logic in your theme |
wp_enqueue_style() | BaseLayout declares <link rel="stylesheet" href="/theme-assets/<id>.css"> once for the whole theme |
wp_enqueue_script() | Theme manifest's jsText / jsTextPosts fields, or plugin page.body.end filter |
wp_options table | Firestore settings/site doc + per-plugin settings.pluginConfigs.<id> |
wp_postmeta | Firestore posts/{id} doc fields directly |
wp_users | Firebase Authentication users + Firestore users/{uid} for app-level role/profile |
wp_login_form() | The admin's <LoginPage> — auto-rendered when not signed in |
Continue reading
- Quick start — deploy now
- Creating plugins — port a WP plugin's logic
- Creating themes — port a WP theme