Skip to main content

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

ConceptWordPressFlexweg CMS
Posts vs Pages✓ — same distinction
Categories + Tags✓ — both available
Block editorGutenbergTiptap (similar UX, different internals)
Themes✓ — pluggable, switchable
Plugins✓ — register filters/actions
Must-use plugins✓ — mu-plugins/ folder
Hooks (filters/actions)add_filter, add_actionpluginApi.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 feedsBuilt-inPlugin (flexweg-rss)
SitemapsBuilt-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.

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

WordPressFlexweg 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 tableFirestore settings/site doc + per-plugin settings.pluginConfigs.<id>
wp_postmetaFirestore posts/{id} doc fields directly
wp_usersFirebase 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