emdash — releases
Latest 20 GitHub releases for emdash-cms/emdash. Auto-mirrored by playbooks/local/autodocgen.yml.
[!info] Pinned in BreeZ-CF:
0.8.x· upstream latest:emdash@0.8.0.
emdash@0.8.0 · emdash@0.8.0
2026-04-27 · by @emdashbot[bot]
Minor Changes
-
#679
493e317Thanks @drudge! - Adds arepeaterBlock Kit element: array-of-objects with scalar sub-fields, drag-to-reorder, and collapsible item cards. Plugin block forms can now capture repeating data (FAQ rows, carousel slides, card grids) inline in the portable-text editor. -
#779
e402890Thanks @ascorbic! - Addssettings_getandsettings_updateMCP tools so agents can read and update site-wide settings (title, tagline, logo, favicon, URL, posts-per-page, date format, timezone, social, SEO).settings_getresolves media references (logo/favicon/seo.defaultOgImage) to URLs;settings_updateis a partial update that preserves omitted fields. Newsettings:read(EDITOR+) andsettings:manage(ADMIN) API token scopes back the tools, with matching options in the personal API token settings UI. -
#777
3eca9d5Thanks @ascorbic! - Behavior change — MCPtaxonomy_list_termsnow uses an opaque base64 keyset cursor over(label, id)instead of the previous raw term-id cursor. The new cursor is robust to concurrent term deletion: it encodes a position in sort space rather than a reference to a specific row. MCP clients that persisted page cursors across this upgrade should drop them and restart pagination — pre-upgrade cursors will returnINVALID_CURSOR.Adds parent-chain validation to
taxonomy_create_term(previously onlytaxonomy_update_termvalidated): rejects non-existent parents, cross-taxonomy parents, self-parent on update, cycles on update, and parent chains exceeding 100 ancestors. Existing taxonomies with chains over the depth limit continue to function but cannot accept new descendants until the chain is shortened. -
#675
b6cb2e6Thanks @eyupcanakman! - Renders local media through storagepublicUrlwhen configured.EmDashImageand the Portable Text image block now call a newlocals.emdash.getPublicMediaUrl()helper, so R2 and S3 deployments with a custom domain serve images from that domain.S3Storage.getPublicUrlnow returns the/_emdash/api/media/file/{key}path when nopublicUrlis set (previously{endpoint}/{bucket}/{key}). -
#398
31333dcThanks @simnaut! - Adds pluggable auth provider system with AT Protocol as the first plugin-based provider. Refactors GitHub and Google OAuth from hardcoded buttons into the sameAuthProviderDescriptorinterface. All auth methods (passkey, AT Protocol, GitHub, Google) are equal options on the login page and setup wizard.
Patch Changes
-
#777
3eca9d5Thanks @ascorbic! - Fixes MCP ownership checks failing with an internal error on content that has noauthorId(seed-imported rows). Admins and editors can now edit, publish, unpublish, schedule, and restore such items; users with only own-content permissions get a clean permission error. -
#777
3eca9d5Thanks @ascorbic! - Fixes content create / update silently accepting invalid data: required fields are now enforced, select / multiSelect values must match the configured options, and reference fields must resolve to a real, non-trashed target. Errors surface with a structuredVALIDATION_ERRORcode and a message naming every offending field. -
#670
37ada52Thanks @segmentationfaulter! - Change text direction of input fields and tiptap editor depending upon the language entered -
#688
0557b62Thanks @corwinperdomo! - Fixes the Settings > Email admin page so activeemail:beforeSend/email:afterSendmiddleware plugins are listed (previously always empty). AddsHookPipeline.getHookProviders()for enumerating non-exclusive hook providers. -
#673
5a581d9Thanks @mvanhorn! - Fixes WordPress media import to emit relative/_emdash/api/media/file/...URLs instead of absolute ones, matching every other media endpoint. Imported media is now recognized byINTERNAL_MEDIA_PREFIXfor enrichment, and no longer pins URLs to the origin that happened to serve the import request (breaking renders on a different port or behind a reverse proxy). -
#750
0ecd3b4Thanks @edrpls! - Make the admin collection list column headers sortable.Title,Status,Locale, andDateare now clickable buttons that toggle direction; the current sort state is exposed viaaria-sorton the<th>so screen readers announce it correctly.The server's
orderByfield whitelist now acceptsstatus,locale, andnamealongside the existing date fields — unchanged from a security standpoint, the repo still rejects unknown field names to prevent column enumeration.Callers of
<ContentList>that don't passonSortChangerender the previous static-label headers, so legacy integrations (e.g. the content picker) are unaffected. -
3138432Thanks @r2sake! - Fixes hydration of the inline PortableText editor on pnpm projects by aliasinguse-sync-external-store/shimto the mainuse-sync-external-storepackage. The shim is a CJS-only React<18 polyfill imported transitively by@tiptap/react; under pnpm's virtual store Vite cannot pre-bundle it, and the browser receives rawmodule.exportswhich fails to load as ESM (SyntaxError: ... does not provide an export named 'useSyncExternalStore'). The aliases redirect to React's built-inuseSyncExternalStore(peer-dep floor is React 18), so users no longer need to add the workaround themselves inastro.config.mjs. -
#755
70924cdThanks @mvanhorn! - Fixes the WordPress importer so collections created mid-import are visible to the subsequent execute phase.POST /_emdash/api/import/wordpress/preparenow callsemdash.invalidateManifest()when it creates new collections or fields. Without this, the DB-persisted manifest cache (emdash:manifest_cachein theoptionstable) stays stale and theexecuterequest reportsCollection "<slug>" does not existfor every item destined for a freshly created collection — a bug that survived dev-server restarts and required manually deleting the cache row. -
#757
1f0f6f2Thanks @ascorbic! - Removes two redundant in-scope database queries from the FTS verify-and-repair path. The inner block re-fetched searchable fields and search config that were already loaded in the outer scope of the same method. No behavior change. -
#777
3eca9d5Thanks @ascorbic! - Fixes paginated list endpoints silently returning the first page when given a malformed cursor. Bad cursors now produce a structuredINVALID_CURSORerror so client pagination bugs surface immediately.Note for plugin authors: the low-level
decodeCursorexport fromemdash/database/repositoriesnow throwsInvalidCursorErroron invalid input instead of returningnull. Direct callers (rare — most code usesfindMany-style helpers that handle this internally) should wrap the call intry/catchor migrate to the higher-level helpers. -
#777
3eca9d5Thanks @ascorbic! - Fixesschema_create_collectionMCP tool to apply its documented default of['drafts', 'revisions']forsupportswhen omitted. -
#189
f5658f0Thanks @Sayeem3051! - Add url and email plugin setting field types (Issue #175) -
#777
3eca9d5Thanks @ascorbic! - Preserves structured error codes through MCP tool responses. Errors returned by MCP tools now include a stable[CODE]prefix in the message text and a_meta.codefield on the response envelope, so MCP clients can distinguish failure modes (e.g. NOT_FOUND, CONFLICT, VALIDATION_ERROR) instead of seeing only a generic message. -
#777
3eca9d5Thanks @ascorbic! - Fixesrevision_restorefor collections that support revisions: restore now creates a new draft revision from the source revision's data and updatesdraft_revision_id, leaving the live columns untouched. Previously, restore overwrote the live row directly and left any pending draft unchanged, opposite to the documented contract ("Replaces the current draft..."). The response is also hydrated so the returneddatareflects the post-restore state.Behavior is unchanged for collections that do not support revisions.
-
#734
cf1edaeThanks @huckabarry! - Preserve clearer error logging and run sandboxedafter()content hook tasks in parallel when deferred plugin hooks execute after save and publish. -
#794
b352e88Thanks @ascorbic! - Sanitises thesnippetfield returned by thesearch()API so it is safe to render withset:html/innerHTML. Previously SQLite's FTS5snippet()function spliced literal<mark>tags around matched terms but left the surrounding text unescaped, meaning a post title likeHello <script>alert(1)</script>would render as live markup. Templates and components rendering snippets directly were exposed; the in-treeLiveSearchcomponent already worked around this client-side. Snippets now contain only HTML-escaped source text plus literal<mark>...</mark>highlight tags, matching the documented contract. -
#183
da3d065Thanks @masonjames! - Fixes Astro dev to use the built admin package for external app installs while keeping source aliasing for local monorepo development. -
#777
3eca9d5Thanks @ascorbic! - Tightens conflict-error matchers inhandleContentCreateandhandleContentUpdate. Both paths now match specifically on"unique constraint failed"or"duplicate key"(avoiding false positives where the word "unique" appears in unrelated error text), and produce sanitizedSLUG_CONFLICT/CONFLICTmessages so raw database error text — including Postgres-internal index names — no longer leaks to API consumers. Clients that pattern-match the previous unsanitized messages will see normalized text instead. -
#777
3eca9d5Thanks @ascorbic! - Fixestaxonomy_listexposing collection slugs for collections that no longer exist. Orphaned slugs are filtered out so the response stays consistent withschema_list_collections. -
#777
3eca9d5Thanks @ascorbic! - Fixescontent_unpublishso thatpublishedAtis cleared when an item is unpublished. -
#608
47978b5Thanks @drudge! - Fixes/_emdash/api/widget-areas/*endpoints returning raw DB rows (snake_case fields,contentas a JSON string) instead of the transformedWidgetshape. Admin UI expectscontentto already be a parsed PortableText array andcomponentId/componentProps/menuNamein camelCase, so expanding a content widget in/_emdash/admin/widgetsproduced an empty editor. All four route handlers (GET /widget-areas,GET /widget-areas/:name,POST /widget-areas/:name/widgets,PUT /widget-areas/:name/widgets/:id) now run their results throughrowToWidget, which was made module-exported. -
#777
3eca9d5Thanks @ascorbic! - Addstaxonomies:manageandmenus:manageAPI token scopes for fine-grained control over taxonomy and menu mutations via MCP and REST. Existing tokens withcontent:writecontinue to work for those operations:content:writenow implicitly grantsmenus:manageandtaxonomies:manageso PATs issued before the split keep their effective permissions. The reverse implication does not hold — a token with onlymenus:managecannot create or edit content. -
Updated dependencies [
86b26f6,493e317,e998083,37ada52,acab807,0ecd3b4,4c9f04d,e402890,ed4d880,31333dc,3eca9d5]:- @emdash-cms/admin@1.0.0
- @emdash-cms/auth@1.0.0
- @emdash-cms/auth-atproto@1.0.0
- @emdash-cms/gutenberg-to-portable-text@1.0.0
create-emdash@0.8.0 · create-emdash@0.8.0
2026-04-27 · by @emdashbot[bot]
Minor Changes
- #785
e0dd616Thanks @MattieTK! - Adds support for positional directory argument, allowingnpm create emdash .to scaffold into the current directory andnpm create emdash my-projectto skip the interactive name prompt.
@emdash-cms/x402@0.8.0 · @emdash-cms/x402@0.8.0
2026-04-27 · by @emdashbot[bot]
@emdash-cms/gutenberg-to-portable-text@0.8.0 · @emdash-cms/gutenberg-to-portable-text@0.8.0
2026-04-27 · by @emdashbot[bot]
@emdash-cms/cloudflare@0.8.0 · @emdash-cms/cloudflare@0.8.0
2026-04-27 · by @emdashbot[bot]
Patch Changes
- Updated dependencies [
493e317,3eca9d5,3eca9d5,37ada52,0557b62,5a581d9,0ecd3b4,3138432,70924cd,1f0f6f2,3eca9d5,e402890,3eca9d5,f5658f0,3eca9d5,3eca9d5,b6cb2e6,3eca9d5,cf1edae,b352e88,31333dc,da3d065,3eca9d5,3eca9d5,3eca9d5,47978b5,3eca9d5]:- emdash@1.0.0
@emdash-cms/blocks@0.8.0 · @emdash-cms/blocks@0.8.0
2026-04-27 · by @emdashbot[bot]
Minor Changes
-
#792
6e0e921Thanks @all3f0r1! - Adds anemptyBlock Kit block: a styled empty-state placeholder with title, optional description, copyable shell command, size variant (sm/base/lg), and an optional list of action elements (CTAs). Plugin admin pages can now render proper empty states for lists, tables, and onboarding flows without rolling their own layout. -
#679
493e317Thanks @drudge! - Adds arepeaterBlock Kit element: array-of-objects with scalar sub-fields, drag-to-reorder, and collapsible item cards. Plugin block forms can now capture repeating data (FAQ rows, carousel slides, card grids) inline in the portable-text editor.
@emdash-cms/auth@0.8.0 · @emdash-cms/auth@0.8.0
2026-04-27 · by @emdashbot[bot]
Minor Changes
- #779
e402890Thanks @ascorbic! - Addssettings_getandsettings_updateMCP tools so agents can read and update site-wide settings (title, tagline, logo, favicon, URL, posts-per-page, date format, timezone, social, SEO).settings_getresolves media references (logo/favicon/seo.defaultOgImage) to URLs;settings_updateis a partial update that preserves omitted fields. Newsettings:read(EDITOR+) andsettings:manage(ADMIN) API token scopes back the tools, with matching options in the personal API token settings UI.
Patch Changes
-
#398
31333dcThanks @simnaut! - Adds pluggable auth provider system with AT Protocol as the first plugin-based provider. Refactors GitHub and Google OAuth from hardcoded buttons into the sameAuthProviderDescriptorinterface. All auth methods (passkey, AT Protocol, GitHub, Google) are equal options on the login page and setup wizard. -
#777
3eca9d5Thanks @ascorbic! - Addstaxonomies:manageandmenus:manageAPI token scopes for fine-grained control over taxonomy and menu mutations via MCP and REST. Existing tokens withcontent:writecontinue to work for those operations:content:writenow implicitly grantsmenus:manageandtaxonomies:manageso PATs issued before the split keep their effective permissions. The reverse implication does not hold — a token with onlymenus:managecannot create or edit content.
@emdash-cms/auth-atproto@0.2.0 · @emdash-cms/auth-atproto@0.2.0
2026-04-27 · by @emdashbot[bot]
Minor Changes
- #398
31333dcThanks @simnaut! - Adds pluggable auth provider system with AT Protocol as the first plugin-based provider. Refactors GitHub and Google OAuth from hardcoded buttons into the sameAuthProviderDescriptorinterface. All auth methods (passkey, AT Protocol, GitHub, Google) are equal options on the login page and setup wizard.
Patch Changes
- Updated dependencies [
493e317,3eca9d5,3eca9d5,37ada52,0557b62,5a581d9,0ecd3b4,3138432,70924cd,1f0f6f2,3eca9d5,e402890,3eca9d5,f5658f0,3eca9d5,3eca9d5,b6cb2e6,3eca9d5,cf1edae,b352e88,31333dc,da3d065,3eca9d5,3eca9d5,3eca9d5,47978b5,3eca9d5]:- emdash@1.0.0
- @emdash-cms/auth@1.0.0
@emdash-cms/admin@0.8.0 · @emdash-cms/admin@0.8.0
2026-04-27 · by @emdashbot[bot]
Minor Changes
-
#679
493e317Thanks @drudge! - Adds arepeaterBlock Kit element: array-of-objects with scalar sub-fields, drag-to-reorder, and collapsible item cards. Plugin block forms can now capture repeating data (FAQ rows, carousel slides, card grids) inline in the portable-text editor. -
#779
e402890Thanks @ascorbic! - Addssettings_getandsettings_updateMCP tools so agents can read and update site-wide settings (title, tagline, logo, favicon, URL, posts-per-page, date format, timezone, social, SEO).settings_getresolves media references (logo/favicon/seo.defaultOgImage) to URLs;settings_updateis a partial update that preserves omitted fields. Newsettings:read(EDITOR+) andsettings:manage(ADMIN) API token scopes back the tools, with matching options in the personal API token settings UI. -
#398
31333dcThanks @simnaut! - Adds pluggable auth provider system with AT Protocol as the first plugin-based provider. Refactors GitHub and Google OAuth from hardcoded buttons into the sameAuthProviderDescriptorinterface. All auth methods (passkey, AT Protocol, GitHub, Google) are equal options on the login page and setup wizard.
Patch Changes
-
#611
86b26f6Thanks @drudge! - Wires up the block configuration sidebar insideWidgetEditor.PortableTextEditornow receivesonBlockSidebarOpen/onBlockSidebarClosecallbacks that hold the activeBlockSidebarPanelin local state, and rendersImageDetailPanelwhen the panel type is"image"— mirroring the content-entry editor. Without this, clicking a block's settings button or the media picker inside widget content had no visible effect. -
#786
e998083Thanks @smart-cau! - Adds Korean translations for 21 admin UI strings that previously fell back to English. Korean (ko) coverage is now complete. -
#670
37ada52Thanks @segmentationfaulter! - Change text direction of input fields and tiptap editor depending upon the language entered -
#720
acab807Thanks @Pouf5! - Fix taxonomies not nesting correctly in a RTL layout -
#750
0ecd3b4Thanks @edrpls! - Make the admin collection list column headers sortable.Title,Status,Locale, andDateare now clickable buttons that toggle direction; the current sort state is exposed viaaria-sorton the<th>so screen readers announce it correctly.The server's
orderByfield whitelist now acceptsstatus,locale, andnamealongside the existing date fields — unchanged from a security standpoint, the repo still rejects unknown field names to prevent column enumeration.Callers of
<ContentList>that don't passonSortChangerender the previous static-label headers, so legacy integrations (e.g. the content picker) are unaffected. -
#184
4c9f04dThanks @masonjames! - Fixes plugin block defaults so initial values are seeded without overriding later edits. -
#700
ed4d880Thanks @dcardosods! - Prefill site title and tagline in Setup Wizard from seed file -
Updated dependencies [
6e0e921,493e317]:- @emdash-cms/blocks@1.0.0
emdash@0.7.0 · emdash@0.7.0
2026-04-23 · by @emdashbot[bot]
Minor Changes
-
#705
8ebdf1aThanks @eba8! - Adds admin white-labeling support viaadminconfig inastro.config.mjs. Agencies can set a custom logo, site name, and favicon for the admin panel, separate from public site settings. -
#742
c26442bThanks @ascorbic! - AddstrustedProxyHeadersconfig option so self-hosted deployments behind a reverse proxy can declare which client-IP headers to trust. Used by auth rate limits (magic-link, signup, passkey, OAuth device flow) and the public comment endpoint — without it, every request on a non-Cloudflare deployment was treated as "unknown" and rate limits were effectively disabled.Set the option in
astro.config.mjs:emdash({ trustedProxyHeaders: ["x-real-ip"], // nginx, Caddy, Traefik });or via the
EMDASH_TRUSTED_PROXY_HEADERSenv var (comma-separated). Headers are tried in order; values ending inforwarded-forare parsed as comma-separated lists.Also removes the user-agent-hash fallback on the comment endpoint. The fallback was meant to give anonymous commenters on non-Cloudflare deployments something approximating per-user rate limiting, but the UA is trivially rotatable; requests with no trusted IP now share a stricter "unknown" bucket. Operators behind a reverse proxy should set
trustedProxyHeadersto restore per-IP bucketing.Only set
trustedProxyHeaderswhen you control the reverse proxy. Trusting a forwarded-IP header from the open internet lets any client spoof their IP and defeats rate limiting.
Patch Changes
-
#745
7186961Thanks @ascorbic! - Fixes an unauthenticated denial-of-service via the 404 log. Every 404 response previously inserted a new row into_emdash_404_log, so an attacker could grow the database without bound by requesting unique nonexistent URLs. Repeat hits to the same path now dedup into a single row with ahitscounter andlast_seen_attimestamp, referrer and user-agent headers are truncated to bounded lengths, and the log is capped at 10,000 rows with oldest-first eviction. -
#739
e9ecec2Thanks @MohamedH1998! - Fixes the REST content API silently strippingpublishedAton create/update andcreatedAton create. Importers can now preserve original publish and creation dates on migrated content. Gated behindcontent:publish_any(EDITOR+) so regular contributors cannot backdate posts.createdAtis intentionally not accepted on update —created_atis treated as immutable. -
#732
e3e18aaThanks @jcheese1! - Fixes select dropdown appearing behind dialog by removing explicit z-index values and addingisolateto the admin body for proper stacking context. -
#695
fae63bdThanks @ascorbic! - Fixesemdash seedso entries declared with"status": "published"are actually published. Previously the seed wrote the content row withstatus: "published"and apublished_attimestamp but never created a live revision, so the admin UI showed "Save & Publish" instead of "Unpublish" andlive_revision_idstayed null. The seed now promotes published entries to a live revision on both create and update paths. -
#744
30d8fe0Thanks @ascorbic! - Fixes a setup-window admin hijack by binding/setup/adminand/setup/admin/verifyto a per-session nonce cookie. Previously an unauthenticated attacker who could reach a site during first-time setup could POST to/setup/adminbetween the legitimate admin's email submission and passkey verification, overwriting the stored email — the admin account would then be created with the attacker's address. The admin route now mints a cryptographically random nonce, stores it in setup state, and sets it as an HttpOnly, SameSite=Strict,/_emdash/-scoped cookie; the verify route rejects any request whose cookie does not match in constant time. -
#685
d4a95bfThanks @ascorbic! - Fixes visual editing: clicking an editable field now opens the inline editor instead of always opening the admin in a new tab. The toolbar's manifest fetch was readingmanifest.collectionsdirectly but the/_emdash/api/manifestendpoint wraps its payload in{ data: … }, so every field-kind lookup returnednulland every click fell through to the admin-new-tab fallback. -
#743
a31db7dThanks @ascorbic! - Locksemdash:site_urlafter the first setup call so a spoofed Host header on a later step of the wizard can't overwrite it. Config (siteUrl) and env (EMDASH_SITE_URL) paths already took precedence; this is a defence-in-depth guard for deployments that rely on the request-origin fallback. -
#737
adb118cThanks @ascorbic! - Rate-limits the self-signup request endpoint to prevent abuse.POST /_emdash/api/auth/signup/requestnow allows 3 requests per 5 minutes per IP, matching the existing limit on magic-link/send. Over-limit requests return the same generic success response as allowed-but-ignored requests, so the limit isn't observable to callers. -
#738
080a4f1Thanks @ascorbic! - Strengthens SSRF protection on the import pipeline against DNS-rebinding. ThevalidateExternalUrlhelper now also blocks known wildcard DNS services (nip.io,sslip.io,xip.io,traefik.me,lvh.me,localtest.me) and trailing-dot FQDN forms of blocked hostnames. A newresolveAndValidateExternalUrlresolves the target hostname via DNS-over-HTTPS (Cloudflare) and rejects if any returned IP is in a private range.ssrfSafeFetchand the plugin unrestricted-fetch path now use the DNS-aware validator on every hop. This adds two DoH round-trips per outbound request; self-hosted admins whose egress blockscloudflare-dns.comcan inject a custom resolver viasetDefaultDnsResolver. -
#736
81fe93bThanks @ascorbic! - Restricts Subscriber-role access to draft, scheduled, and trashed content. Subscribers retaincontent:readfor member-only published content but no longer see non-published items via the REST API or MCP server. Adds a newcontent:read_draftspermission (Contributor and above) that gates/compare,/revisions,/trash,/preview-url, and the corresponding MCP tools. -
Updated dependencies [
8ebdf1a,2e4b205,e3e18aa,743b080,fa8d753,81fe93b]:- @emdash-cms/admin@0.7.0
- @emdash-cms/auth@0.7.0
- @emdash-cms/gutenberg-to-portable-text@0.7.0
create-emdash@0.7.0 · create-emdash@0.7.0
2026-04-23 · by @emdashbot[bot]
@emdash-cms/x402@0.7.0 · @emdash-cms/x402@0.7.0
2026-04-23 · by @emdashbot[bot]
@emdash-cms/plugin-embeds@0.1.7 · @emdash-cms/plugin-embeds@0.1.7
2026-04-23 · by @emdashbot[bot]
Patch Changes
- Updated dependencies [
8ebdf1a,7186961,e9ecec2,e3e18aa,fae63bd,30d8fe0,d4a95bf,a31db7d,adb118c,080a4f1,81fe93b,c26442b]:- emdash@0.7.0
- @emdash-cms/blocks@0.7.0
@emdash-cms/gutenberg-to-portable-text@0.7.0 · @emdash-cms/gutenberg-to-portable-text@0.7.0
2026-04-23 · by @emdashbot[bot]
@emdash-cms/cloudflare@0.7.0 · @emdash-cms/cloudflare@0.7.0
2026-04-23 · by @emdashbot[bot]
Patch Changes
-
#740
63509e1Thanks @ascorbic! - Sandboxed plugin HTTP requests now follow redirects manually and re-validate the destination at every hop. The allowedHosts list is checked on each redirect target (not just the initial URL), so an allowed host that 302s to a disallowed one no longer bypasses the scope. Credential headers (Authorization, Cookie, Proxy-Authorization) are stripped on cross-origin redirects.network:fetch:anyandallowedHosts: ["*"]now still reject literal private IPs, cloud-metadata addresses, and known internal hostnames — the allowlist scopes which public hosts a plugin may reach, not whether SSRF protection applies. Non-http(s) URL schemes are rejected. Caps redirect chains at 5 hops. -
Updated dependencies [
8ebdf1a,7186961,e9ecec2,e3e18aa,fae63bd,30d8fe0,d4a95bf,a31db7d,adb118c,080a4f1,81fe93b,c26442b]:- emdash@0.7.0
@emdash-cms/blocks@0.7.0 · @emdash-cms/blocks@0.7.0
2026-04-23 · by @emdashbot[bot]
@emdash-cms/auth@0.7.0 · @emdash-cms/auth@0.7.0
2026-04-23 · by @emdashbot[bot]
Patch Changes
- #736
81fe93bThanks @ascorbic! - Restricts Subscriber-role access to draft, scheduled, and trashed content. Subscribers retaincontent:readfor member-only published content but no longer see non-published items via the REST API or MCP server. Adds a newcontent:read_draftspermission (Contributor and above) that gates/compare,/revisions,/trash,/preview-url, and the corresponding MCP tools.
@emdash-cms/admin@0.7.0 · @emdash-cms/admin@0.7.0
2026-04-23 · by @emdashbot[bot]
Minor Changes
- #705
8ebdf1aThanks @eba8! - Adds admin white-labeling support viaadminconfig inastro.config.mjs. Agencies can set a custom logo, site name, and favicon for the admin panel, separate from public site settings.
Patch Changes
-
#680
2e4b205Thanks @CacheMeOwside! - Fixes dark mode toggle having no effect with the classic theme. -
#732
e3e18aaThanks @jcheese1! - Fixes select dropdown appearing behind dialog by removing explicit z-index values and addingisolateto the admin body for proper stacking context. -
#647
743b080Thanks @arashackdev! - Adds Persian (Farsi) locale with full admin translations. Adds Vazirmatn as the default font family for Farsi. -
#689
fa8d753Thanks @edrpls! - Fixes the taxonomy term picker to match across diacritic boundaries.Typing
Mexicoin the admin picker now surfaces a term labeledMéxicoinstead of prompting a duplicate create. Input and term labels are folded via NFD decomposition + lowercase before substring-matching, so editors who type without diacritics — or with locale keyboards that produce precomposed vs. combining forms — still see the canonical term.Before this fix,
"mexico"and"méxico"were treated as distinct strings, so the picker showed zero suggestions and the editor had no way to find the existing term except to create a duplicate. Duplicate terms then split the taxonomy and broke public-facing filter pages that group content by slug.The exact-match check that gates the "Create new term" button uses the same fold, so typing
MexicowhenMéxicoexists also suppresses Create — closing the duplicate-creation loop. -
Updated dependencies []:
- @emdash-cms/blocks@0.7.0
emdash@0.6.0 · emdash@0.6.0
2026-04-20 · by @emdashbot[bot]
Minor Changes
-
#626
1859347Thanks @ascorbic! - Adds eager hydration of taxonomy terms ongetEmDashCollectionandgetEmDashEntryresults. Each entry now exposes adata.termsfield keyed by taxonomy name (e.g.post.data.terms.tag,post.data.terms.category), populated via a single batched JOIN query alongside byline hydration. Templates that previously looped and calledgetEntryTerms(collection, id, taxonomy)per entry can readentry.data.termsdirectly and skip the N+1 round-trip.New exports:
getAllTermsForEntries,invalidateTermCache.Reserved field slugs now also block
terms,bylines, andbylineat schema-creation time to prevent new fields shadowing the hydrated values. Existing installs that already have a user-defined field with any of those slugs will see the hydrated value overwrite the stored value on read (consistent with the pre-existing behavior ofbylines/bylinehydration); rename the field to keep its data accessible. -
#600
9295cc1Thanks @ascorbic! - Adds Noto Sans as the default admin UI font via the Astro Font API. Fonts are downloaded from Google at build time and self-hosted. The base font covers Latin, Cyrillic, Greek, Devanagari, and Vietnamese. Additional scripts (Arabic, CJK, Hebrew, Thai, etc.) can be added via the newfonts.scriptsconfig option. Setfonts: falseto disable and use system fonts.
Patch Changes
-
#648
ada4ac7Thanks @CacheMeOwside! - Adds the missingurlfield type for seed files, content type builder, and content editor with client-side URL validation. -
#658
f279320Thanks @ascorbic! - Addsafter(fn)— a helper for deferring bookkeeping work past the HTTP response. On Cloudflare it hands off towaitUntil(extending the worker's lifetime); on Node it fire-and-forgets (the event loop keeps the process alive for the next request anyway). Host binding is plumbed through a newvirtual:emdash/wait-untilvirtual module so core stays runtime-neutral — Cloudflare-specific imports live in the integration layer, not in request-handling code.First use: cron stale-lock recovery (
_emdash_cron_tasksUPDATE) now runs after the response ships instead of blocking it. On D1 this shaves a primary-routed write off the cold-start critical path.Usage:
import { after } from "emdash"; // Fire-and-forget; errors are caught and logged so a deferred task // never surfaces as an unhandled rejection. after(async () => { await recordAuditEntry(); }); -
#642
7f75193Thanks @Pouf5! - AddsmaxUploadSizeconfig option to set the maximum media file upload size in bytes. Defaults to 52_428_800 (50 MB) — existing behaviour is unchanged. -
#595
cfd01f3Thanks @ascorbic! - Fixes playground initialization crash caused by syncSearchState attempting first-time FTS enablement during field creation. -
#663
38d637bThanks @ascorbic! - CachegetSiteSetting(key)per-request. It was firing an uncachedoptionstable read on every call, so templates that pull several settings (orEmDashHeadreadingseoon every page render) paid N round-trips to the D1 primary instead of sharing one. Noticeable on colos far from the primary — APS/APE were seeing ~30–100 ms of avoidable warm-render latency per page.Wraps each key in
requestCached("siteSetting:${key}", ...)so concurrent callers in a single render share the in-flight query. -
#631
31d2f4eThanks @ascorbic! - Improves cold-start performance for anonymous page requests. Sites with D1 replicas far from the worker colo should see the biggest improvement; on the blog-demo the homepage cold request on Asia colos dropped from several seconds to under a second.Three underlying changes:
- Search index health checks run on demand (on the first search request) rather than at worker boot, reclaiming the time a boot-time scan spent walking every searchable collection.
- Module-scoped caches (manifest, taxonomy names, byline existence, taxonomy-assignment existence) are now reused across anonymous requests that route through D1 read replicas. They previously rebuilt on every request.
- Cold-start Server-Timing headers break runtime init into sub-phases (
rt.db,rt.plugins, etc.) so further regressions are easier to diagnose.
-
#605
445b3bfThanks @ascorbic! - Fixes D1 read replicas being bypassed for anonymous public page traffic. The middleware fast path now asks the database adapter for a per-request scoped Kysely, so anonymous reads land on the nearest replica instead of the primary-pinned singleton binding.All D1-specific semantics (Sessions API, constraint selection, bookmark cookie) live in
@emdash-cms/cloudflare/db/d1behind a singlecreateRequestScopedDb(opts)function. Core middleware has no D1-specific logic. Adapters opt in via a newsupportsRequestScope: booleanflag onDatabaseDescriptor;d1()sets it to true.Other fixes in the same change:
- Nested
runWithContextcalls in the request-context middleware now merge the parent context instead of replacing it, so an outer per-request db override is preserved through edit/preview flows. - Baseline security headers now forward Astro's cookie symbol across the response clone so
cookies.set()calls in middleware survive. - Any write (authenticated or anonymous) now forces
first-primary, so an anonymous form/comment POST isn't racing across replicas. - The session user is read once per request and reused in both the fast path and the full runtime init (previously read twice on authenticated public-page traffic).
- Bookmark cookies are validated only for length (≤1024) and absence of control characters — no stricter shape check, so a future D1 bookmark format change won't silently degrade consistency.
- The
!configbail-out now still applies baseline security headers. __ec_d1_bookmarkreferences aligned to__em_d1_bookmarkacross runtime, docs, and JSDoc.
- Nested
-
#654
943d540Thanks @ascorbic! - Dedups repeat DB queries within a single page render. Measured against the query-count fixture:- The "has any bylines / has any taxonomy terms" probes were module-scoped singletons, but the bundler duplicates those modules across chunks — each chunk ended up with its own copy of the singleton, so the probe re-ran whenever a different chunk called the helper. Stored on
globalThiswith a Symbol key (same pattern asrequest-context.ts), so a single value is shared across all chunks now. - Wraps
getCollectionInfo,getTaxonomyDef,getTaxonomyTerms, andgetEmDashCollectionin the request-scoped cache so two callers with the same arguments in the same render share a single query.
Biggest wins land on pages that render multiple content-heavy components (a post detail page with comments, byline credits, and sidebar widgets). On the fixture post page: -3 queries cold / -1 warm under SQLite, -2 queries cold under D1.
- The "has any bylines / has any taxonomy terms" probes were module-scoped singletons, but the bundler duplicates those modules across chunks — each chunk ended up with its own copy of the singleton, so the probe re-ran whenever a different chunk called the helper. Stored on
-
#668
2cb3165Thanks @CacheMeOwside! - Fixes boolean field checkbox displaying as unchecked after publish in the admin UI. -
#500
14c923bThanks @all3f0r1! - Adds inline term creation in the post editor taxonomy sidebar. Tags show a "Create" option when no match exists; categories get an "Add new" button below the list. -
#606
c5ef0f5Thanks @ascorbic! - Caches the manifest in memory and in the database to eliminate N+1 schema queries per request. Batches site info queries during initialization. Cold starts read 1 cached row instead of rebuilding from scratch. -
#671
f839381Thanks @jcheese1! - Fixes MCP OAuth discovery and dynamic client registration so EmDash only advertises supported client registration mechanisms and rejects unsupported redirect URIs or token endpoint auth methods during client registration. Also exempts OAuth protocol endpoints (token, register, device code, device token) from the Origin-based CSRF check, since these endpoints are called cross-origin by design (MCP clients, CLIs, native apps) and carry no ambient credentials, and sends the required CORS headers so browser-based MCP clients can reach them. -
#664
002d0acThanks @ascorbic! -getSiteSetting(key)now transparently piggybacks ongetSiteSettings()when the batch has already been loaded in the current request. If a parent template has calledgetSiteSettings()(which is request-cached), a latergetSiteSetting("seo")— fromEmDashHead, a plugin, or user code — reads the key from that cached result instead of firing its own round-trip. Falls back to a per-key cached query when nothing has been primed.Exposes
peekRequestCache(key)for internal use by other helpers that want the same "read from a broader cached query if available" pattern.On the blog-demo fixture: the SEO call added in PR #613 now costs zero extra queries per page (it reads from the Base layout's existing
getSiteSettings()result). -
#465
0a61ef4Thanks @Pouf5! - Fixes FTS5 tables not being created when a searchable collection is created or updated via the Admin UI. -
#636
6d41fe1Thanks @ascorbic! - Fixes two correctness issues from the #631 cold-start work:ensureSearchHealthy()now runs against the runtime's singleton database instead of the per-request session-bound one. The verify step reads, but a corrupted index triggers a rebuild write, and D1 Sessions on a GET request usesfirst-unconstrainedrouting that's free to land on a replica. The singleton goes through the default binding, which the adapter correctly promotes tofirst-primaryfor writes.- The playground request-context middleware now sets
dbIsIsolated: true. Without it, schema-derived caches (manifest, taxonomy defs, byline/term existence probes) could carry values across playground sessions that have independent schemas.
-
#627
b158e40Thanks @ascorbic! - Prime the request-scoped cache forgetEntryTermsduring collection and entry hydration.getEmDashCollectionandgetEmDashEntryalready fetch taxonomy terms for their results via a single batched JOIN; now the same data is seeded into the per-request cache under the same keysgetEntryTermsuses, so existing templates that still callgetEntryTerms(collection, id, taxonomy)in a loop get cache hits instead of a serial DB round-trip per iteration.Empty-result entries are seeded with
[]for every taxonomy that applies to the collection so "this post has no tags" also short-circuits without a query. Cache entries are scoped to the request context via ALS and GC'd with it. -
#653
f97d6abThanks @ascorbic! - Adds opt-in query instrumentation for performance regression testing. SettingEMDASH_QUERY_LOG=1causes the Kysely log hook to emit[emdash-query-log]-prefixed NDJSON on stdout for every DB query executed inside a request, tagged with the route, method, and anX-Perf-Phaseheader value. Zero runtime overhead when the flag is unset — the log option is only attached to Kysely when enabled.Also exposes the helpers at
emdash/database/instrumentationso first-party adapters (e.g.@emdash-cms/cloudflare) can wire the same hook into their per-request Kysely instances. -
#613
e67b940Thanks @nickgraynews! - Fixes site SEO settingsgoogleVerificationandbingVerificationnot being emitted into<head>. The fields were stored in the database and editable in the admin UI but were never rendered as<meta name="google-site-verification">or<meta name="msvalidate.01">tags, making meta-tag verification with Google Search Console and Bing Webmaster Tools impossible. EmDashHead now loads site SEO settings and renders these tags on every page. -
#659
0896ec8Thanks @ascorbic! - Two query-count reductions on the request hot path:- Widget areas now fetch in a single query.
getWidgetArea(name)used to do two round-trips — one for the area, one for its widgets. Single left-join now. Saves one query per<WidgetArea>rendered on a page. - Dropped the "has any bylines / has any term assignments" probes. Those fired on every hydration call to save a single query on sites with zero bylines/terms — exactly the wrong tradeoff. The batch hydration queries already handle empty sites at the same cost, so the probes are removed. Pre-migration databases (tables not created yet) are still handled via an
isMissingTableErrorcatch. Saves two queries per render on pages that hydrate bylines and taxonomy terms.
On the fixture post-detail page: SQLite
/posts/[slug]drops from 34 → 32, D1 from 43 → 39. The widget-area JOIN shaves one off every page that renders a widget area.invalidateBylineCache()andinvalidateTermCache()are preserved as no-op exports so callers don't break. - Widget areas now fetch in a single query.
-
#558
629fe1dThanks @csfalcao! - Fixes/_emdash/api/search/suggest500 error.getSuggestionsno longer double-appends the FTS5 prefix operator*on top of the oneescapeQueryalready adds, so autocomplete queries like?q=desnow return results instead of raisingSqliteError: fts5: syntax error near "*". -
#552
f52154dThanks @masonjames! - Fixes passkey login failures so unregistered or invalid credentials return an authentication failure instead of an internal server error. -
#601
8221c2aThanks @CacheMeOwside! - Fixes the Save Changes button on the Content Type editor failing silently with a 400 error -
#598
8fb93ebThanks @maikunari! - Fixes WordPress import error reporting to surface the real exception message instead of a generic "Failed to import item" string, making import failures diagnosable. -
#629
6d7f288Thanks @CacheMeOwside! - Adds toast feedback when taxonomy assignments are saved or fail on content items. -
#638
4ffa141Thanks @auggernaut! - Fixes repeated FTS startup rebuilds on SQLite by verifying indexed row counts against the FTS shadow table. -
#582
04e6ccaThanks @all3f0r1! - Improves the "Failed to create database" error to detect NODE_MODULE_VERSION mismatches from better-sqlite3 and surface an actionable message telling the user to rebuild the native module. -
Updated dependencies [
dfcb0cd,cf63b02,0b32b2f,913cb62,6c92d58,a2d5afb,39d285e,f52154d]:- @emdash-cms/admin@0.6.0
- @emdash-cms/auth@0.6.0
- @emdash-cms/gutenberg-to-portable-text@0.6.0
create-emdash@0.6.0 · create-emdash@0.6.0
2026-04-20 · by @emdashbot[bot]