Posts
A post in Flexweg CMS is a piece of dated content with a category and tags — typically a blog article, news item, case study or release note. Posts appear in listing pages (home, category archives, search results) sorted by publication date.
For pages without a date / category context (e.g. About, Contact, Privacy Policy), use Pages instead.
Posts list
Open Posts in the sidebar. You see a paginated list with columns:
- Title — clickable, opens the editor
- Status — Draft / Online (with a colored badge)
- Category — primary category if set, otherwise empty
- Author — display name resolved from
users/{uid} - Date — most recent of (publishedAt → updatedAt → createdAt)
Above the list:
- Search — multi-token substring match on title + slug + excerpt. Case-insensitive. Doesn't search post bodies (would be too slow client-side).
- Filter tabs — All / Draft / Online — filters by status
- New post button — opens the editor with an empty post
Below the list:
- Pagination — 25 posts per page by default. Adjust in Settings → General → Performance.
Selecting one or more posts via the row checkboxes activates bulk actions:
- Publish (re-publishes the selected drafts)
- Unpublish (puts the selected online posts back to draft, deletes their HTML)
- Delete (removes from Firestore + deletes their HTML)
Creating a post
Click New post. The editor opens with an empty post.
Top bar
- Status badge (Draft / Online) — current state
- Save button — saves the draft to Firestore without publishing
- Publish / Update — publishes (writes the HTML to Flexweg) or re-publishes if already online
- More menu — Unpublish, Delete
Editor area
The main editor is a block-based Tiptap-powered WYSIWYG. You write the post body here:
- Type / hit Enter to create a paragraph
- Press / to open the block inserter (paragraph, heading, image, list, quote, code, embed, custom HTML, columns, theme blocks)
- Highlight text → bubble menu offers bold, italic, underline, link, etc.
- Each top-level block has a hover toolbar (top-right of the block) with move up / move down / duplicate / delete actions
See Editor overview for details on each block type.
Right sidebar
Two tabs: Document (post-level metadata) and Block (selected block's properties).
Document tab
- Title — what users see (the slug auto-derives)
- Slug — the URL segment. Auto-generated from the title; click to edit. The admin validates collisions live (see URL structure)
- Excerpt — short summary (used by RSS, OG tags, listing pages)
- Status — Draft (not yet published) or Online (published)
- Publication date — defaults to "now" on first publish; editable
- Primary category — picks a single category. Determines the post's URL:
/<category-slug>/<post-slug>.html. Optional — without it, the URL is/<post-slug>.html. - Tags — multiple. Tags don't affect URLs but are surfaced by themes and used by
flexweg-searchfor filtering. - Author — defaults to you; admins can reassign to another user
- Hero image — picked from the media library. Themes use this for the OG image, listing card cover, and post header.
- SEO:
- SEO title — overrides the
<title>tag (defaults to post title) - SEO description — overrides the
<meta description>(defaults to excerpt) - OG image — overrides the hero for Open Graph (defaults to hero)
- SEO title — overrides the
Block tab
Shows the inspector for the currently-selected block. Each block type has its own inspector. For example:
- A heading has level (H1-H6)
- An image has alt text, caption, link target, format picker
- A custom HTML block has a CodeMirror editor for editing the HTML
- Theme blocks have their own custom inspectors
If your cursor is on a paragraph (no inspector), the Block tab is hidden / shows "no settings".
Saving and publishing
Save draft
Click Save. The post is written to Firestore as status: "draft". No HTML is uploaded. The post appears in the list with a Draft badge.
First publish
Click Publish. The admin runs a sequence:
- Validates — title and slug are non-empty, slug is unique
- Renders the body — markdown → safe HTML via marked + DOMPurify
- Applies plugin filters —
post.markdown.before,post.html.body,post.template.propsget a chance to transform the post - Renders the page — wraps the body in the active theme's
BaseLayout+SingleTemplate, runs throughreact-dom/server.renderToStaticMarkup - Hashes the result —
sha256(html). If the hash matcheslastPublishedHash, skips the upload (no changes since last publish) - Uploads —
POST /files/uploadwith the HTML at<category-slug>/<post-slug>.html - Records — Firestore updates:
status: "online",publishedAt: now,lastPublishedPath,lastPublishedHash - Cascades — re-publishes the home page (because the latest-posts list changed) + every category archive that lists this post + plugin outputs (sitemaps, RSS, search index)
- Fires
publish.completeaction — plugins likeflexweg-rsslisten here - Toasts —
Published to /<category-slug>/<post-slug>.html
You see a publish log in the bottom of the editor with each step's status.
Re-publish
Once a post is online, the Publish button becomes Update. Clicking it runs the same flow — but if nothing actually changed, the upload is skipped (hash matches).
Slug change
If you change the slug or primary category, the URL changes. The admin deletes the old file before uploading the new one. If the deletion fails (transient API error), the path is recorded in previousPublishedPaths[] and retried on the next publish.
This means:
- Your URLs stay tidy — no orphan
.htmlfiles - Visitors with the old URL bookmarked get a 404 (the file is deleted) — consider this a feature for SEO consistency, not a bug
See Stale path cleanup.
Unpublish
More menu → Unpublish. The admin deletes the HTML file from Flexweg, clears lastPublishedPath, sets status back to draft. Listings re-render to remove the entry.
The post's content stays in Firestore — it's only the public file that's removed. Re-publish anytime.
Delete
More menu → Delete. Confirms, then:
- Unpublishes (deletes the HTML if currently online)
- Removes the Firestore document
- Cascades the listings update
This is destructive — there's no trash can. Make sure you mean it.
Drafts vs scheduled publishing
There's no scheduled publishing in v1. You either:
- Save as draft (not visible publicly until you click Publish)
- Click Publish (immediately writes the HTML)
Plugin authors could implement a scheduled queue via publish.before + setTimeout, but it'd be brittle (browser closed = no scheduled publish). For real scheduling, integrate a separate cron service that calls Firestore via the API.
Importing posts
The flexweg-import must-use plugin can bulk-create posts from:
- A folder of markdown files (with YAML frontmatter for metadata)
- A WordPress XML export
See [Importing content].
Continue
- Pages — pages without dates / categories
- URL structure — slug rules and collision detection
- Editor overview — block-based editor in detail
- Categories and tags — taxonomy management
- How publishing works — the publish flow in depth