October 12, 2026

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: widget areas, per-post caching and static exports to S3
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 sidebar area. Themes declare more in justflows-theme.json (widgetAreas) or place one with the core.widget-area template block. Plugins add areas through the widgets.areas filter.
  • Agents can manage it too, through the management API and the widget_areas_* and widget_layout_update MCP 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: true to return CSRF, rate-limit and dispatcher errors as { error: { code, message } }.
  • Neutral API URLs. Declare an apiNamespace and build links with ctx.http.url() or the SDK's pluginApiUrl() 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.quotas can set limits for another workspace or site, ctx.users.removeRole() takes back a role a plugin gave, and tenancy.workspaceCreated names 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 (0043 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.
Read the full changelog