Minor Changes
-
#4117
d9705adThanks @khoinguyenpham04! - Updates the visual-editing rich text editor's selection toolbar and slash menu to match the admin editor's.Selecting text now shows the admin's toolbar: a Turn into menu, link, bold, italic, underline, strikethrough, inline code, and a More menu with subscript, superscript, alignment, and Clear formatting. With the caret in a link, a bar shows where it goes, with buttons to edit or remove it, and clicking a link no longer opens it in a new tab. A typed bare domain such as
example.comlinks tohttps://example.com, and a link the editor can't use is refused with a message.The slash menu now groups its commands, shows each one's Markdown shortcut and description, ranks matches as you type (
/blfinds Bullet List), and finds Heading 4 to 6 by name. Headings 4 to 6 now show at their own size while editing instead of as Heading 1. Both menus follow the system's light or dark theme and stay readable in dark mode. -
#4137
9b11a80Thanks @khoinguyenpham04! - Adds an image picker and an image toolbar to the visual-editing rich text editor.Typing
/imagenow opens a picker below the line instead of a full-screen dialog. It shows the media library nine images a page, newest first, with a tab for each media provider the site has set up. One click, or the arrow keys and Enter, inserts an image.The picker no longer uploads images. To use a new image, upload it to the media library in the admin first, then choose it with
/image. When the library is empty, the picker links to the media library, which opens in a new tab.Images now show as they publish, with their alignment and a caption field right underneath. Selecting one shows a toolbar to replace it, edit its alt text, or delete it. Replacing an image keeps its caption and takes the new image's alt text.
Typing
/imagenow opens a picker only in the editor it was typed in, not in every editor on the page, and an inserted image no longer gets its file name as its alt text. An image inserted from a line inside a quote or list now goes after the quote or list; saving used to drop an image inside one. -
#4068
4fc0cedThanks @danielmlr! - Addsemdash migrate --take-over-lock <id>, which applies pending migrations under a stuck D1 migration lock without freeing it first. Releasing the lock with--release-lockand then runningemdash migratelet a Worker inautomode take the free lock in between, and the apply failed with a lock error.pnpm emdash migrate --take-over-lock 1788264000000
Like
--release-lock, it asks you to confirm the target, or needs--expected-target-fingerprintin a non-interactive shell. -
#4136
83fe26dThanks @khoinguyenpham04! - Adds readable revision history to the content editor. Selecting a version in the Revisions panel shows it in place, read-only, with added text highlighted and removed text struck through in the color of the person who saved it. The selected version lists its changes in plain words, such as "Changed the title", and selecting one scrolls to it. Restore moves to a bar at the bottom of the editor, and a version that matches an earlier one is marked "Restored from" with that version's date. Save and Publish pause while a version is open. The panel no longer shows raw JSON.The revisions list API (
GET /_emdash/api/content/{collection}/{id}/revisions) and the MCPrevision_listtool include each revision'sauthoras{ id, name }, ornullwhen no author was recorded or the account no longer exists, so editors without access to user management can see who saved each version.ContentEditor'sonRevisionRestoredcallback can now return a promise. The restore stays pending until it settles, and the restore dialog shows the error if it rejects. When a host passes this callback, the restored version stays on screen untilitemcarries the restored draft, and the writer can't go back to editing before then. The editor still refreshes the entry'scontentandrevisionsqueries, so a host that readsitemfrom the entry'scontentquery needs no change. Other hosts must reloaditemfrom the callback. -
#3623
49319ebThanks @swissky! - Adds a Disallow AI training option to Settings > SEO. When it is on,/robots.txtdisallows the AI training crawlers and opt-out tokensAmazonbot,Applebot-Extended,Bytespider,CCBot,ClaudeBot,Google-Extended,GPTBot, andmeta-externalagent, and adds aContent-Signal: search=yes, ai-train=noline. Search engine access stays as your other rules define it. The rules are added to both the default and a customrobots.txt. The option is also available asseo.disallowAiTrainingin the settings API and the MCPsettings_updatetool. It is off by default, so existingrobots.txtoutput is unchanged.
Patch Changes
-
#4034
d7f2b8aThanks @danielmlr! - Fixesemdash whoamifailing on sites behind Cloudflare Access: it now sends the headers from--headerandEMDASH_HEADERSand readsEMDASH_URL, like the other commands. Without a token on localhost, it now signs in through the dev bypass and reports the dev user, and exits 1 when no site answers or the site isn't running underastro dev, so scripts can use it as a reachability check. -
#3955
291e0f8Thanks @khoinguyenpham04! - Fixes<CommentForm>making right-to-left pages (<html dir="rtl">) about 10,000 pixels wider, with a horizontal scrollbar. This happened on every such page in Firefox, and in Chrome and Safari when the form or an element around it is positioned, for example withposition: relative. On right-to-left pages,<Comments>now also indents threaded replies from the right, and the separator before a signed-in commenter's email in<CommentForm>no longer touches the email. -
#3907
bf046caThanks @emdashbot! - ReducesgetComments()to a single database query when the approved comment list fits on one page.Pages rendering comments previously ran a
COUNTquery followed by the list query, even when the result was not truncated.getComments()now fetches the list first and derives the total from the returned rows whenever cursor pagination indicates there are no more pages, falling back toCOUNTonly for posts that exceed the page limit. Cache misses on sites without an object cache benefit most. -
#4133
ae6c1a4Thanks @ascorbic! - Fixes Contributors being unable to save changes to their own drafts. A Contributor could create an entry, but the next save, the entry lock, and setting categories or tags returned 403. Contributors can now edit their own entries while they are drafts, in the admin, the REST API, and the MCPcontent_updatetool. They still cannot edit entries once they are published or scheduled, or publish them.The new
content:edit_own_draftspermission grants this and is held by Contributor and every higher role. No other role's permissions change. -
#3937
9ba9753Thanks @swissky! - Fixes slower page loads for logged-out visitors on D1 withsession: "auto"or"primary-first"and nocoalesce. A session runs a request's queries one at a time, so EmDash's preload of site settings, menus, widget areas, and taxonomy terms for HTML pages delayed the page's own first query and added queries the page might never use. In that setup EmDash no longer preloads them; those queries run only when the page uses them. Sites withcoalesce: truekeep the preload, batched into one round trip.New Cloudflare sites from the templates now use
d1({ binding: "DB", session: "auto", coalesce: true }), so reads issued together share one D1 round trip, with or without read replication. To get the same on an existing site, setsession: "auto"(or"primary-first") andcoalesce: truein itsd1()call;coalescehas no effect without a session.coalesceis experimental: a read and a write started together may run write-first, so await a read before a write that must not affect it. -
#4171
3116dbeThanks @ascorbic! - Fixes the admin dashboard for users withoutcontent:read_drafts, such as Subscribers. Recent activity and collection counts now include only published entries, matching what these users can open elsewhere in the admin, and scheduled-publication policy notices are no longer shown to them. Contributors and above see the dashboard as before. -
#4128
e7414c2Thanks @swissky! - On sites that use a route cache such as Workers Cache, creating or duplicating an entry no longer purges cached pages. The new entry is a draft that no public page shows, so pages that list the collection stay cached until a later change to published content. This applies toPOST /_emdash/api/content/{collection},POST /_emdash/api/content/{collection}/{id}/duplicate, and thecontent_createandcontent_duplicateMCP tools.content_createwithstatus: "published"still purges the collection and the new entry. -
#4000
20b7177Thanks @emdashbot! - Fixes first-user passkey registration so the newly created Admin is authenticated immediately after registration verification. Clicking "Open the dashboard" now opens the dashboard instead of redirecting to the login page. -
#4073
55adb91Thanks @emdashbot! - Fixes force-deleting a collection so it also removes the entries' records in shared tables: revisions, SEO data, comments, content bylines, taxonomy assignments, entry locks, and the revision prune queue. Previously these rows were left behind because only the content table, FTS table, and collection row were dropped. -
#4045
13293c5Thanks @emdashbot! - Fixes site settings updates during a running export so the export'swrite_epochbump happens inside the same transaction. If the export fence cannot record the write, the settings update now rolls back instead of committing invisibly to the export. -
#3824
bd0095eThanks @khoinguyenpham04! - Fixes MCPcontent_createandcontent_updaterejecting writes that omit a blocks field with "must be an array". A create that omits a blocks field treats it as empty, and an update keeps the stored blocks. Supplied non-array values are still rejected. -
#3885
02d57deThanks @emdashbot! - Fix plugin storage cursor behavior when the anchor row of an ordered page is deleted.PluginStorageRepository.query()now encodes scalar ordered field values into the cursor and compares subsequent pages against those values, so ascending walks no longer end early and descending walks no longer restart from the top. Cursors for non-scalar values (objects or arrays) or very long strings fall back to the anchor-row lookup used before this fix, keeping those pages working while still surfacing a clear invalid-cursor error when that anchor row has been deleted. Legacy cursors that do not contain the field values still work under the same fallback. -
#3825
52dfe3fThanks @khoinguyenpham04! - Fixes content writes to single-type blocks fields rejecting blocks that omit_type, including nested Markdown conversion over MCP and the TypeScript client. EmDash infers the only allowed type on ordinary writes; multiple allowed types, seed files, and stored blocks of a different type still require an explicit_type. Explicit invalid types remain errors. -
#3942
57e6507Thanks @khoinguyenpham04! - Fixes the visual-editing rich text editor adding a revision to the entry's history every time it saves on blur. Its saves now replace the pending draft revision, like the admin editor's autosave. -
#3982
8e1e849Thanks @emdashbot! - Fixes new and changed redirects being ignored for pages already in the Workers Cache. Saving a redirect for an exact path now clears the cached page at that path. -
#4102
d98e381Thanks @emdashbot! - Fixes the MCPcontent_createtool so it accepts optionalseoandpublishedAtfields and persists them in the same call. Agents no longer need to create a post and then callcontent_updatejust to set SEO metadata or backdate an item.publishedAtis gated behind thecontent:publish_anypermission, matching the REST API andcontent_update. -
#3791
dd983cdThanks @DiogoDuart3! - Fixes two problems with number fields in the content editor. Clearing a number field now saves it as empty instead of0, which previously failed a field's minimum (such asmin: 1) or silently stored zero. Decimal values such as4.50no longer block the Save button with the browser's "nearest valid values" message, whileintegerfields keep stepping in whole numbers. -
#3202
24d5ea4Thanks @eisenbruch! - Fixes full-text search so queries containing lowercase words like "and", "or", "not" or "near" are no longer parsed as FTS5 operators. This restores results for searches with possessive apostrophes and keeps prefix matching for those queries. -
#4092
a7ff1bfThanks @danielmlr! - Fixes WordPress imports through the EmDash Exporter plugin losing Advanced Custom Fields values: a field stored on only one post was never created, and a repeater row that only one post had was lost. For ACF field groups assigned to a post type, the import now creates a field for every ACF field and stores selects, checkboxes, repeaters, groups, relationships and other structured values as JSON. Relationship, post object, user and taxonomy values that arrive as WordPress posts, users or terms keep only their IDs, so a related post's content and password or a user's password hash are not copied into EmDash. A collection from an earlier import keeps the fields it has, so its repeaters still arrive as one field per row and sub-field.What should I do?
Running the import again skips entries that already exist, so if you imported ACF relationship, post object or user fields through the exporter with an earlier version, check those JSON fields for whole WordPress posts and users yourself.
-
#988
0ce875fThanks @masonjames! - Fixes MCP tool names for plugins with scoped IDs such as@acme/calendar: their tools are listed asacme__calendar__<tool>, which MCP clients accept. Update saved calls that use the old scoped names. Plugins with plain IDs keep their names, and the admin plugin list displays the generated names before consent.A plugin tool whose JSON Schema cannot be read no longer stops other tools from loading. Unreadable input schemas accept object inputs for route validation; unreadable output schemas are omitted. Duplicate generated names keep the first tool and log a warning instead of disabling the MCP endpoint.
-
#3584
2652774Thanks @swissky! - Removes up to two database round trips from the first request after a cold start on sites with the plugin marketplace or registry enabled.With the sandbox disabled (
sandbox: false), a failure to load the in-process plugin adapter now stops startup with an error instead of silently starting without any marketplace plugins, matching how sandboxed plugins listed in the integration config already behave. -
#4005
ecbd1ffThanks @saariuslystoned! - Fixes server-rendered pages that inspect public plugin route metadata before dispatching, so logged-out visitors can load plugin data. Public metadata is available throughgetPublicPluginRouteMetafor both visitors and signed-in users. Private route metadata stays on the signed-ingetPluginRouteMetahelper. -
#2768
9daecceThanks @saariuslystoned! - Fixes scheduled maintenance stopping after the initialization request in Cloudflareastro dev. EmDash now drives its own maintenance through a dev-only workerd route from the long-lived dev server, keeping plugin cron jobs, scheduled publishing, cleanup, and declared plugin storage index creation running between requests without invoking application-wide scheduled jobs. Also works with HTTPS dev servers, including self-signed certificates. -
#4053
cea0841Thanks @danielmlr! - Fixes thedefaultRoleandroleMappinghover text for external auth providers and Cloudflare Access, which called role 30 "Editor" and role 40 "Developer". Role 30 is Author and can publish its own content, and role 40 is Editor. Only the descriptions and examples changed, not how roles resolve.What should I do?
If you set
defaultRoleor aroleMappingentry to30because the hover text said Editor, check that Author access is what you meant. Use20(Contributor) for people who should not publish. The AccessdefaultRolestill defaults to30. -
#3997
2ccc645Thanks @bimsonz! - Fixes Astro's route cache, such as Workers Cache, storing a page at a redirect's source after the redirect was saved. A Worker isolate that had not yet reloaded its redirect rules could render the old page and the route cache kept it, even after the cached page was purged. Before the route cache stores a response, the redirect middleware now checks that its rules are current and answers with the redirect when they have changed. This costs one database read for each response the route cache stores; other responses are unchanged. -
#4097
651aaf9Thanks @emdashbot! - Fixes oversized plugin screenshots failing to load in the admin registry gallery with HTTP 413.The registry artifact proxy now requests the
feed_fullsizeimage preset for screenshots instead offeed_thumbnail. This aligns the admin preview with the public plugin site and avoids the lossless upscaling that pushed modest source images over the 1 MiB proxy cap. -
#4072
4e458bbThanks @danielmlr! - Fixes the datetime normalization migration taking hours on large Cloudflare D1 sites and starting over after every interruption. It now writes changed rows in batches, and runningemdash migrateagain after a failed write continues from the last written batch. -
#4044
e0d395dThanks @emdashbot! - Fix saving a draft when retired fields remain only in the published content row. A full read-then-write update no longer fails validation for keys the entry already stores in its live data but that are absent from the current draft revision. Genuinely never-stored unknown keys are still rejected. -
#3987
1dd1b37Thanks @danielmlr! - Fixes saves in the admin and through MCP reporting an error when the configured cache provider fails to purge, although the change was already saved. These saves now report success, and the failed purge is logged. -
#3921
2b7ad41Thanks @emdashbot! - Fixesemdash seedto avoid leaking duplicate media rows and adds--skip-mediafor storage-less seeds.$mediareferences now deduplicate against existing ready media rows by content hash, so re-runningemdash seed ... --on-conflict=updatereuses the same rows instead of creating new ones each time.- New
--skip-mediaflag resolves$mediareferences to external URLs without downloading files or creating local./uploads, which is useful when seeding a site whose media lives in remote object storage.
-
#4014
b60ec26Thanks @khoinguyenpham04! - Fixes visual editing selecting all of a text field's text when it is clicked, which made the first keystroke replace the whole value. A click now places the caret where it lands, including on a field inside a link, and text selected by dragging stays selected. -
#3942
57e6507Thanks @khoinguyenpham04! - Fixes visual editing for empty Portable Text fields, such as the body of a new post. Edit mode now renders the inline editor for them, so the field can be written on the page instead of having no editor at all. The empty editor stays blank until it is clicked, then shows a "Type / for commands..." hint. In edit mode, an empty Portable Text field reads as an empty array instead ofnull. -
#4058
7167d89Thanks @swissky! - Warns duringastro buildandastro devwhen the installed@emdash-cms/cloudflareversion differs fromemdash. The adapter depends on the exactemdashversion it was released with, so upgrading only one of the two packages installs a second copy ofemdashthat the adapter runs against. The warning names both versions and the command to install matching ones. -
#3265
b358838Thanks @eisenbruch! - FixesgetEmDashCollection'swherefilter so an object with none ofgt,gte,ltorlte(such as{ in: [...] }) now logs a warning instead of silently returning unfiltered rows. Long array values no longer fail on Cloudflare D1 with "too many SQL variables": the longest arrays are each bound as one JSON parameter, keeping ordering and paging in the database on SQLite, D1 and PostgreSQL. Values bound that way compare as text. -
#4056
3e58e0eThanks @swissky! - Fixes WordPress WXR imports overcounting skipped items. The "skipped (already exists)" count in the admin, and theskippedvalue returned byPOST /_emdash/api/import/wordpress/execute, now only include entries whose slug already exists in the target collection and locale. Menu items, reusable blocks (imported as sections), revisions and post types you disabled are no longer counted. The "X of Y" progress shown during the import no longer runs past the number of selected items. -
#4113
ab47586Thanks @emdashbot! - Send an identifyingUser-Agent: EmDash-Importer/1.0header on all WordPress plugin importer requests. Without this header, hosts that block requests with an empty User-Agent (such as ScalaHosting's bot filter) returned 403 during analyze and media import. The plugin API fetcher now adds the header to every request, and direct media downloads fromfetchMediasend the same value. This matches the existing media import route behavior. -
#4150
f414d0aThanks @swissky! - Explains why the WordPress exporter import can't connect when Cloudflare's bot protection answers with a challenge page. The URL check and the analyze and import steps now say that bot protection blocked the request and how to let it through, instead of reporting that WordPress couldn't be detected or that the analysis failed. In this case the analyze and execute endpoints return theWP_SITE_CHALLENGEerror code with status 502, and probe results includechallenged: true. -
#4150
f414d0aThanks @swissky! - Stops a WordPress exporter import with the bot-protection message when Cloudflare challenges a request for taxonomies, menus, site settings, or the media list. Previously the import skipped that part without telling you and finished without it. The analyze and execute endpoints now returnWP_SITE_CHALLENGEwith status 502 in these cases too. -
#3892
5053e19Thanks @swissky! - Fixes the WordPress export file (WXR) import in the admin keeping links to the old WordPress domain in imported content. Links to the source site are now rewritten to root-relative paths. The WordPress permalink structure is kept (for example/2024/05/my-post/), so add redirects if your routes differ. Links to uploaded files stay absolute, including in raw HTML blocks, which the WordPress plugin import previously turned into broken relative/wp-content/links. Links inside reusable blocks imported as sections are not changed.The import analysis now also shows the real site title and address instead of a generic "WordPress Site" heading. For programmatic use,
parseWxrStringnow returns the export's site metadata (it was always empty before), and content fetched throughwxrSourcegets the same link rewriting. -
#4090
d93b130Thanks @danielmlr! - Fixes WordPress XML imports creatingacf_fieldandacf_field_groupcollections full of serialized field settings on sites that use Advanced Custom Fields. The admin import now skips ACF's own post types, as it already does for WordPress internals such as revisions and menu items. Collections created by earlier imports stay in place; delete them in the admin if you don't need them. -
#4123
a065c0eThanks @emdashbot! - Fixesmedia:afterUploadhooks being silently dropped for REST API multipart uploads and MCPmedia_createuploads on Cloudflare Workers. Both upload paths now defer hook execution throughafter(), matching the admin confirm flow so hooks that perform async work complete as expected. -
Updated dependencies [
6524921,f9dda4d,bda9024,bda9024,e8bd98e,5c8ab6b,443ca3b,ce9e405,983c08c,ae6c1a4,d4e0af9,718c1f2,19fbdd6,302e011,3511069,82627fd,9d89ded,67360d1,af92bf7,75ef2ab,58ac71c,15bbfd7,e0363c3,7a3667b,0861e24,3195f42,01e1f12,dd983cd,be9378c,0ce875f,624390b,bf92be8,5b227af,83fe26d,fae844f,dd216db,649cfae,49319eb,dd3121c,db8325a,7659466,d4efb97,20ed58b,0e8811d,f414d0a]:- @emdash-cms/admin@1.3.0
- @emdash-cms/auth@1.3.0
- @emdash-cms/gutenberg-to-portable-text@1.3.0
- @emdash-cms/registry-client@0.7.1
- @emdash-cms/blocks@1.3.0