Flarum 2.0.0-rc.1 Released 🎉
We've made it. Nearly three years of development, eight betas, 397 merged PRs, and 30 contributors later — Flarum 2.0 is now at Release Candidate 1.
✅ Safe to use in production for early adopters. The path from here to stable is fully supported — upgrades from rc.1 through to 2.0.0 final will be smooth. Normal precautions apply: back up your database, test locally or on a staging environment first if you have one, and have a rollback plan.
👀 You're already running it. This very forum — discuss.flarum.org — has been updated to rc.1.
🧪 Want to try Flarum 2.0 first? Check out our nightly demo at nightly.flarum.site, rebuilt every day from the latest 2.x code.
🧭 What "Release Candidate" actually means
An RC is a release we think is ready to ship. It's feature-complete, the API is frozen, and if nothing critical turns up, rc.1 becomes 2.0.0 with no functional changes.
Here's the deal:
- No more breaking changes. The extension API is locked. A 2.x-compatible extension built against rc.1 will still work on 2.0.0 final.
- A clean upgrade path from here on. If you're on rc.1, getting to 2.0.0 will be a plain
composer update — no surprises.
- We'll move fast on regressions. Anything reported against rc.1 that blocks real-world use gets bumped to the top of the pile.
And here's where you come in — we need people running this on real forums. The more setups we see, the fewer surprises at 2.0.0.
- Install it. Locally, or on a staging environment if you have one — ideally against a copy of your real data.
- Use it like you use your forum. Post, reply, moderate, upload, search, customise. Try the extensions you rely on.
- Tell us how it went — good or bad. Bugs, upgrade friction, extensions that break, UI papercuts, weird slowdowns. Also: "it just worked" is genuinely useful to hear.
Where to report:
- Bugs in core — flarum/frameworkissues
- General feedback, questions, extension behaviour —Support
- Security issues —
security@flarum.org (please don't post publicly)
Not sure which bucket something belongs in? Post on discuss and we'll sort it.
✨ What's new in rc.1?
rc.1 is a polish-and-hardening release. Aside from a handful of small additions requested during the beta cycle, the focus is on performance, security, and fit-and-finish. The changes below were added since beta.8 was released.
🔒 Security hardening
A cluster of password-flow and session-management fixes land in rc.1.
- Password reset tokens are now expiry-checked on submission. Previously the expiry window was only validated when the reset form was rendered — an expired token could still complete a reset if submitted directly.
- Changing your password now invalidates all existing sessions. Every session, remember-me, and developer token for the user is dropped when the password changes.
- Requesting a new password reset deletes the stale one. Only a single valid reset token can exist for a user at any moment.
- Avatar-from-URL fetches are hardened. The Guzzle client used to retrieve remote avatars now disables redirects, enforces a short timeout, and rejects oversized responses before buffering them into memory.
- Logo, favicon and dark-logo uploads are now validated. Previously only avatars ran through the image validator — rc.1 introduces a shared
AbstractImageValidator and wires it into every image-upload controller, closing a gap where admin image endpoints accepted non-image uploads.
⚡ Performance
- Discussion-list N+1 queries eliminated. A cluster of N+1 patterns on the discussion list (tag permission subqueries, likes/groups eager loading, visibility guards on erasure requests and message counts) have been squashed — the forum index now issues roughly a quarter as many queries as in beta.8.
- New composite index on
notifications. Unread-count queries were becoming noticeably slow on large forums. A (user_id, is_deleted, read_at, type) composite index fixes it.
- Tag slugs resolve in one query.
TagFilter previously issued one query per slug; rc.1 batches them into a single whereIn.
- Frontend performance pass. Preconnect / DNS-prefetch hints on the document head,
fetchpriority applied to logo and preload hints, and a sessionStorage-based CSS loader that eliminates the flash of unstyled content that could occur on cold navigations. The viewport meta has also been relaxed so pinch-to-zoom is no longer blocked on mobile.
➕ Small additions
- Reset-settings button on extension admin pages. Each bundled extension's admin page now has a "Reset to defaults" button, backed by a new
DELETE /api/settings endpoint and a Settings\Event\Reset event that extensions can listen to for their own cleanup.
- HiDPI avatars. Uploaded avatars now generate
@2x and @3x variants automatically and the <Avatar> component renders a srcset, so avatars look crisp on retina and mobile displays.
- Abandoned-extensions sync runs weekly. The admin-facing abandoned-package warning system now re-syncs the flarum/abandoned-extensions list weekly, with a "Check Now" trigger available on demand and admin notifications when an installed extension becomes abandoned.
forum-widget extension category. A new category for widget-style extensions, sitting between themes and language packs in the extension list.
- Presence-channel authorization extender. The realtime extension gains a
Realtime::authorizePresenceChannel extender, letting extensions gate access to presence channels at the WebSocket auth layer.
🛠️ Fixes & polish
📱 Frontend & mobile
ReplyPlaceholder shows avatar and badges on mobile when composing.
- Back-navigation no longer loses loaded pages or scroll position —
PaginatedListState now compares params by value instead of reference.
UserCard controls icon restored so the mobile controls button is visible again.
- Admin nav dropdown no longer flickers on mobile.
- Admin StatusWidget layout fixed on mobile — items now flow in a grid and the tools button spans the row.
- FontAwesome Kit no longer overrides
display: none on hidden icons. Tag-select and subscription dropdown markup cleaned up in the process.
- Bottom page padding is no longer cut off in certain layouts.
- Radio groups no longer collide — multiple radio groups on the same page no longer share a
name and interfere with each other.
- Admin avatar-driver select now shows the correct default on fresh installs, and the abandoned-sync status message has proper padding.
🧭 Routing & URLs
- Trailing slashes on URLs no longer 404.
ResolveRoute now strips a trailing slash before dispatching, so /d/123-foo/ resolves the same as /d/123-foo.
- Extensions with malformed names no longer emit PHP warnings.
ExtensionManager now skips package names that lack the vendor/package format instead of crashing nameToId() with an undefined-array-key warning.
🏷️ Tags
bypassTagCounts now works for non-admins holding the permission — the check was using the admin-only shortcut where it should have been calling hasPermission.
- Tags homepage meta description now uses your forum description (falling back to the
All Tags translation only when no description is set).
📌 Sticky
- Ambiguous
id column in the is_unread_sticky subquery fixed — extensions that join on discussions (e.g. byobu) no longer break MySQL with an ambiguous-column error.
🔌 API & serialization
Attribute fields now correctly run through serializeValue — a long-standing bug where booleans like allowSignUp were emitted as strings has been fixed.
- Array values are preserved when an
Attribute with a Str cast returns an array, avoiding "Array to string conversion" errors in extensions.
Arr::serialize() now respects JsonSerializable objects like MessageBag.
💬 Community wording
- Replaced "community" with "forum" in a user-facing string for consistency.
🌱 The ecosystem is ready
rc.1 is the signal to the extension ecosystem: the API is stable. If you maintain an extension, now is the time to publish your 2.x-compatible release.
Language packs — @rob006 has been coordinating language pack updates. If you maintain a translation, please check in on those threads.
Friends of Flarum — the FoF catalogue has near-complete 2.x coverage. Thank you to everyone involved.
Extension developers — final compatibility blockers are being prioritised between rc.1 and stable. If something in core is standing between you and a release, open an issue.
🚀 Try it yourself
- Live demo — nightly.flarum.site, rebuilt daily
- Right here — discuss.flarum.org is running rc.1
- Update your install —
composer update -W, then php flarum migrate, php flarum cache:clear, and php flarum assets:publish (back up first, and test locally or on staging if you can)
💙 Thank you
Thanks to everyone who landed changes in this release, reported bugs, and tested the betas.
A special shout-out to @ClaudiusH — who has spent an enormous amount of time running the nightly builds, surfacing bugs big and small throughout the beta cycle, and tirelessly maintaining the German language pack. A huge chunk of the polish in this release traces back to his reports. Thank you, Claudius.
📋 Changelog
Added
- (core) reset-settings button and confirmation modal on extension admin pages, backed by
DELETE /api/settings and Settings\Event\Reset by @IanM #4522
- (core)
forum-widget extension category by @IanM #4543
- (core) HiDPI avatar srcset — generate @2x/@3x variants on upload and expose
avatarSrcset on the user resource by @IanM #4555
- (core) weekly sync of the abandoned-extensions list with admin notifications and manual "Check Now" trigger by @IanM #4560
- (realtime)
Realtime::authorizePresenceChannel extender for gating presence-channel access by @IanM #4573
Security
- (core) enforce expiry check on password reset token submission by @IanM #4550
- (core) invalidate active sessions when password is changed by @IanM #4551
- (core) delete stale password tokens when requesting a new reset by @IanM #4552
- (core) harden avatar-from-URL Guzzle client — disable redirects, add timeout and size limit by @IanM #4553
- (core) validate logo, favicon and dark-mode logo uploads via a shared
AbstractImageValidator by @luceos #4579
Fixed
- (core) suppress PHP warning when extension name lacks
vendor/package format by @IanM #4510
- (core) strip trailing slash from URI before route dispatch by @IanM #4511
- (core) incorrect bottom page padding by @IanM #4518
- (core)
Api\Serializer attribute fields now run through serializeValue so booleans no longer serialize as strings by @IanM #4530
- (core)
PaginatedListState::paramsChanged() uses value comparison so back-navigation preserves loaded pages and scroll by @IanM #4532
- (core) show avatar and badges in
ReplyPlaceholder composing state on mobile by @IanM #4533
- (core) preserve array values in
Attribute::serializeValue to avoid "Array to string conversion" for extensions returning arrays from Str-typed attributes by @IanM #4534
- (core) small correctness and accessibility fixes — serializer meta caching, queue exception handler,
@hasSection directive, Checkbox inputAttrs, FormGroup Switch aria-describedby by @IanM #4562
- (core) restore controls icon on
UserCard so the mobile controls button is visible by @IanM #4568
- (core) remove hardcoded radio input
name in FormGroup so multiple radio groups can coexist by @rafaucau #4571
- (core) prevent FontAwesome Kit from overriding
display on hidden icon elements by @IanM #4575
- (admin) improve
StatusWidget mobile layout on the dashboard by @IanM #4531
- (admin) avatar driver default value and abandoned-sync message padding by @IanM #4567
- (admin) nav dropdown flickers on mobile by @luceos #4578
- (sticky) qualify ambiguous
id column in is_unread_sticky subquery so extensions that join discussions no longer break by @IanM #4520
- (tags) use
hasPermission for bypassTagCounts so the permission works for non-admins on restricted tags by @IanM #4539
- (tags) use forum description for meta description on tags homepage by @IanM #4558
Changed
- (core) replace "community" wording with "forum" for consistency by @luceos #4580
- (webpack-config) remove deprecated Babel plugins from config by @rafaucau #4498
Performance
- (core) eliminate N+1 queries on the discussion list by @IanM #4508
- (core) add composite index on
notifications to fix slow unread-count queries by @IanM #4509
- (core) frontend performance improvements — preconnect hints,
fetchpriority, FOUC fix, viewport by @IanM #4561
- (tags) resolve tag slugs in a single batch query in
TagFilter by @IanM #4563