Minor Changes
-
#3335
0371107Thanks @ascorbic! - Adds multi-release fixtures toemdash/testing/registry, allowing browser and integration tests to exercise registry updates between authoritative package versions. Existing single-release fixtures continue to work unchanged. -
#3440
03b6b3bThanks @swissky! - Adds two settings to the Navigation section of the content type editor:- Icon: the Phosphor icon name shown for the collection in the admin sidebar and in command palette navigation, such as
calendar-blank. A sidebar folder shows the icon of the first collection in it that declares one. A name that does not resolve falls back to the collection's default icon. - Hide from navigation: removes the collection's sidebar entry, its command palette link, and its dashboard quick action. The collection stays reachable by URL, the API, and plugins. Collections that were already hidden now also drop out of the command palette.
API and seed files
The manifest now publishes each collection's
icon. Collection icon names are now trimmed and limited to 64 characters in the schema API and the MCP collection tools, and limited to 64 characters in seed files, so longer values are rejected. Sending an emptyiconclears the stored icon. - Icon: the Phosphor icon name shown for the collection in the admin sidebar and in command palette navigation, such as
-
#3440
03b6b3bThanks @swissky! - Adds anadmin.quickCreatecollection setting that removes the collection's "new entry" quick action from the admin dashboard. Set it tofalsein a seed file or through the schema API, or turn off "Quick action on the dashboard" in the content type editor's Navigation section. Collections without the setting keep their quick action. A schema API update replaces the wholeadminobject, so include any existingadmin.listColumnsin the same request. -
#1939
2410395Thanks @swissky! - Adds a core update notice to the admin dashboard. When a newer EmDash version is available, admins see a dismissible banner with a link to the release notes. The banner names the newest release that has been public on npm for at least 24 hours.The check is on by default: the server sends a GET request to
https://registry.npmjs.org/emdashat most once a day, in the background, with no site data. To wait longer before a release is announced, for example to match pnpm'sminimumReleaseAge, or to turn the check off:emdash({ updateCheck: { minimumReleaseAge: "7d" } }); // a duration string or seconds emdash({ updateCheck: false });
The banner reads
GET /_emdash/api/admin/core-update, which requires the newupdates:readpermission (admins only). -
#3059
c36d974Thanks @danielmlr! - Updates the MCP content tools and revision restore to honor an entry's edit lock, so an AI tool connected over MCP no longer overwrites an entry that someone else has open in the admin.content_update,content_delete,content_publish,content_unpublish,content_schedule,content_unschedule,content_discard_draftandrevision_restorefail withENTRY_LOCKEDwhile another user holds the entry's lock, where the call used to succeed. The error message names the holder, and_meta.detailscarries theiruserId,userName,acquiredAtandexpiresAt. Reading the item again does not clear the refusal. Each of these tools takes an optionaloverrideLock: trueto write anyway.POST /_emdash/api/revisions/{revisionId}/restorenow returns409 ENTRY_LOCKEDin the same case. Pass"overrideLock": truein the request body to restore anyway.To keep the previous behavior for a whole collection, switch edit locking off for it under Content Types → your collection → Edit locking.
-
#3394
38d200dThanks @ttmx! - Adds thebyline:afterSaveandbyline:afterDeleteplugin hooks, so plugins can keep external copies of author data, such as a search index, current when byline profiles change.Both hooks require the
bylines:readcapability and receive the same public byline profile thatctx.bylines.get()returns.byline:afterSaveruns after a byline or one of its translations is created or updated, withisNewset on creation.byline:afterDeletereceives the byline as it was before deletion. They run after changes made through the admin API or MCP tools, not seeds or imports, and hook errors are logged without undoing the change.Credit changes on an entry are still reported through
content:afterSave. Relinking a byline to another user can change the credits inferred for that user's entries without a per-entry event. -
#3394
38d200dThanks @ttmx! - Adds thebylines:readplugin capability, which lets plugins read public byline profiles and the bylines credited on content entries throughctx.bylines.ctx.bylinesprovidesget()and cursor-paginatedlist()for profiles, plusgetEntriesBylines()for credits.getEntriesBylines()resolves up to 100 entries of one collection in a single call, so a search indexer or feed plugin can attach author names to a page ofctx.content.list()results:const page = await ctx.content.list("posts", { limit: 100 }); const credits = await ctx.bylines.getEntriesBylines( "posts", page.items.map((entry) => entry.id), );
Credits match what the site renders: the credits assigned in the editor, or the author's linked byline, marked
source: "inferred", when an entry has none. They resolve at the entry's own locale. Profiles omit the linked user account, guest flag, and byline custom field values.The capability is independent of
content:readandusers:read. It is available to native plugins and to sandboxed plugins on Cloudflare Worker Loader and Node.js workerd. Installation and update consent list it as a new permission. -
#3501
4d6a87bThanks @ascorbic! - Removes the legacyemdash plugincommand group, including its marketplace login, logout, scaffold, validation, bundle, and publish commands.Use the dedicated
@emdash-cms/plugin-clipackage for sandboxed plugin authoring and registry publishing:pnpm add -D @emdash-cms/plugin-cli pnpm exec emdash-plugin --helpNative plugins are npm packages and continue to use their package build scripts.
-
#3495
9358edeThanks @ascorbic! - Addsadmin.footerLabelfor customizing or hiding the label beside the version in the admin sidebar. The label defaults to"EmDash"instead of reusing the configured site name. Set it to a string to use another label, or set it tofalseto show the version alone.
Patch Changes
-
#3493
148ff3eThanks @MA2153! - Fixes bulk term assignment only working with the built-intagtaxonomy. Editors can now add a term from any taxonomy, such as a category or a custom taxonomy, to up to 50 posts from a collection's bulk-actions bar or from that taxonomy's page. When several taxonomies apply to a collection, the dialog asks which one to use. ThePOST /_emdash/api/taxonomies/bulk-tagendpoint now accepts a term from any taxonomy, and matches only entries in the collections that use that taxonomy. -
43565efThanks @MA2153! - List endpoints now treat a zero or negativelimitas 1, matching the documented range of 1 to 100. Previously some lists, including public comment lists, could return more items than the maximum page size. -
#3517
2987a51Thanks @ascorbic! - Fixesemdash-env.d.tsnot updating when the schema changes duringastro devwith the Cloudflare adapter. Adding or editing collections and fields now regenerates types on Cloudflare too, not only on Node. -
#2390
2bce20cThanks @dashimu! - Fixes the admin UI remaining on "Loading EmDash..." when running EmDash from source in development on Windows. No configuration change is required. -
#2898
8b1b585Thanks @scottbuscemi! - Fixes rich text image settings so caption, alt text, tooltip, size, and alignment edits persist when authors click back into the post. Captions and tooltip titles also round-trip independently, so clearing a caption no longer restores it from the tooltip text. -
#3487
9f721fbThanks @ascorbic! - Fixes draft publication of a reference field from undoing link changes published from the opposite end of the relation in the meantime.Draft reference changes now merge with concurrent changes from either end of the relation. Additions and removals made in the draft take effect, while links changed only from the opposite end remain unchanged. Restoring a revision continues to replace the live selection exactly.
-
#3439
ff61df9Thanks @emdashbot! - Fixes WordPress WXR imports failing partway through large exports. The admin now imports taxonomy terms, content, and reusable blocks in bounded requests while preserving translation links and the complete import summary.Direct API clients can continue using a single request for small exports. Larger exports return
WXR_IMPORT_TOO_LARGEand must use the chunkedtaxonomy,content, andfinalizephases. -
#3439
ff61df9Thanks @emdashbot! - FixesPOST /_emdash/api/import/wordpress/prepareso it also accepts the post-type shape returned by/import/wordpress/analyze(suggestedCollectionandrequiredFields), in addition to the existingcollection/fieldsshape. -
#3488
54ef82fThanks @ascorbic! - Fixes Astro route-cache fills rebuilding purged pages from stale object-cache data on Cloudflare KV. Anonymous cache fills now read from the database, while edge-cache hits continue to avoid the Worker. Content cache reads also stop fetching the retired legacy epoch, reducing KV reads while current publishers keep invalidating older rolling-deploy readers. -
#3468
aa054aeThanks @danielmlr! - Fixes missing MCP tools for plugins built withemdash-plugin buildand registered inplugins: []. Administrators can now review and enable those tools with the plugin's Agent access switch, and enabled tools appear intools/list, as they already did for sandboxed and registry installs. -
#3310
e59d9b4Thanks @eisenbruch! - Fixesctx.kv.list("settings:")scanning the options table twice. The general prefix scan read every settings row and discarded it beforeSettingsAccess.list()read the same rows again, so a plugin that lists its settings in apage:metadatahook cost every page render an extra query. -
#3421
530dabfThanks @swissky! - Fixes the admin marketplace plugin icon and theme thumbnail proxies following redirects off the marketplace origin, which could make the server fetch an internal or loopback URL and return the response to any user with plugin read access (Editor and above). Sites withmarketplaceconfigured are affected. Redirects that stay on the marketplace origin still work; others now return502withPROXY_REDIRECT_UNTRUSTED, and chains longer than five hops return502withPROXY_TOO_MANY_REDIRECTS. -
#3473
22575b7Thanks @eisenbruch! - Adds an optionalcaptionto the MCPmedia_uploadtool, stored on the media record next toalt. The tool previously had no caption, and its schema dropped an unknowncaptionargument without an error, so a caption sent with an upload (often the picture's credit) was lost and the record stored none. -
#3463
09a5bb0Thanks @akapug! - Fixesemdash migrate --from-configfailing with "Stripping types is currently unsupported for files under node_modules" when the Astro config imports a plugin that publishes TypeScript source, such as@emdash-cms/plugin-forms. -
#3422
7097874Thanks @swissky! - Fixes anonymous OAuth dynamic client registration (POST /_emdash/api/oauth/register) letting unauthenticated visitors create unlimited OAuth client records. Registration is now limited to 10 per minute per client IP. Requests over the limit get a429withRetry-After: 60and atemporarily_unavailableerror body. Like the other auth rate limits, it applies only when EmDash can determine the client IP: on Cloudflare, or whentrustedProxyHeaders(orEMDASH_TRUSTED_PROXY_HEADERS) is configured. -
#3409
1ee3c4fThanks @eisenbruch! - Fixes taxonomy-filtered listings sorted byupdated_ator a custom field reading the whole collection on D1.published_atandcreated_atsorts still use the indexed path that stops atLIMIT;updated_atand field sorts now seek from the term's assignments again. -
#3502
ccf24b4Thanks @marccardinal! - Fixes type errors when registering native plugins whose options are declared as an interface, such asformsPlugin(),embedsPlugin(), and plugins scaffolded byemdash plugin init --native. A type-checkedastro.config.mjs(for example, one checked byastro check) no longer reports TS2322 for thesepluginsentries. Plugin options must still be an object. -
#3484
0993a3dThanks @ascorbic! - Fix plugin one-shot schedules that use a space-separated date and time or a UTC offset. New and existing one-shot due times are stored as canonical UTC timestamps, so the scheduler wakes and runs them at the intended instant. Invalid saved task data or a recurring schedule with no future run is now disabled after it is claimed instead of blocking the rest of the batch and being retried indefinitely. -
#3325
c23009dThanks @ascorbic! - Fixes an open redirect in the admin login page and the logout, magic-link sign-in, and dev-bypass routes: a?redirect=value containing a tab, carriage return, or line feed (for example/%09/evil.example) could send the browser to another site. Redirect values that contain control characters are now ignored. -
#3418
895fb69Thanks @swissky! - Fixes registry plugin updates ignoringpolicy.minimumReleaseAge. Updating an installed plugin to a release younger than the configured age, or to a release without a valid index timestamp, is now refused, just as installing it is. Publishers and packages listed inminimumReleaseAgeExcluderemain exempt.The admin update endpoint (
POST /_emdash/api/admin/plugins/registry/:id/update) now refuses aversionolder than the installed one withDOWNGRADE_NOT_ALLOWED; the admin dashboard never sends one, so dashboard updates are unaffected. Updates withoutversionstill follow the aggregator's latest release, which can be older than the installed one after a publisher withdraws the newest release. To roll back to an older release, uninstall the plugin and install that version. -
#3485
b869811Thanks @ascorbic! - Fixes fresh Cloudflare D1 sites remaining partially initialized when the first-request seed is interrupted while creating a collection. The next request now resumes the seed. Existing configured sites and sites initialized withemdash seedare left unchanged, so upgrades and cold starts do not recreate deleted sample content. Attempts to create a collection over an orphaned content table now return a specific conflict instead of a generic server error. -
#3436
36a73e3Thanks @swissky! - Fixes sites on Cloudflare D1 or SQLite that stay stuck on the036_i18n_menus_and_taxonomiesmigration after a first attempt stopped partway, for example on a slow cold start. Every retry failed withno such table, and pages rendered without CMS data. The migration now resumes where it stopped and completes, and content-taxonomy assignments come back intact.Already-stuck sites
On a site that was already stuck at the menus, menu items, taxonomies, or taxonomy definitions table, earlier retries emptied that table. The migration completes after updating, but those rows must be restored from a backup, such as D1 Time Travel or a copy of the SQLite database file.
-
#1814
bb06e3fThanks @masonjames! - Fixes seed application failing with a database uniqueness error when a seeded slug or slugless entry ID belongs to content in the trash. Inskipandupdatemodes, EmDash leaves the trashed content unchanged and counts the collision as skipped. Inerrormode, it reports a conflict identifying the trashed entry. References and translations do not resolve through skipped trashed entries. -
#3384
7a8d368Thanks @swissky! - Fixes the setup wizard creating two administrator accounts when two passkey setup verifications finish at the same time. Whichever verification saves its account second now fails withADMIN_EXISTS, and no second account is created. -
#3326
a3421efThanks @ascorbic! - Hardens redirect rules, including rules stored without going through the admin API:- The redirect middleware now only redirects to site-relative paths that start with a single
/. It skips any rule whose destination has a scheme, starts with//or/\, or contains control characters, and logs a warning with the rule's ID. - Pattern sources treat parentheses and other regex characters as literal text. Previously a source such as
/(.*.*.*x)/[slug]compiled into a regex that could stall requests, and one with an unbalanced(stopped every redirect on the site from working. - Fixes pattern rules that put a
[param]before a[...splat], such as/[category]/[...rest], which swapped the two captured values. - Stored pattern rules whose source is malformed (for example,
/[a][b][c]) are skipped with a warning instead of being compiled. - Seed files now get the same redirect checks as the redirects API. Validation fails when a pattern source is malformed, a destination uses a placeholder the source doesn't capture, or a destination would resolve to another site.
- The redirect middleware now only redirects to site-relative paths that start with a single
-
#3281
c0c0d73Thanks @danielmlr! - Fixesastro buildfailing withGetStaticPathsRequiredinside an EmDash route when the Astro config setsoutput: "static". Every route EmDash adds, including the admin, the API,sitemap.xml, androbots.txt, now renders on demand under either output setting, so a static-output site still needs a server adapter. The site's own pages keep Astro's default: underoutput: "static"they prerender at build time and show content from that build. Builds withoutput: "server"are unchanged. -
#3455
f9cc7b4Thanks @mvanhorn! - Fixesclient.terms()so dictionary consumers can skip usage counts.client.terms("tag", { includeCounts: false })leavescountoff each term, and counts stay enabled by default. -
#3327
f796444Thanks @ascorbic! - Fixes stored cross-site scripting throughurlcontent fields. EmDash previously acceptedjavascript:anddata:values, so a theme rendering<a href={entry.data.website}>could run an attacker's script on the site origin. Aurlfield, including one inside a repeater or block, now accepts only these values:http:andhttps:URLsmailto:andtel:links- site-relative paths such as
/about, and fragments such as#contact
The REST API, MCP tools, site transfers, WordPress imports, and the admin editor reject any other value with a validation error. Seeds and plugin content updates also reject unsafe schemes and path forms that browsers resolve to another site, including
//example.comand/\\example.com. The admin editor now accepts relative paths, fragments,mailto:, andtel:and keeps URL input left-to-right in every locale.Existing entries are not changed. An unsafe stored value is still returned by queries, and saving or duplicating that entry fails until the field is corrected.
sanitizeHref()andisSafeHref()now reject unsafe protocol-relative, backslash-prefixed, and control-character forms when rendering older content. -
#3303
d8ea3fcThanks @ascorbic! - Fixes a denial-of-service in public URL routing: a collection URL pattern with several placeholders in one path segment, such as/{a}{b}{c}{d}{e}x, let a single crafted request tie up the server for seconds whileresolveEmDashPath()matched it.Collection URL patterns now allow at most one placeholder per path segment.
/{year}/{month}/{slug}.htmland/p-{id}/{slug}are still valid, but/{year}{month}/{slug}and/{slug}-{id}are rejected when a collection is created or its pattern is changed through the admin, the REST API, the MCPschema_update_collectiontool, or a seed. Seed files with such a pattern fail validation before anything is applied. The admin's collection editor shows the problem next to the URL Pattern field.If a collection already has a pattern that breaks this rule, it keeps working for generating links in menus, sitemaps and redirects, but
resolveEmDashPath()no longer matches it, and the site logs a warning naming the collection. REST, MCP and admin updates that send the stored pattern back unchanged still succeed. Give each placeholder its own segment (for example, change/{slug}-{id}to/{id}/{slug}) to route those entries again. -
#3445
b2ce32cThanks @swissky! - Fixes admin sign-in silently returning to the login page when no Astro session driver is configured. Signing in with a passkey, magic link, invite link, or signup link now fails with aSESSION_UNAVAILABLEerror explaining that a session driver is required, and OAuth sign-in returns to the login page with the same explanation, instead of reporting success without keeping the user signed in. Magic links, invite links, and signup links stay usable for a retry.astro devandastro buildalso warn when the driver is missing or sessions are disabled withsession: false. The Node, Cloudflare, and Netlify adapters configure a driver automatically; on other adapters, such as Vercel, configuresession.driverinastro.config.mjs. -
Updated dependencies [
f465247,70589bc,6e58b48,f2f9119,2e943ff,148ff3e,1ba8fcb,03b6b3b,03b6b3b,cc91805,2410395,d583dfd,db76eae,8b1b585,ff61df9,ccd80cb,b84ea22,38d200d,38d200d,72f10bd,bf1aa14,bc32000,c23009d,42bf9f5,895fb69,d96f039,9358ede,f796444,d8ea3fc,b2ce32c]:- @emdash-cms/admin@0.42.0
- @emdash-cms/blocks@0.42.0
- @emdash-cms/auth@0.42.0
- @emdash-cms/plugin-types@0.5.0
- @emdash-cms/registry-lexicons@0.7.0
- @emdash-cms/registry-client@0.7.0
- @emdash-cms/registry-verification@0.3.3
- @emdash-cms/gutenberg-to-portable-text@0.42.0