Justflows 0.3.5: widget areas, per-post caching and static exports to S3
Sidebars that themes and plugins can fill, Cache-Control per content type or post, static exports served from S3 or OVH, a real /account page, and a lot for plugin developers.

Justflows 0.3.5 is out. It is a big one: sidebars arrive, caching gets per-post control, static exports can live in object storage, every signed-in user gets a proper account page, and plugin developers get a stack of new APIs. Here are the highlights. The full changelog is on GitHub.
Widget areas: sidebars that themes and plugins can fill
Appearance → Widgets now shows every widget area at once. Drag widgets from the palette (Content, Menu, Recent posts, Search, and widgets from plugins) onto an area, reorder them, and expand one to edit its settings. Changes have draft and publish, and you can set replacements per language.
- Add and delete your own areas, and choose per content type which area shows and where: left, right, or above the content.
- Set the sidebar width and gap under Styles → Layout, site-wide or per layout target, or give one area its own width.
- Core ships a
sidebararea. Themes declare more injustflows-theme.json(widgetAreas) or place one with thecore.widget-areatemplate block. Plugins add areas through thewidgets.areasfilter. - Agents can manage it too, through the management API and the
widget_areas_*andwidget_layout_updateMCP tools.
Cache-Control per content type, or per post
Each content type, and each individual post under Advanced, can now inherit the site settings, turn caching off, or set its own validated
Cache-Control header. Per-post overrides follow drafts and revisions. The policy applies to live pages, shared page caches and static exports alike. Private types such as account pages have no override: they are never cached.Static exports straight to S3 or OVH
Tools → Static site export can now write to the object storage connection you already have (
STATIC_EXPORT_STORAGE_DRIVER=s3) instead of local files, without changing where media is stored. Each site gets its own export prefix based on its stable site id, so changing a domain keeps the exported files.- Pages can be served from a private bucket through your existing CDN origin. The app signs the S3 reads and streams pages with the right content type and cache headers, so a CDN such as Bunny keeps using the app as its origin.
- Admin, API and plugin routes, signed-in visits and query-string requests always go to the live app. A missing object falls back to live rendering.
- A new export is only switched live after every upload has finished. If an upload fails, the previous deployment stays in place.
- Exports now contain HTML only. Images, styles and scripts keep their existing URLs instead of being copied into the bucket.
- Exports run in the background and report progress, so a long crawl no longer times out at a proxy or CDN.
- Exported responses carry
X-Justflows-Render: static-export, so you can tell them apart from live renders.
A real account page at /account
Every signed-in user now has an account page at
/account with their profile and sections from active plugins. Shop, for example, adds customer details, orders and subscription actions. You can design the page in the page builder like any other content, while personal data stays per request and the page is kept out of caches, search, sitemaps and static exports. Workspace owners also see their workspaces, sites and limits there. The old /platform-account URL redirects.Measure storage, and cap it
Diagnostics and Platform → Website now show how much storage each site uses (media, private files and static exports), both local and external. A new Total storage limit (
storage.bytes) caps local plus external usage per website. Existing files stay when you lower a limit; new uploads are refused once it is reached.For plugin developers
- Durable events.
ctx.hooks.emit(name, payload, { durable: true })persists webhook deliveries before listeners run, so failures can be retried. - Structured errors. Routes can opt into
structuredErrors: trueto return CSRF, rate-limit and dispatcher errors as{ error: { code, message } }. - Neutral API URLs. Declare an
apiNamespaceand build links withctx.http.url()or the SDK'spluginApiUrl()to expose/api/<namespace>/…. - More admin pages. A plugin can register up to 40 admin routes instead of 20, and
settings/…routes are linked from its Settings page. - Platform tools.
ctx.quotascan set limits for another workspace or site,ctx.users.removeRole()takes back a role a plugin gave, andtenancy.workspaceCreatednames a new workspace's first administrator.
// Persist the delivery before isolated listeners run, so it can be retried
await ctx.hooks.emit("order.paid", payload, { durable: true });
Fixes worth knowing about
- PostgreSQL times no longer drift. On a server that wasn't running in UTC, times without a zone were shifted by the server's offset. They are now UTC, as on MySQL and MariaDB. The Docker images were not affected.
- Separate plugin databases now keep UTC timestamps too, so Shop backups moved between databases pass their checksums.
- Permalinks for lists, menus, search, sitemaps and exports are generated in batches instead of one query per link, along with batched comment moderation, revision pruning and trash checks.
- A domain's TXT challenge can be verified after its nameservers have switched.
Upgrading
This release adds four database migrations (
Read the full changelog0043 to 0046) for storage quota locks, workspace owners, account pages and per-type cache control. Read the full release notes before you upgrade, especially if you run PostgreSQL on a server that is not set to UTC.✌️
Thanks for reading!
Find more posts on the blog or star Justflows on GitHub.