Minor Changes
-
#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. -
#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. -
#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(). -
#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
-
#3120
71901fcThanks @ascorbic! - AddsregistryLoader()for reading the moderated EmDash plugin registry through Astro live content collections. Collection loads support free-text and exact publisher/package searches, capability filters, and limits. Single-entry loads resolve a publisher handle or DID and include the latest visible release when one exists.Registry searches recognize exact handles, DIDs, and identity/slug pairs. Package views include the publisher's current verified handle when available.