Minor Changes
-
#4695
22b87edThanks @khoinguyenpham04! - Adds real-time co-editing to the admin editor, off by default for now. WithcoEditing: true, in collections with revisions, several people can edit an entry at the same time: each person sees the others' avatars, the field each one is in and their cursors in rich text, concurrent rich-text edits merge, and undo reverts only your own edits. A room for each entry saves everyone's edits together, with the same permissions, validation, hooks and revisions as other saves, and changes from the REST API, MCP, the CLI, the visual editor and plugins appear in open editors.Without co-editing, opening an entry takes an edit lock, and everyone else can only Open read-only or Take over. With it on, edit locks apply only in single-editor mode, which the editor uses, for example, in collections without revisions, on hosts that can't run rooms, and in browsers that can't open WebSocket connections. Admin tabs opened before co-editing is turned on keep single-editor mode until they reload. Edit with other people describes what editors see.
- Saved appears once a save holds all of your own edits. After 5 seconds without a connection, the button shows Offline: typing continues and merges on reconnect, and Preview, publishing and the other operations are disabled until then.
- Publishing, scheduling, unpublishing, discarding changes, restoring a version and moving to Trash first save everyone's edits, then run.
- Fields other than rich text keep one of two simultaneous changes, except bylines, reference fields and taxonomy terms, which keep what each person adds and removes. Turn into, dragging, deleting, wrapping, joining or indenting a block loses text someone else types inside it at the same moment.
- Edits that can't merge are saved as a recovery revision.
Public pages run no new queries, with co-editing on or off. Where rooms run, opening an entry adds a join request and a WebSocket connection. A room saves 2 seconds after the latest change, and at least every 10 seconds while people keep typing, however many people edit. Measured on Node.js with an in-memory SQLite database, saving a 2,000-word entry took 2.27 ms of CPU time at the median (3.83 ms at the 95th percentile), about two thirds of it the site's own draft save. Merging a write from outside the editor took 0.80 to 1.19 ms, and a notification with nothing to merge 0.67 ms (CPU time).
What should I do?
Nothing, unless you want co-editing. To turn it on, set the option and rebuild and deploy the site:
emdash({ coEditing: true, });
On Node.js with
@astrojs/node, in standalone or middleware mode, co-editing runs in the site's own process and the site starts as before. Check these cases first, which Co-editing on Node.js covers in detail:- A reverse proxy must forward WebSocket upgrades on
/_emdash/api/content/{collection}/{id}/room. Otherwise each browser session falls back to single-editor mode after three failed connections, and the server logs a[co-editing]warning. - Several processes on one database: route each room path to one process, for example by hashing the path, or leave co-editing off.
node:clusterworkers, including PM2's cluster mode, keep single-editor mode.- Another WebSocket handler on the same server must leave room paths alone. Replace a
wsserver created with{ server },express-wsor@fastify/websocketwith awsserver created withnoServer: truethat handles only your paths, or leave co-editing off. - On Node.js 22.16 to 22.20, 23, and 24.0 to 24.8, a server whose only
upgradelistener is EmDash's answers upgrade requests on other paths, such asUpgrade: h2c, with404. Node.js 22.21 and later, and 24.9 and later, serve them as before. - While rooms are open, EmDash handles
SIGTERMandSIGINTand stops the process up to 5 seconds later. Give process managers a kill timeout over 5 seconds, such as PM2'skill_timeout, which defaults to 1.6 seconds.
On Cloudflare, sites created from this release's templates already have the binding and the room exports, so they need only
coEditing: true. For an existing site, also add the binding and theexportsentry towrangler.jsonc, as Co-editing on Cloudflare shows:A Worker that uses the
migrationsarray, which can't be combined withexports, adds{ "tag": "emdash-rooms", "new_sqlite_classes": ["EmDashRoom"] }to it instead. Then re-export the room classes from the Worker entry point:import handler, { createScheduledHandler, EmDashRoom, EmDashRoomSite, PluginBridge, } from "@emdash-cms/cloudflare/worker"; export { EmDashRoom, EmDashRoomSite, PluginBridge }; export default { ...handler, scheduled: createScheduledHandler(), } satisfies ExportedHandler;
A Worker with its own
fetchcallshandleRoomUpgrade(request, env)first and returns its response when it isn'tnull. A site without the binding keeps single-editor mode.Changes for API clients and plugins
These apply only on sites with co-editing on.
- While people co-edit an entry, it has no edit lock: writes that got
409 ENTRY_LOCKEDland and merge into the editor. Writers that send_revcan get409 CONFLICTmore often, because the room saves every few seconds, and MCP publish, unpublish, schedule and discard conflict while the room holds unsaved edits. - The lock route's
POSTdeletes user locks while a room holds the entry, instead of granting one. - A room saves as one user, the actor, but a save can hold several people's edits. A hook that checks the actor's role before accepting content should check everyone in
editedBy. In a room,content:beforeSavehooks get only the fields a save changed, as they do for visual-editor saves. - While a room is open, editor-draft requests skip the
baseRevisioncheck, and the editor refuses a plugin's patch only when a field it changes was edited after the capture.
These apply on every site, with co-editing on or off:
PUT /_emdash/api/content/{collection}/{id}takes an optionalexpectedobject with thebylinesand reference selections (references, every page of a field) that a change was based on, as a read returned them.POST /_emdash/api/content/{collection}/{id}/terms/{taxonomy}takes an optionalexpectedlist of term ids. When a stored list differs, nothing is written and the request fails with409 LIST_CONFLICT: read the entry again, apply the change to the list it holds now, and retry. Requests withoutexpectedwork as before.POST /_emdash/api/content/{collection}/{id}/roomjoins an entry's room. It needs a signed-in session rather than an API token, and answers{ "mode": "single-editor" }unless the site runs co-editing.- A migration adds the
_emdash_roomsand_emdash_room_updatestables, which hold co-editing rooms. Site transfer never copies them. GETandPOST /_emdash/api/content/{collection}/{id}/lockresponses include aroomfield, which istrueonly while a co-editing room holds the entry.
Patch Changes
-
#4555
b14c091Thanks @emdash-autofix! - Fixes Cloudflare Access logins without identity-provider groups, such as one-time PIN logins, failing whenroleMappingis configured. These logins now receivedefaultRole(30 if not configured). -
#4582
2abe403Thanks @swissky! - Fixes@emdash-cms/cloudflareinstalling its own copy ofemdashwhen the site has a differentemdashversion. The adapter now declaresemdashas a peer dependency with its exact matching version, so a site always has a singleemdash.Keep both packages on the same version and update them together:
npm install emdash@latest @emdash-cms/cloudflare@latest
A project with mismatched versions now fails
npm installwith anERESOLVEerror instead of installing a second copy. pnpm and Yarn print a peer dependency warning, andastro buildandastro devwarn as before. -
#4716
b766773Thanks @emdash-autofix! - Fixes the preview path for Cloudflare Stream videos stuck at0:00in the Media Library detail panel. The panel uses Stream's hosted player instead of the browser's native HLS/DASH support. Stream preview URLs ending in/watchare converted to the embeddable/iframeURL. Media providers can supply an optionalplayerUrlfor admin video previews; providers without one keep native video playback.If you use Astro's content security policy, allow the Stream player host in
frame-srcso the preview can load. EmDash's default admin policy already allows HTTPS frames. -
#3115
daa6164Thanks @dchaudhari7177! - Fixesemdash migrateon D1 failing with no hint about how to select a database. When neither--d1nor--wrangler-configis given, the error now names both options and the binding the build expects, and says that a Wrangler config in the project root is not read unless it is named. -
#4308
4df65ebThanks @khoinguyenpham04! - Updates@cloudflare/kumoto 2.15.0. These packages now require exactly@cloudflare/kumo@2.15.0, so sites and plugins that install Kumo themselves should upgrade to that version to match the admin's prebuilt styles. Updateemdashand the Forms, Field Kit, AI Moderation and AI Search plugins together: their earlier releases require Kumo 2.6.0, and these plugin releases need anemdashrelease with this change. Updating only one side leaves Kumo 2.15 next to an admin built for 2.6.In the admin, Select menus now open below their field instead of over it, and text sized
text-baseortext-lghas a taller line (1.5), so labels and headings take slightly more height. Kumo'sDropdownMenunow spaces item icons itself, so plugin admin pages should remove any margin classes such asme-2from the elements they pass as an item'sicon. -
#4668
4d4165cThanks @khoinguyenpham04! - Fixesctx.content.delete()in sandboxed plugins, on Cloudflare and with@emdash-cms/sandbox-workerd, so it moves entries to Trash as the admin does. Sites with anobjectCacheno longer keep serving the trashed entry from the cache, and the entry'supdated_atand_revno longer change. -
#4712
9e3e34dThanks @emdash-autofix! - Raises the default sandbox subrequest allowance from 10 to 30 per invocation, allowing plugins to make more settings, storage, and HTTP bridge calls before reaching the budget. On Cloudflare, 30 bridge calls is the maximum permitted by the platform's Worker-invocation cap. The Node.js workerd runner uses the same default value but does not enforce subrequest limits.CPU and wall-time limits are unchanged, and existing explicit
limits.subrequestsoverrides still apply. No configuration changes are required.To retain the previous ceiling, supply a custom runner module through
emdash({ sandboxRunner: ... }). Its exportedcreateSandboxRunnerfactory must passlimits: { subrequests: 10 }in theSandboxOptionssupplied toCloudflareSandboxRunnerorcreateSandboxRunnerfrom@emdash-cms/cloudflare/sandbox. These are runtime-only APIs; neithersandbox()noremdash()accepts a directlimitsoption. See the custom runner example. -
#4277
26528bbThanks @emdash-autofix! - Fixesctx.kvvalues disappearing when a plugin with the same ID moves betweenplugins: []andsandboxed: []. This storage migration requires a coordinated upgrade; it cannot be deployed as a rolling upgrade.Both sandbox runners share the in-process KV store. A database migration copies existing non-
settings:sandbox values and revisions without overwriting in-process values, warns about conflicting plugin IDs and keys, and retains the original sandbox rows for recovery. Uninstalling a marketplace or registry plugin with data deletion removes its shared KV values and settings; failed-install rollback preserves existing data, including partial writes made by an installation hook before it fails. Plugin-test KV fixtures and inspection use the shared store; use the settings inspection API forsettings:keys.What should I do?
- Back up the database.
- Stop all application instances and plugin jobs that write to it.
- Upgrade
emdashand your sandbox runner together, then build the site without starting it. - Run
pnpm emdash migrateagainst the reviewed database target. - Resolve conflicting KV copies before plugins can write again.
- Start or deploy the upgraded site.
Do not rely on default
automigrations for this upgrade: they start plugins immediately after migrating, leaving no conflict-review window. Mixed package versions are not automatically rejected. See the plugin upgrade guide for target selection, recovery queries, and rollback instructions. Downgrading packages alone hides post-upgrade KV writes; restoring the pre-upgrade database backup discards all site changes made since it was taken. -
Updated dependencies [
c108e7b,1eedf99,118dcf9,32eab5c,40e4400,e79a7ff,7169073,2abe403,22b87ed,e8ba1b8,39a9ccb,007f71c,b766773,ddc4e82,638cbaf,678a0c2,7d48e95,18844a7,8418def,fbddae0,f5aab79,5137be5,679ad6c,612569b,b331cf3,a17a4b3,786fd09,97aa98a,f8462af,55273d7,53b4f32,55964d5,235a11a,4ccc279,ab2c40b,98e0924,e30df1e,caca761,7159eda,9699645,dd4ae19,82eb778,73c9b70,3f9c77b,4d4165c,e25d2ee,5a9f1bd,6331f6b,19c4f28,fc44f40,df0f952,60fef17,b9fc5f2,dfbb9fc,5605cec,9aa2259,b528fc0,752cbcd,b8886d9,c095458,8f397e0,b436669,4cb8305,006cfc0,4d4165c,0c57c94,56c801c,aef09ae,1f46f0d,c9696af,4d4165c,9db2de8,1357172,2c2a1e8,532e78b,5845694,284fa80,c9dffb7,2f64c3c,25574da,34a8e5a,3a13512,1cf644e,fac4008,c54ca68,e21e01e,f298866,dafb551,3d569ab,6ee8a5f,d541993,34a6816,88a61f8,0008289,a7cf38f,76c1936,222658c,c2792d5,064b566,96945d3,26528bb,a107170,c77bfb1,776c95e,44daa08,746db9f,652d957]:- emdash@1.5.0