github emdash-cms/emdash emdash@0.42.0

latest releases: @emdash-cms/plugin-test@0.2.5, @emdash-cms/sandbox-workerd@0.9.0, @emdash-cms/plugin-embeds@0.1.52...
5 hours ago

Minor Changes

  • #3335 0371107 Thanks @ascorbic! - Adds multi-release fixtures to emdash/testing/registry, allowing browser and integration tests to exercise registry updates between authoritative package versions. Existing single-release fixtures continue to work unchanged.

  • #3440 03b6b3b Thanks @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 empty icon clears the stored icon.

  • #3440 03b6b3b Thanks @swissky! - Adds an admin.quickCreate collection setting that removes the collection's "new entry" quick action from the admin dashboard. Set it to false in 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 whole admin object, so include any existing admin.listColumns in the same request.

  • #1939 2410395 Thanks @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/emdash at most once a day, in the background, with no site data. To wait longer before a release is announced, for example to match pnpm's minimumReleaseAge, 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 new updates:read permission (admins only).

  • #3059 c36d974 Thanks @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_draft and revision_restore fail with ENTRY_LOCKED while another user holds the entry's lock, where the call used to succeed. The error message names the holder, and _meta.details carries their userId, userName, acquiredAt and expiresAt. Reading the item again does not clear the refusal. Each of these tools takes an optional overrideLock: true to write anyway.

    POST /_emdash/api/revisions/{revisionId}/restore now returns 409 ENTRY_LOCKED in the same case. Pass "overrideLock": true in 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 38d200d Thanks @ttmx! - Adds the byline:afterSave and byline:afterDelete plugin 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:read capability and receive the same public byline profile that ctx.bylines.get() returns. byline:afterSave runs after a byline or one of its translations is created or updated, with isNew set on creation. byline:afterDelete receives 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 38d200d Thanks @ttmx! - Adds the bylines:read plugin capability, which lets plugins read public byline profiles and the bylines credited on content entries through ctx.bylines.

    ctx.bylines provides get() and cursor-paginated list() for profiles, plus getEntriesBylines() 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 of ctx.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:read and users: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 4d6a87b Thanks @ascorbic! - Removes the legacy emdash plugin command group, including its marketplace login, logout, scaffold, validation, bundle, and publish commands.

    Use the dedicated @emdash-cms/plugin-cli package for sandboxed plugin authoring and registry publishing:

    pnpm add -D @emdash-cms/plugin-cli
    pnpm exec emdash-plugin --help

    Native plugins are npm packages and continue to use their package build scripts.

  • #3495 9358ede Thanks @ascorbic! - Adds admin.footerLabel for 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 to false to show the version alone.

Patch Changes

  • #3493 148ff3e Thanks @MA2153! - Fixes bulk term assignment only working with the built-in tag taxonomy. 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. The POST /_emdash/api/taxonomies/bulk-tag endpoint now accepts a term from any taxonomy, and matches only entries in the collections that use that taxonomy.

  • 43565ef Thanks @MA2153! - List endpoints now treat a zero or negative limit as 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 2987a51 Thanks @ascorbic! - Fixes emdash-env.d.ts not updating when the schema changes during astro dev with the Cloudflare adapter. Adding or editing collections and fields now regenerates types on Cloudflare too, not only on Node.

  • #2390 2bce20c Thanks @dashimu! - Fixes the admin UI remaining on "Loading EmDash..." when running EmDash from source in development on Windows. No configuration change is required.

  • #2898 8b1b585 Thanks @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 9f721fb Thanks @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 ff61df9 Thanks @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_LARGE and must use the chunked taxonomy, content, and finalize phases.

  • #3439 ff61df9 Thanks @emdashbot! - Fixes POST /_emdash/api/import/wordpress/prepare so it also accepts the post-type shape returned by /import/wordpress/analyze (suggestedCollection and requiredFields), in addition to the existing collection/fields shape.

  • #3488 54ef82f Thanks @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 aa054ae Thanks @danielmlr! - Fixes missing MCP tools for plugins built with emdash-plugin build and registered in plugins: []. Administrators can now review and enable those tools with the plugin's Agent access switch, and enabled tools appear in tools/list, as they already did for sandboxed and registry installs.

  • #3310 e59d9b4 Thanks @eisenbruch! - Fixes ctx.kv.list("settings:") scanning the options table twice. The general prefix scan read every settings row and discarded it before SettingsAccess.list() read the same rows again, so a plugin that lists its settings in a page:metadata hook cost every page render an extra query.

  • #3421 530dabf Thanks @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 with marketplace configured are affected. Redirects that stay on the marketplace origin still work; others now return 502 with PROXY_REDIRECT_UNTRUSTED, and chains longer than five hops return 502 with PROXY_TOO_MANY_REDIRECTS.

  • #3473 22575b7 Thanks @eisenbruch! - Adds an optional caption to the MCP media_upload tool, stored on the media record next to alt. The tool previously had no caption, and its schema dropped an unknown caption argument without an error, so a caption sent with an upload (often the picture's credit) was lost and the record stored none.

  • #3463 09a5bb0 Thanks @akapug! - Fixes emdash migrate --from-config failing 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 7097874 Thanks @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 a 429 with Retry-After: 60 and a temporarily_unavailable error body. Like the other auth rate limits, it applies only when EmDash can determine the client IP: on Cloudflare, or when trustedProxyHeaders (or EMDASH_TRUSTED_PROXY_HEADERS) is configured.

  • #3409 1ee3c4f Thanks @eisenbruch! - Fixes taxonomy-filtered listings sorted by updated_at or a custom field reading the whole collection on D1. published_at and created_at sorts still use the indexed path that stops at LIMIT; updated_at and field sorts now seek from the term's assignments again.

  • #3502 ccf24b4 Thanks @marccardinal! - Fixes type errors when registering native plugins whose options are declared as an interface, such as formsPlugin(), embedsPlugin(), and plugins scaffolded by emdash plugin init --native. A type-checked astro.config.mjs (for example, one checked by astro check) no longer reports TS2322 for these plugins entries. Plugin options must still be an object.

  • #3484 0993a3d Thanks @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 c23009d Thanks @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 895fb69 Thanks @swissky! - Fixes registry plugin updates ignoring policy.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 in minimumReleaseAgeExclude remain exempt.

    The admin update endpoint (POST /_emdash/api/admin/plugins/registry/:id/update) now refuses a version older than the installed one with DOWNGRADE_NOT_ALLOWED; the admin dashboard never sends one, so dashboard updates are unaffected. Updates without version still 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 b869811 Thanks @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 with emdash seed are 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 36a73e3 Thanks @swissky! - Fixes sites on Cloudflare D1 or SQLite that stay stuck on the 036_i18n_menus_and_taxonomies migration after a first attempt stopped partway, for example on a slow cold start. Every retry failed with no 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 bb06e3f Thanks @masonjames! - Fixes seed application failing with a database uniqueness error when a seeded slug or slugless entry ID belongs to content in the trash. In skip and update modes, EmDash leaves the trashed content unchanged and counts the collision as skipped. In error mode, it reports a conflict identifying the trashed entry. References and translations do not resolve through skipped trashed entries.

  • #3384 7a8d368 Thanks @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 with ADMIN_EXISTS, and no second account is created.

  • #3326 a3421ef Thanks @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.
  • #3281 c0c0d73 Thanks @danielmlr! - Fixes astro build failing with GetStaticPathsRequired inside an EmDash route when the Astro config sets output: "static". Every route EmDash adds, including the admin, the API, sitemap.xml, and robots.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: under output: "static" they prerender at build time and show content from that build. Builds with output: "server" are unchanged.

  • #3455 f9cc7b4 Thanks @mvanhorn! - Fixes client.terms() so dictionary consumers can skip usage counts. client.terms("tag", { includeCounts: false }) leaves count off each term, and counts stay enabled by default.

  • #3327 f796444 Thanks @ascorbic! - Fixes stored cross-site scripting through url content fields. EmDash previously accepted javascript: and data: values, so a theme rendering <a href={entry.data.website}> could run an attacker's script on the site origin. A url field, including one inside a repeater or block, now accepts only these values:

    • http: and https: URLs
    • mailto: and tel: 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.com and /\\example.com. The admin editor now accepts relative paths, fragments, mailto:, and tel: 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() and isSafeHref() now reject unsafe protocol-relative, backslash-prefixed, and control-character forms when rendering older content.

  • #3303 d8ea3fc Thanks @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 while resolveEmDashPath() matched it.

    Collection URL patterns now allow at most one placeholder per path segment. /{year}/{month}/{slug}.html and /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 MCP schema_update_collection tool, 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 b2ce32c Thanks @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 a SESSION_UNAVAILABLE error 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 dev and astro build also warn when the driver is missing or sessions are disabled with session: false. The Node, Cloudflare, and Netlify adapters configure a driver automatically; on other adapters, such as Vercel, configure session.driver in astro.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

Don't miss a new emdash release

NewReleases is sending notifications on new releases.