emdash@0.39.0
Minor Changes
-
#3180
6e151efThanks @ascorbic! - Adds binary-safectx.http.fetch()behavior to sandboxed plugins on Cloudflare Worker Loader and Node/workerd. Request and response bodies are buffered with an 8 MiB decoded limit, and the returned WHATWGResponsepreserves bytes, status text, headers, final URL, redirect state, and clones across both runners.Redirected requests follow Fetch method and body rules. The Node/workerd runner also applies the installed version's current network capability and host list immediately after a plugin update.
Reading binary responses
Read bytes from the buffered response with the standard Response API:
const response = await ctx.http!.fetch("https://api.example.com/report"); const bytes = new Uint8Array(await response.arrayBuffer());
Testing external HTTP
createPluginRuntimeTestHost()addshttp.respond(),http.requests(), andhttp.clear()for deterministic production-bridge tests:await host.http.respond("https://api.example.com/report", new Response(new Uint8Array([0, 255]))); await host.transport.invokeRoute("import-report"); expect(host.http.requests()).toContainEqual( expect.objectContaining({ url: "https://api.example.com/report" }), );
-
#3188
4fef109Thanks @ascorbic! - Adds saved-entry panels and actions for sandboxed plugins. Declare collection-filteredadmin.editorPanelsandadmin.editorActionsentries that point to private plugin routes.Panels load Block Kit only when an editor opens them. Actions support confirmation and can return a toast, request an entry refresh, or navigate through a structured link target. EmDash reloads and ownership-authorizes the saved entry before invocation, then exposes only its canonical identity, locale, and version through
routeCtx.ui; unsaved editor values never cross the sandbox boundary.createPluginRuntimeTestHost()includes panel and action helpers that exercise the production authorization, response-validation, and Worker Loader path. -
#3171
80ccfafThanks @ascorbic! - Adds capability-gated schema, translation, public URL, and content revision discovery for plugins.Declare
schema:readto list collection and field definitions throughctx.schema. Existingcontent:readaccess can inspect safe content identity, discover locale siblings withgetTranslations(), and resolve published routes withgetPublicUrl(). Public URL resolution follows the site's collection pattern, locale routing, and trailing-slash policy and returnsnullfor content without a public route.Revision snapshots require the separate
content:revisions:readcapability because retained history can contain field values that an administrator removed later. This capability implies ordinarycontent:readaccess. Installation and plugin updates show both new authorities for consent, and the native, Cloudflare Worker Loader, and Node.js workerd runtimes expose the same methods. -
#3184
46784e1Thanks @ascorbic! - Adds capability-gated redirect access for sandboxed plugins. Declareredirects:readto list redirect rules with cursor pagination and read a rule with an opaque_rev. Declareredirects:writeto create, update, and delete redirect rules; write access implies read access and installation consent states that the plugin can change where visitors are sent.Redirect mutations use EmDash's redirect validation and cache invalidation path. Writes are serialized across runtimes so duplicate-source and loop validation use a consistent rule graph. The expanded redirect schema remains compatible with writes from previous host processes during rolling deployments. Loop validation runs when a rule is created or its source or destination changes; enabled-only updates retain the host API's existing behavior. Updates and deletes require the latest
_rev, reject concurrent changes withCONFLICT, and do not let plugins set the host-owned automatic redirect marker. The Cloudflare Worker Loader and Node.js workerd runners expose the same API, andcreatePluginRuntimeTestHost()includes redirect fixtures and inspection for production-boundary tests. -
#3145
f6bf82fThanks @ascorbic! - Updates plugin discovery to show only the registry. Sites with an enabledsandboxRunneruse the hosted aggregator athttps://registry.emdashcms.comby default. The new top-levelregistryoption accepts a registry URL or configuration object, whileregistry: falsedisables registry discovery and registry-installed plugins without disabling the sandbox runner.The former
experimental.registrylocation is deprecated but remains supported when the top-level option is omitted. A top-level value takes precedence.The
marketplaceintegration option is deprecated but remains supported for plugins already installed from Marketplace. Those plugins continue to run and can still be updated or uninstalled from Plugins. Marketplace browse and install pages are hidden, and configured sites display a migration guide banner.What should I do?
Move an existing
experimental.registryvalue to the top-levelregistryoption. The deprecated location continues to work during the pre-1.0 compatibility period.Set
registry: falseif the site needs its sandbox runner but should not load registry-installed plugins or expose registry discovery.Keep
marketplaceconfigured while any installed Marketplace plugin still needs updates. Replace or uninstall those plugins, then remove the option by following the Marketplace migration guide. -
#3170
3538bb8Thanks @ascorbic! - Addscomments:readandcomments:moderatefor sandboxed plugins.ctx.commentscan get, count, and cursor-page through non-trashed comments, and can change a comment betweenapproved,pending, andspamwhen the caller supplies the status it previously observed.comments:readexposes comment bodies, author names and email addresses, pseudonymous IP hashes, user agents, and moderation metadata. It does not expose the linked EmDash user-account ID.comments:moderateimplies that read access, and installation or an update that requests either capability requires operator consent.Status changes use the core moderation path. A stale expected status rejects with
COMMENT_STATUS_CONFLICT, and an overlapping transition can reject withCOMMENT_MODERATION_IN_PROGRESS; a successful transition runscomment:afterModerateonce with the calling plugin's origin and preserves approval notifications. Hard deletion and bulk status replacement are not included. -
#3251
dbd77efThanks @ascorbic! - Adds explicit, consented access to selected unsaved content for sandboxed editor panels and actions.Plugins can request
admin.editor-draft:readto receive extension-selected field values after an editor invokes them, andadmin.editor-draft:patchto propose atomic whole-fieldsetorclearoperations. Patch access does not imply read access. Each extension must declare explicit collection scope and narrow its access to field slugs, translatable fields, or both.EmDash authenticates and authorizes the saved entry, reloads its schema and revision, validates snapshot and patch limits, and rejects stale or invalid responses. The admin shows a host-rendered before-and-after preview, applies accepted changes to the visible form, marks it dirty, and leaves saving to the editor. Panel load and ordinary typing do not expose draft data or invoke the plugin.
createPluginRuntimeTestHost()now provides draft capture and host-validated patch application helpers for production-boundary plugin tests. -
#3172
2818e66Thanks @ascorbic! - Adds separate sandboxed-plugin capabilities for reading media bytes and editing media metadata.Declare
media:bytes:readto usectx.media.readBytes(). Reads are available only for ready media, default to a 10 MiB limit, enforce the caller's limit while consuming the storage stream, and cannot request more than 16 MiB. The result includes the content hash; ordinarymedia:readmetadata excludes content hashes, storage keys, and author identity.Ready-media metadata URLs use an authenticated media ID route. Authenticated callers with the
media:readpermission can fetch the asset without receiving its storage key; logged-out requests are rejected before the route queries media.Declare
media:metadata:writeto usectx.media.updateMetadata()for alt text, captions, and focal points. This capability cannot upload, replace, move, or delete media. It does not implymedia:readormedia:bytes:read.@emdash-cms/plugin-testalso provides binary media fixtures and inspection through the runtime-backed host so plugin tests can exercise the production Worker Loader bridge. -
#3164
6ce67bbThanks @danielmlr! - Fixes concurrent core migrations on Cloudflare D1 failing partway with errors such astable "_plugin_storage" already exists.emdash migrateand runtime migrations inautomode take a migration lock in the D1 database. A second run waits up to 10 seconds: it succeeds without applying anything if the first run finishes in that time, and otherwise fails without applying migrations.A run that stops before releasing the lock leaves it held, because it may have stopped partway through a migration. This happens when a CI job is cancelled during
emdash migrate, when a Worker inautomode stops during a runtime migration, or when a development server is stopped while it applies migrations. Until the lock is released, pending migrations do not run and a D1 site inautomode fails to initialize EmDash. Once the lock is older than a minute, migration runs fail at once with the lock's time and id, and the runtime retries after its migration-failure backoff.Adds
emdash migrate --release-lock <id>to release such a lock.emdash migrate --statusreports the lock and its id. After confirming that no migration is running, release the lock with that id:pnpm emdash migrate --release-lock 1788264000000
Releasing the lock of a remote D1 database needs a build manifest and an API token with D1 Edit permission. A lock in the local D1 database of a development server is released with Wrangler. See Release a stuck migration lock for both procedures.
-
#2880
ad1dee2Thanks @danielmlr! - Adds the field constraints declared in a collection schema to the content editor, so authors see a limit before a save can fail on it.Text fields with
maxLengthshow a live character count below the input and stop accepting input at the limit; aminLengthis shown as a hint. Number fields withminormaxshow the allowed range and set it on the input. Content that is outside its bounds, such as text saved before a limit was lowered, is marked in the editor before a save is attempted.The admin manifest now carries a field's
validationobject for every field type. Previously only repeater, file and image fields exposed it, so length and range rules never reached the editor. Plugin field widgets for trusted plugins receive the samevalidationobject as a prop, so a custom widget can enforce the limits without hardcoding them. -
#3173
7aa12b3Thanks @ascorbic! - Addsctx.settingsfor plugin configuration and encrypts fields declared astype: "secret"before writing them to the database. Native plugins, Cloudflare Worker Loader plugins, and Node/workerd plugins share the same versioned AES-GCM envelope and plugin-scoped API.@emdash-cms/plugin-testcan update generated settings through the runtime host and inspect their raw persisted envelope.Set
EMDASH_ENCRYPTION_KEYin the runtime process environment before saving secret settings. A standalone Node server does not load.envautomatically. To rotate the key, place the new key first in a comma-separated list and retain old keys until every plugin secret has been saved again. EmDash does not currently report which key IDs remain in use, so track each resaved credential and verify its integration before removing an old key. Restores need both the database and every encryption key referenced by its stored envelopes.Cloudflare sites using
nodejs_compatwith a compatibility date before2025-04-01must also addnodejs_compat_populate_process_envbefore saving secrets through the generated admin form. Cloudflare enables that behavior by default for later compatibility dates.Existing plaintext secrets remain readable and are encrypted when saved again. The
ctx.kv.get("settings:<key>")compatibility alias remains available throughout the EmDash 0.x release line; new plugin code should usectx.settings.get("<key>").Only fields declared as
type: "secret"inadmin.settingsSchemause this encryption path. Arbitrary plugin KV and state values are unchanged; credentials stored by the bundled AT Protocol and webhook notifier plugins are not migrated by this release. -
#3120
71901fcThanks @ascorbic! - AddsGET /_emdash/api/healthso external tools can confirm an EmDash site is reachable and whether its plugin registry is enabled. The anonymous response does not query the database and permits cross-origin reads. -
#3194
1e13daaThanks @ascorbic! - Adds separately consented publication and restore actions to native and sandboxed plugin contexts.Plugins with
content:publishcan read an entry with an opaque revision and publish, unpublish, schedule, or unschedule it through the same runtime behavior as REST and MCP. Each mutation requires the revision returned by the read or preceding action, and a plugin cannot recursively run the same action for the same entry. The capability impliescontent:readbut notcontent:write.Plugins with
content:restorecan read and restore trashed entries without receiving ordinary content-read or write authority. Restore is revision-fenced and returns the next revision. Existing plugin installations receive neither capability unless a new version declares it and the administrator approves the expanded access. -
#3185
c029134Thanks @ascorbic! - Addshooks.content-policy:registerfor sandboxed and native plugins that need to inspect and reject publication, scheduling, or unpublication without receiving content read, write, or publication-action access.Policy plugins can register
content:beforePublish,content:beforeSchedule, andcontent:beforeUnpublish. Each event identifies the API, MCP, visual editor, plugin, scheduler, or system origin and includes the authenticated actor when one exists. Return{ cancel: true, reason }to reject the action with a stable error code. EmDash validates the reason as 1–500 plain-text characters. For allowed actions, the revision read before policy evaluation becomes the mutation precondition.Scheduled content runs
content:beforePublishagain when it becomes due. A policy rejection unschedules the entry, lists its public-safe reason and entry link on the dashboard, and avoids retrying the same permanent rejection on every scheduler tick. Successful rescheduling, publication, or deletion clears the record; administrators can dismiss stale records.@emdash-cms/plugin-testexposes stored scheduler rejections throughinspect.scheduledPolicyRejections(). -
#3190
6daffeaThanks @ascorbic! - Adds declared request and raw response contracts for sandboxed plugin routes across the native,
Cloudflare Worker Loader, and Node/workerd runtimes.Use
methodsto have the host reject other HTTP methods with405 Method Not Allowed. Use
request.bodywithjson,text,bytes,form-data, ornonefor bounded buffered parsing, and
list the safe request headers the handler needs. Undeclared routes retain their existing
method-agnostic JSON and query-string behavior.Routes with
response: "raw"returnpluginResponse()with an unwrapped text or byte body, status,
and allowlisted representation, download, or redirect headers. Raw responses are limited to 8 MiB.
The host removes all other plugin-supplied headers, applies the route's cache and browser security
policy, and rejects active same-origin content types.pluginRoute()infers a sandboxed handler's input from its declared body mode.
definePluginRoute()provides the equivalent inference for trusted native routes.
createPluginRuntimeTestHost()acceptsrawBodyfor testing the production request parser with
text, bytes, URL-encoded data, and multipart form data. -
#3162
a4af578Thanks @ascorbic! - AddscreatePluginRuntimeTestHost()for sandboxed plugin tests that must exercise EmDash orchestration instead of invoking an isolate directly. The host separates direct transport calls, fixtures, production actions, observable-state inspectors, scheduled time control, cold restart, and disposal.Runtime actions cover the shipped content lifecycle, plugin activation and deactivation, media upload, public comment submission, comment moderation, plugin-route policy, and scheduled task execution. The controlled scheduler clock applies to cron tasks and scheduled publishing.
restart()retains D1, plugin storage, media storage, and plugin state while replacing runtime and isolate memory. The host captures delivered email for assertions.createPluginTestHost()and its top-levelinvokeHook()andinvokeRoute()methods remain compatible for fast transport-level tests.emdashPluginTest()supplies the runtime modules required by the documented Vitest configuration. Generated plugin projects continue to use Worker Loader by default and describe Node/workerd parity as an opt-in test for runner-sensitive behavior. -
#3174
06bad83Thanks @ascorbic! - Adds structured Block Kit navigation and host-attested administrator locale context for sandboxed plugin pages and dashboard widgets.Plugins can return
linkelements that target saved content, another page declared by the same plugin, generated plugin settings, or an external HTTP, HTTPS, ormailto:URL. EmDash constructs internal admin URLs and opens external links withnoopener noreferrer. Links never dispatch block actions and cannot appear as form fields.Block Kit route handlers receive
routeCtx.uiwith the validated surface, administrator locale, and text direction. The host validates every sandboxed page and widget response before rendering it, rejects undeclared plugin-page targets and active URL protocols, and permits external images only over HTTPS to hosts declared inallowedHostsundernetwork:requestconsent or undernetwork:request:unrestrictedconsent. Responses are limited to 256 KiB, 20 levels, 2,000 nodes, 1,000 items per array, and 64 KiB per string.createPluginRuntimeTestHost()addsadmin.loadPage(),loadWidget(),act(), andsubmit()helpers that exercise the private production route, Worker Loader isolate, host UI context, and response validation.This is a breaking security tightening for sandboxed plugins that return an external Block Kit image without matching network authority. EmDash rejects the complete page or widget response instead of allowing the administrator's browser to contact an unapproved host.
What should I do?
If a plugin returns external Block Kit images, add
network:requestand every image hostname toallowedHosts, or addnetwork:request:unrestrictedwhen the plugin genuinely requires any hostname. Publish a plugin update so administrators can review and approve the expanded authority. Root-relative images need no manifest change. -
#3235
808f473Thanks @swissky! - Adds ascope=titleoption to the search API and thesearch()/searchCollection()helpers that restricts matching to each collection's title field instead of the full indexed text. -
#3182
70ab2f8Thanks @ascorbic! - Adds translation-aware sandboxed plugin content creation throughctx.content.create(collection, data, { locale, translationOf }).The source must be an active entry in the same collection. The new entry joins its translation group, inherits its byline credits and taxonomy assignments, and takes non-translatable field values from the source. Content validation and save hooks run in both the Cloudflare Worker Loader and Node/workerd runners. Save-hook-originated creates do not re-enter save hooks, and the creating plugin's own
content:afterSavehook is not re-entered.Each translation group permits one active entry per locale. Duplicate locale creates return
CONFLICT, missing sources returnNOT_FOUND, invalid or unconfigured locales returnVALIDATION_ERROR, and save hooks can returnSAVE_REJECTED. -
#3169
8ad06e9Thanks @ascorbic! - Adds thetaxonomies:writesandboxed-plugin capability for creating taxonomy terms and adding or removing term assignments throughctx.taxonomies.Assignment methods accept term row IDs or translation-group IDs and apply idempotent deltas, so they do not replace existing assignments and concurrent additions are preserved. EmDash validates collection attachment, entry existence, term ownership, configured locales, translation identity, and hierarchy before changing taxonomy state. Sandboxed
createTerm()rejectsparentIdfor a non-hierarchical taxonomy instead of ignoring it. The capability impliestaxonomies:readand requires renewed consent when an installed plugin first declares it.Existing REST and MCP term mutations also reject creating or updating a term with a parent in a non-hierarchical taxonomy. Callers that assign parents must mark the taxonomy as hierarchical before creating or reparenting terms.
This release includes migration
082_taxonomy_translation_locale_unique, which enforces one term per translation group and locale. If an existing database contains duplicate rows, the migration preserves them as independent term groups and copies their assignments before adding the unique index. It can restart safely after any completed statement.@emdash-cms/plugin-testadds taxonomy fixtures and an assignment inspector for production-boundary tests. Taxonomy definition management, assignment replacement, term updates, and term deletion remain unavailable to sandboxed plugins.
Patch Changes
-
#3068
5510725Thanks @logelog! - Reduces the CPU work needed to render the Archives widget on sites with many posts. Archive links, ordering, and post counts stay the same. -
#3272
fc4a7beThanks @ascorbic! - Fixes revision restore on Cloudflare D1 so the restored content and its audit revision commit atomically. If either write fails, the entry and its revision history remain unchanged. -
#3146
4ebd2a8Thanks @ascorbic! - Fixes datetime sorting and range queries by storing every content datetime as a UTC ISO string with fixed milliseconds. The admin converts date-and-time fields through the site's configured timezone, while API, MCP, and CLI writes now requireZor an explicit UTC offset.The core migration reports noncanonical values before changing them, then normalizes content columns and revision snapshots in bounded batches. Legacy values without an offset use the site timezone. If a value falls in a repeated or skipped daylight-saving hour, the migration stops before writing and reports the content row or revision that needs an explicit offset.
-
#3228
9bffbfaThanks @ascorbic! - Fixes content writes when a database schema contains a field type that the running EmDash version does not support. Entries remain readable, but the admin makes them read-only and content create or update requests returnUNSUPPORTED_FIELD_TYPEinstead of treating the unknown field as text and risking data loss.Deploy this release to every runtime before enabling a later EmDash feature that adds a new field type. Sites whose schemas use only supported field types require no action.
-
#3142
9c61f93Thanks @eisenbruch! - Fixes an entry with a pending draft becoming unsaveable after one of its fields is deleted. The draft revision stores the wholedata, so the deleted field's value stayed in it and every read handed it back; writing that data back was then refused withunknown field on collection, an explicitnullwas refused too, and omitting the key was accepted but left the merge carrying it, so no request body got the entry out of the state. An update now drops a key the collection has no field for when the entry already stores it, and a saved entry is written without those keys, so it sheds them. A key the entry does not already store is still reported as an unknown field. This applies to every content write, including those made from a plugin or a sandboxed plugin bridge. -
#3269
a10f9caThanks @ascorbic! - Fixes the generated OpenAPI document so publish, unpublish, and discard-draft include their optional request bodies, and restore-from-Trash describes the returned item and revision token. The MCPcontent_unscheduledescription now states that scheduled drafts return to draft while published entries stay published. -
#3271
dd885e5Thanks @ascorbic! - Fixesemdash export-seedchanging the database it exports. The command now opens the source database read-only and stops with instructions to runemdash migratewhen its schema is outdated, instead of applying pending migrations during an export. -
#2776
e9c4433Thanks @yet2come! - Fixesemdash export-seed --with-contentsoreferencefield values survive a round trip throughemdash seed. The export now names a reference's target by the seed id it assigns that entry, and writes a referenced collection before the collection pointing at it. Previously the export emitted the source database's row id, which the restored database does not carry: the literal$ref:<row-id>string was stored in the column, the restore reported success, and the reference was lost wherever it was rendered. -
#2776
e9c4433Thanks @yet2come! - Fixesemdash export-seedso its progress line goes to stderr and kysely'sorderBydeprecation notice is no longer triggered, allowingemdash export-seed > seed.jsonto write a file that parses as JSON. Previously the redirected file began withℹ Database: …andorderBy(array) is deprecated…, the command still exited0with an empty stderr, and the corruption surfaced only whenemdash seedrejected the file at restore time. -
#3144
222f329Thanks @ascorbic! - Fixesmenu_set_itemscreating replacement items without a translation group and repairs existing affected items, so seed exports retain each item's localization identity. -
#3248
3cec6f9Thanks @ascorbic! - Fixes registry plugins appearing in discovery but failing installation when their signed profiles predated repository metadata.Profiles without the optional repository extension permit releases without provenance. Manual publishing adds an available canonical HTTPS repository with optional provenance, preserves explicit profile policies on later releases, and refuses manual releases when the publisher requires provenance. EmDash routes installation verification correctly and shows site administrators actionable publisher guidance when signed records fail verification.
-
#2779
363dd56Thanks @danielmlr! - Fixes the image field type so a stored focal point is readable.focalXandfocalYreach content entries but were missing from the generated collection types, so reading them from an image field was a type error. The dark-variant slot shares the media shape, so its focal point is readable on the same terms. -
#3127
3533d2cThanks @eisenbruch! - Fixes MCP write tools leaving cached pages stale on sites with Astro route caching enabled (for examplecacheCloudflare()). A change made over MCP reached the database, but the cached page kept serving the old copy until its TTL expired. The tools now invalidate the same route-cache tags as the matching REST routes.Content tools
content_create,content_update,content_publish,content_unpublish,content_delete,content_restore,content_permanent_delete,content_schedule,content_unschedule,content_discard_draftandcontent_duplicateinvalidate the same tags as their REST routes.A
content_updatethat only stages a draft invalidates nothing, as over REST. Acontent_updatewith astatusstill invalidates when its publish or unpublish step fails, if the update step already changed live content.Taxonomy, menu and settings tools
taxonomy_create,taxonomy_update,taxonomy_delete,taxonomy_create_term,taxonomy_update_term,taxonomy_delete_term,menu_create,menu_update,menu_delete,menu_set_itemsandsettings_updateinvalidate their taxonomy, menu or site-settings cache tags. -
#3264
a6b9884Thanks @DavidPivert! - The MCPtaxonomy_update_termandtaxonomy_delete_termtools accept an optionallocale, so a term whose translations share a slug can be edited or deleted in one language without touching the others. Withoutlocale, the tools still act on the lowest matching locale, as before. -
#3285
71572baThanks @ascorbic! - Exposes the existing media upload handler on authenticated Astro request locals, matching the publishedEmDashHandlerscontract. -
#3096
f0e3817Thanks @dchaudhari7177! - Fixes media usage tracking ignoring images inside Portable Text gallery blocks. A media item used only in galleries showed an empty "Used in" list and looked unused; gallery images are now indexed asportable_text_imagereferences with a field path into the gallery (body[3].images[0].asset._ref). Indexes written before this fix are now reported as stale rather than complete, so gallery-only media no longer looks safe to delete until those indexes are rebuilt. -
#3230
27e9450Thanks @emdashbot! - Fixes theQueryOptions.limittype comment to match the documented and enforced maximum of 100 rows. Plugin storage queries were already clamped at 100; the previous comment incorrectly stated 1000. -
#3158
1fea699Thanks @danielmlr! - Fixes permanently deleting an entry leaving its byline credits in the database, where a seed that re-creates a slugless entry under the same ID picked them up. Credits left behind by entries deleted before this release are not removed. -
#3180
6e151efThanks @ascorbic! - Fixes repeat 404 hits unnecessarily running row-cap maintenance when consecutive requests share the same timestamp. -
#3285
71572baThanks @ascorbic! - Restores the existing OpenAPI, relation-definition, and content-reference HTTP endpoints in generated Astro sites. These handlers were shipped in the package but omitted from route injection, so requests returned Astro's generic404 Not Foundinstead of reaching the API. -
#3152
a823276Thanks @ascorbic! - Fixes standard sandboxed plugins so lifecycle, content, media, comment, email, cron, and page metadata hooks run through the same ordered, capability-gated host pipeline as trusted plugins on Cloudflare Workers and Node.js.Sandbox contexts now expose canonical capabilities, database-backed
ctx.cron, complete content metadata and filtering, and a realResponseshape fromctx.http.fetch(). Cloudflare response bodies still cross the bridge as text. Admin-managed settings now share thectx.kvsettings namespace, lifecycle hooks run once at the correct install/enable boundary, and uninstall cleanup runs before plugin data or bundles are removed.Plugin builds also preserve hook, route permission and cache, MCP, settings, and field-widget metadata in registry bundles and npm descriptors.
-
#3068
5510725Thanks @logelog! - Reduces duplicate database reads when widget areas render while layout prefetch is still running on remote database adapters. -
#3199
a487ae3Thanks @ascorbic! - Addsemdash/plugins/hostas a narrow runtime entry for platform sandbox adapters. The Cloudflare Worker loads scheduled maintenance and sandbox bridge dependencies when those capabilities first run, reducing startup CPU while preserving existing Worker exports and behavior. -
#3285
71572baThanks @ascorbic! - Fixes registry and marketplace installation failing after the plugin bundle and state were written because the request runtime did not expose plugin lifecycle hooks. Failed plugin updates now restore the previous state, remove the failed bundle, and reactivate the previous version after resynchronizing the runtime. Registry update and uninstall requests are also registered in generated Astro sites instead of returning404 Not Found.Registry consent now uses a neutral summary when a release has no build provenance, keeps record identifiers and publisher-policy mechanics under collapsed technical details, and shows the requested permission count with a scroll cue for longer lists.
-
#3128
b3433d1Thanks @eisenbruch! - Fixes pages failing withTypeError: Cannot read properties of undefined (reading 'set')when a preview link, an_editlink or a signed-in editor reaches a response Astro renders without a route-cache handle, such as the 404 page for a URL that matches no route. EmDash's middleware now skips the route-cache opt-out when there is no cache handle instead of throwing. -
#3162
a4af578Thanks @ascorbic! - Fixes ISO date-time plugin schedules being treated as recurring tasks when the cron parser accepts the timestamp. A successful one-shot task is now removed after it runs. -
#3235
808f473Thanks @swissky! - The search API now resolves the signed-in session, sostatus=draftqueries return draft content for users with the required permission instead of silently falling back to published results. Anonymous requests are unaffected. -
#3274
9ca2de5Thanks @ascorbic! - Fixes seed application overwriting admin-managed site settings. The defaultskipmode preserves existing settings such assite:titleandsite:taglinewhile filling in missing settings; useupdateto overwrite supplied settings orerrorto stop at the first conflict. -
#3236
26e035dThanks @ascorbic! - Adds a disposable R2 bucket toemdashPluginTest()so sandbox plugin tests can exercisectx.media.upload()andctx.media.delete()through the same Worker Loader bridge used in production.Fixes registry installation rejecting sandbox plugins whose manifests declare
content.publish,content.restore, orcontent.policyaccess. These permissions now survive bundle-manifest validation and reach the normal installation consent checks. -
#3221
dda36bfThanks @emdashbot! - Fixes 404 logging to enforce theMAX_404_LOG_ROWScap only when a new unique path is inserted. Repeat hits now skip the full-tableCOUNT(*), significantly reducing D1 row reads for sites that serve many repeated 404s. -
#3238
9e17b18Thanks @swissky! - Fix 404 logging on PostgreSQL. Recording a missed path failed withcolumn reference "hits" is ambiguous, so the 404 log stayed empty and hit counts never incremented on Postgres. The 404 Errors tab and its hit counters now populate correctly. -
#3290
7f1a49dThanks @ascorbic! - Fixes API tokens scoped only tosettings:readorsettings:managebeing accepted by the backup routes under/_emdash/api/settings/backups, including the full-site export. An API token now needs theadminscope to use any backup route; other tokens get a 403. Session sign-ins are unaffected.JSON backups also no longer include
emdash:site_url, the deployment URL recorded at setup. Site settings, title, tagline, and locale are still included. -
#3157
3030d09Thanks @danielmlr! - Fixes entries going live again after they were unpublished or restored from Trash.Unpublishing an entry cancels its pending schedule, so scheduled publishing no longer republishes it when the old time arrives. Restoring an entry from Trash returns it as a draft with no schedule, whatever its status was when it was trashed: a published entry is no longer public again the moment it is restored, and a schedule is dropped whether or not it has come due. This applies to Restore in the admin,
POST /_emdash/api/content/{collection}/{id}/restore, thecontent_restoreMCP tool,emdash content restore, andEmDashClient.restore(). Scripts and agents that restore an entry and expect it to be live must publish or schedule it afterwards.content:afterRestorehooks receive the entry withstatus: "draft". -
#3205
93df4e8Thanks @danielmlr! - Fixes the content editor reporting a failed save with the field's slug and the validator's wording, such asexcerpt: Too big: expected string to have <=160 characters. When a save, autosave, new entry or new translation fails field validation, the error toast now names each field by the label the editor shows and says what the field needs, for example "Summary can have at most 160 characters." -
Updated dependencies [
bdbe41c,71901fc,4fef109,80ccfaf,46784e1,f6bf82f,3538bb8,4ebd2a8,dbd77ef,9bffbfa,2818e66,4ebcb07,89bd85b,3ad2b50,ad1dee2,808f473,dc685eb,3cec6f9,2375b4a,6c23ff3,1e13daa,c029134,667f62e,a823276,6daffea,71572ba,563aa73,fc32ebf,06bad83,c783951,801a7ca,8ad06e9,93df4e8,ce1659c]:- @emdash-cms/admin@0.39.0
- @emdash-cms/registry-lexicons@0.6.0
- @emdash-cms/blocks@0.39.0
- @emdash-cms/plugin-types@0.4.0
- @emdash-cms/registry-verification@0.3.2
- @emdash-cms/registry-client@0.6.1
- @emdash-cms/auth@0.39.0
- @emdash-cms/gutenberg-to-portable-text@0.39.0