Minor Changes
-
#3364
c99bcd3Thanks @khoinguyenpham04! - Adds bulk tagging for editors. Select posts from a collection's bulk-actions bar or paste up to 50 public links from the Tags page, review their exact titles and languages, and apply one existing or new tag. Assignments take effect immediately without publishing other draft edits, preserve existing tags, and report unmatched links or failed writes for retry. -
#3320
840a9d3Thanks @ascorbic! - Adds first-classblocksfields for ordered, typed page compositions. Define retained block-type versions through the schema API, MCP, or seed files; edit block cards in the admin; and render stored compositions with<Blocks value components fallback>fromemdash/ui.Generated collection types include each retained block version, and
defineBlockComponents<T>()type-checks that a component map covers every generated_type. Image, file, repeater-image, and Portable Text media inside blocks participate in normalization, MIME validation, usage tracking, and cleanup protection.Deploy renderer support before activating a breaking block-type version. Existing versions remain available for drafts, revisions, and stored content, and migrating a stored block to a new version requires explicit
migrateBlocks: trueintent.Sites upgrading from a release older than 0.39 must deploy 0.39 first and upgrade every runtime before creating a blocks field. The 0.39 unknown-field protection prevents an older runtime in a rolling deployment or rollback from overwriting block JSON.
-
#3167
ed51c68Thanks @kwmr! - Adds optionallinkon portable-text image blocks: editor link buttons work on image selection, andImage.astrowraps images in a sanitized<a>whenlink.hrefis set. Legacylink: "https://…"strings written by WordPress/Gutenberg imports are normalised on read, so already-imported linked images keep their link and are upgraded to the object shape on their next edit. -
#1944
bf6b0a9Thanks @swissky! - Localizes invite, magic-link, and account-recovery emails: they now follow the site locale (falling back to the requesting user's admin language) instead of always being sent in English. Email HTML setslanganddiron the root element, so right-to-left languages render correctly. A non-canonical site locale (pt-br) is normalized to its catalog (pt-BR); an unsupported value falls back to the requesting user's admin language.@emdash-cms/auth's invite and magic-link builders (buildInviteEmail,buildMagicLinkEmail, now exported) accept optional injected copy and locale via newemailStrings/emailLocaleconfig options (InviteEmailStrings/MagicLinkEmailStrings).@emdash-cms/admin/localesexports the copy resolversgetInviteEmailStrings/getMagicLinkEmailStringsand the BCP 47 matchermatchLocale. -
#3333
26f2076Thanks @swissky! - Adds optionalccandreplyTofields to plugin email messages, including from sandboxed plugins:await ctx.email.send({ to, cc: ["team@example.com"], replyTo: visitorEmail, subject, text });
ctx.email.send()throws whenccis not an array of strings orreplyTois not a string.email:beforeSend,email:deliver, andemail:afterSendhooks receive both fields onevent.message, and the development console provider prints them. The Cloudflare email provider delivers both, and a message'sreplyTooverrides the provider's configuredreplyTo. Customemail:deliverproviders should passccandreplyToto their email service. -
#3360
6f83886Thanks @danielmlr! - Updates taxonomy definitions sohierarchicalandcollectionsbelong to the taxonomy instead of to each locale. Every locale reads the same values, and translations can no longer disagree about whether a taxonomy is hierarchical or which collections it applies to.labelandlabelSingularstay per locale.This changes behavior for multilingual sites:
PUT /_emdash/api/taxonomies/{name}and the MCPtaxonomy_updatetool changehierarchicalandcollectionsfor every locale, even whenlocaleis passed. Previously they changed only the addressed locale's definition.- Creating a definition for a name that already exists in another locale joins that taxonomy, with or without
translationOf, instead of starting a separate translation group. It takes the taxonomy'shierarchicalandcollections; a create that sends different values returnsVALIDATION_ERROR, where it previously stored them for the new locale only. Change them with an update instead. getTaxonomyTerms(),getTaxonomyDef()andgetTaxonomyDefs()return a taxonomy in every locale, including a locale without its own definition, which takes its label from the first locale on its fallback chain that has one, else the default locale, else the lowest locale code. Previously such a locale got no definition and an empty term list, so the built-in Categories and Tags widgets rendered empty on sites whose default locale had no definition.- Seed files may omit
hierarchicalandcollectionson a taxonomy entry whosetranslationOfpoints at an entry with the samename. Applying a seed followstranslationOfthrough entries with the samenameand takes them from the last one, whatever the translations carry and wherever the entries appear in the file. Validation warns when a translation declares values other than the ones it takes, or when two entries that declare them for one taxonomy disagree. The exportedSeedTaxonomytype marks both fields optional, so code that reads them from a seed must handleundefined.emdash export-seedwrites them only on the entry the translations point at. - Importing a WordPress export (WXR) into a locale assigns the terms of a taxonomy that is defined only in another locale. Previously the import skipped them and reported the taxonomy as missing.
The upgrade migration merges existing definitions of each taxonomy name into one. Where locales disagreed, the taxonomy becomes hierarchical if any locale's definition was, and applies to every collection any locale's definition listed, so no term tree flattens and no collection loses a taxonomy it showed in some locale. Definitions of one name that were in separate translation groups are joined into one group. Definitions of different names that shared a translation group, which older seed files and API versions could create, get one group per name.
What should I do?
If your site defines different
hierarchicalorcollectionsvalues per locale on purpose, check them after upgrading and set the values you want once.If your code changes
hierarchicalorcollectionsby writing to_emdash_taxonomy_defswith SQL, use the taxonomy API, the MCPtaxonomy_updatetool or a seed file instead. A direct write no longer changes what EmDash reads, only what sandboxed plugins see. EmDash still updates those columns whenever a taxonomy changes, for code that reads them.@emdash-cms/plugin-test:runtimeHost.fixtures.taxonomyDefinition()setshierarchicalandcollectionsfor every locale of the taxonomy. -
#3346
1796cd5Thanks @ascorbic! - Adds site transfer: export a whole EmDash site as a.emdashpackage and import it into an empty EmDash site, on Cloudflare (D1 and R2) or Node (SQLite or PostgreSQL). A package carries content, revisions, schema and block types, taxonomies, bylines, menus, widgets, redirects, SEO, settings, media files, and comments unless you leave them out. Imports are checked before anything is written, run in bounded steps, and end with a receipt issued only after the imported site is verified against the package.From the command line
emdash site export --output site.emdash --url https://old.example.com emdash site import site.emdash --analyze --url https://new.example.com emdash site import site.emdash --plan sha256:… --confirm --url https://new.example.comsite exportruns the export on the server, downloads and checks it file by file, and writes the package atomically.--no-commentsleaves out comments and reactions. Run it again with the same--outputto resume an interrupted export.site import <file> --analyzechecks the package locally, uploads only what the target is missing, and prints the plan: record counts, package users and suggested matches, the changes the import makes, warnings, blockers, and the plan digest. Map package users with--map-principal <id or email>=<user id, email, or none>(repeatable), and keep this site's title or tagline with--use-target-titleand--use-target-tagline. The command exits with2when the plan has blockers.site import <file> --plan <digest> --confirmruns the reviewed plan and prints the receipt. It refuses to run if the plan changed after review.site import status|resume|receipt <operation-id>inspects, continues, or prints the receipt of an import. Resuming an import that is still uploading needs the package file as a third argument.
Every command accepts
--json; errors print as{ "error": { "code", "message" } }. The client adds matchingtransfer*methods andusers().REST API
The API lives under
/_emdash/api/admin/transfer. Exports:POST exports(with an optionalIdempotency-Key),POST exports/:id/advanceuntilnextRequestInMsisnull, thenGET exports/:id/archivefor one archive orGET exports/:id/manifestandGET exports/:id/files/<path>file by file. Imports:POST importswith the package'smanifest.json,PUT imports/:id/files/<path>for each file listed byGET imports/:id/missing,POST imports/:id/analyzeuntil the plan is ready (optionally withdecisions),POST imports/:id/executewith the reviewed{ packageDigest, planDigest },POST imports/:id/advanceuntil it finishes, andGET imports/:id/receipt.GET capabilitiesreports supported formats and limits and whether this site can receive an import.A plan lists every change the import makes as
transformations, including those the exporter already made (for example, records left out because their parent no longer existed, or content that refers to media files the origin site did not have).warningshold findings that need attention, and anyblockersprevent execution.MCP
The MCP server adds
site_transfer_capabilities,site_export_start,site_export_status,site_import_analyze,site_import_start,site_import_resume,site_import_status, andsite_import_receipt. Package files are uploaded over HTTP. Tool results are bounded summaries and never include record values, principal emails, media bytes, or download URLs.Access
Site transfer needs the
transfer:exportortransfer:importpermission (admins). API and OAuth tokens needtransfer:exportto export,transfer:analyzeto upload and analyze, andtransfer:executeto run, cancel, or abandon an import. Theadminscope includes all three, so existing admin tokens andemdash logincredentials can use site transfer; grant a single transfer scope to give a token, such as an agent's, narrower access.An MCP token without
adminor the needed transfer scope can still start an export or import once an admin approves it: the tool fails withTRANSFER_APPROVAL_REQUIREDand anapprovalId, a signed-in admin approves the request under Settings → Transfer (orPOST approvals/:id/approve), and the agent repeats the call withapprovalId. A request must be approved within 15 minutes, and an approval must be used within 15 minutes. It is single-use and bound to the user, the token, and the exact request.The audit log records who creates an export or import, requests, cancels, or abandons an import, or approves or denies a transfer request, and whose request completed or failed each import. Entries hold operation and approval ids, digests, record counts, and error codes, never package content.
Writes during an import
While an import is writing, and after a failed or cancelled import until it is abandoned, content and settings changes through the REST API, MCP, public comment and reaction forms, plugin routes, sandboxed plugins, and scheduled publishing return
503 TRANSFER_IMPORT_IN_PROGRESS. Reads, sign-in, user management, API token management, and entry edit locks keep working. Do not send visitors to an imported site until its receipt is available.When the site cannot check whether writes are allowed, writes that returned
503 MEDIA_USAGE_ACTIVATION_CHECK_FAILEDnow return503 TRANSFER_FENCE_CHECK_FAILED, in REST, MCP, and plugin route responses. Update any client that matches the old code.The public media route now refuses storage keys under
transfers/. System cleanup removes an import's staged files 24 hours after it finishes and an export's when it expires. -
#3376
3be2921Thanks @ascorbic! - Updates thesqlite()database adapter to open the database in write-ahead logging (WAL) mode, matching whatemdash init,emdash dev, andemdash seedalready do. Sites whose database was created or touched by those commands were already running in WAL mode; sites whose database was only ever opened by the running server switch to it on their next start.In WAL mode, SQLite keeps
-waland-shmfiles next to the database (for exampledata.db-walanddata.db-shm), and the server needs write access to the database directory. If your backup script copies only the.dbfile while the site is running, switch tosqlite3 data.db ".backup backup.db", or stop the server first and copy all three files.Keep the database on a local disk or block volume: WAL does not work on network filesystems such as NFS or SMB.
Patch Changes
-
#3354
f58b8f6Thanks @swissky! - Fixes entries that could not be saved because a rich text, multi-select, or repeater field held a blank string, typically left behind by an import. Saving failed withexpected array, received stringon a field the editor never touched. Blank or whitespace-only values for these fields are now stored as empty (null) when sent through the admin, the content API, or MCP, so such entries save normally and are repaired the next time they are saved from the admin. Blank values in required fields are still rejected as missing. -
#3017
5a9d822Thanks @jakevis! - Fixes email-verification signup, which could not be completed: the verification email linked to the JSON API endpoint instead of the signup page, the signup page itself redirected anonymous visitors to login, and that redirect dropped the?token=from the URL. The email now links to/_emdash/admin/signup?token=…(as the invite email already did), the page is reachable without a session, and the login redirect preserves the query string of the page it returns to. -
#3241
b8fae35Thanks @swissky! - Fixes the General and SEO settings screens so removing the site logo, favicon, or default social image remains cleared after saving. REST, MCP, andsetSiteSettings()callers can remove these media references by setting them tonull; omitted settings remain unchanged. -
#3400
678b848Thanks @danielmlr! - Fixes new comments waiting for review with the reason "No moderator configured" on a site with one comment moderation plugin. When nocomment:moderatechoice is stored and exactly one plugin provides the hook, EmDash selects that plugin over the built-in moderator. EmDash no longer stores the built-in moderator as the choice when it is the only moderator, so a moderation plugin added later takes over.A choice stored by an earlier release is kept, which on most existing sites is the built-in moderator. The admin route
PUT /_emdash/api/admin/hooks/exclusive/comment:moderatecurrently changes a stored choice. -
#3382
1b1d443Thanks @swissky! - Enforces the public comment submission rate limit under concurrent requests. Parallel submissions could previously all pass the check before any of them was saved, so a burst could post far more than the allowed 5 comments per 10 minutes per IP address (or 20 in total, shared by all visitors, when the site cannot determine a trusted client IP). The limit now resets at fixed 10-minute boundaries, so up to twice the cap can be accepted in quick succession across a boundary. On sites with Turnstile enabled, only submissions that pass the CAPTCHA count toward the limit. Submissions that pass the rate-limit check but are rejected later, for example by acomment:beforeCreatehook, now also count toward the limit. Rate-limited responses now include aRetry-Afterheader. -
#3358
043960eThanks @swissky! - FixesGET /_emdash/api/content/{collection}?status=allreturning an empty list.status=allnow lists entries of every status, the same as omittingstatus. The endpoint,EmDashClient.list(), andemdash content list --statusnow reject any other value outsidedraft,published,scheduled,archived,pending,private, andfuturewith a400 VALIDATION_ERRORinstead of silently returning no items. Clients that sent a mistyped or unsupported status should send one of these values or omitstatus. -
#3390
f3f7cc3Thanks @swissky! - Fixes plugin cron schedules (ctx.cron.schedule()with@daily,@hourly, or standard cron expressions) so they resolve in UTC on every host. Node self-hosts previously resolved them in the server'sTZ, so the same schedule fired at a different time than on Cloudflare Workers, which always runs in UTC.Node deployments whose
TZis not UTC will see recurring plugin tasks move after upgrading:0 2 * * *now runs at 02:00 UTC instead of 02:00 local time, and schedules no longer shift with daylight saving time. Each task switches the next time it runs or is rescheduled. Cloudflare Workers deployments and one-shot ISO 8601 schedules are unchanged. Plugin authors who need a local wall-clock time should write the expression in UTC; host-timezone resolution cannot be restored. -
#3361
5eaf095Thanks @swissky! - FixesdefinePlugin()silently producing a plugin with no hooks when given a plugin descriptor, such as the result ofcloudflareEmail({...}). The wrapped plugin registered normally but did nothing, so a wrapped email provider left the site with no email delivery.definePlugin()now throws an error that names the plugin, so a site that still wraps a descriptor fails to start until the wrapper is removed. Pass the descriptor directly to thepluginsarray of theemdash()integration instead:// Before: a local plugin module whose createPlugin() returns // definePlugin(cloudflareEmail({ from: "cms@mails.example.com" })) // After, in astro.config.mjs emdash({ plugins: [cloudflareEmail({ from: "cms@mails.example.com" })], });
-
#3300
2e8e063Thanks @merrickma! - Fixes a D1/SQLite performance regression where taxonomy-filtered listings (e.g.where: { category: "News" }ordered bypublished_atorcreated_at) read orders of magnitude more rows than necessary. ThepickedCTE now uses a plainJOINso the planner can drive from the content table's deleted-sort index, probe the pivot by primary key, and short-circuit atLIMIT, restoring the indexed-sort early-limit behavior. -
#3359
9a5d5b9Thanks @swissky! - Fixes every admin page (including login and the setup wizard) returning an empty response whenfonts: falseis set in theemdash()integration config. The admin now falls back to system fonts. -
#3151
08e93b8Thanks @emdashbot! - FixcloudflareEmail()silently failing to reachbinding.send()on some Astro Cloudflare builds, and make email delivery errors visible in runtime logs.- The provider now resolves
cloudflare:workersenvthrough a dedicated helper module (cloudflare-email-env.ts) so the Worker runtime evaluates it as a static import rather than a direct dynamic import of the built-in specifier. This avoids bundler/runtime paths whereimport("cloudflare:workers")inside the bundled Worker did not resolve. EmailPipeline.send()nowconsole.errors the provider and recipient before re-throwing a delivery error, so failures are not lost when callers (e.g. magic-link routes) intentionally swallow the error to avoid leaking whether an account exists.
No configuration changes are required; existing
send_emailbindings continue to work. - The provider now resolves
-
#3348
6f1b046Thanks @swissky! - Fixes gallery blocks seeded with$mediashowing empty images and losing their media on first edit. TheGallerycomponent now renders these images, including galleries seeded with earlier versions, and the content editor previews them and keeps their media references, alt text, and dimensions when it saves. Seeding a gallery now stores each$mediaimage as a regular gallery media reference. Galleries whose references an earlier autosave already stripped are not restored. -
#3398
7df822bThanks @danielmlr! - Fixes Portable Text image blocks seeded with$media, including those seeded with earlier versions, rendering with an emptysrcand losing their media reference when first edited in the admin or through visual editing. Seeding now stores the image as a regular media reference with its alt text and dimensions on the block, the same shape the editor saves. Blocks whose media reference an earlier edit already removed need their image selected again. -
#3350
21ee693Thanks @swissky! - Fixes saving from visual editing changing custom blocks identified byurl, such as embeds imported from WordPress, to useid, which dropped theirurl. Blocks now keep the identity field they were stored with: blocks with bothidandurlkeep both, and blocks with neither no longer gain an emptyid. Custom blocks inserted in the content editor no longer gain an emptyidwhen no ID is entered. -
#3438
c78a6cbThanks @swissky! - Fixes upgrades that hang or fail on the035_bounded_404_logmigration when_emdash_404_logholds many rows, on PostgreSQL, SQLite, and Cloudflare D1. Deduplicating the 404 log slowed down sharply as the log grew, so a log with a few hundred thousand entries never finished, held the migration lock, and on PostgreSQL could run the server out of memory. The migration now takes about as long as reading the log once, so sites no longer need to empty the 404 log before upgrading.Sites that already failed on this migration
On SQLite and D1, a site whose earlier attempt stopped partway kept failing on every start with a unique-constraint error on
_emdash_404_log. The migration now finishes deduplicating on the next start. If startup instead reports that the migration lock is held, release the stuck migration lock first. PostgreSQL sites were rolled back to their pre-migration state and simply complete on the next start. -
#3338
ecef5a9Thanks @khoinguyenpham04! - Fixes cached published pages remaining stale when tag or category assignments change. Assignments still save immediately, and the editor clarifies that term changes do not wait for Publish changes. -
#3337
ad1465dThanks @swissky! - Fixes magic link and account recovery emails failing for recipients whose mail is scanned (for example by Microsoft 365 Safe Links). Opening the link now shows a confirmation page in the admin, and the one-time link is only used when the recipient presses Continue, so a scanner that fetches the link no longer uses it up or receives the session. Links in emails sent before the upgrade keep working.GET /_emdash/api/auth/magic-link/verifyno longer signs in; it redirects to the confirmation page. Scripts that signed in by requesting that URL must now sendPOST /_emdash/api/auth/magic-link/verifywith a JSON body{ "token": "..." }and theX-EmDash-Request: 1header, then keep the returned session cookie. -
#3416
f832fe9Thanks @swissky! - Fixes marketplace plugin updates installing a version that failed or did not clear the marketplace security audit. Updating to a version whose audit verdict isfailorwarn, whether it is the latest version or one chosen explicitly, is now refused with the sameAUDIT_FAILEDerror as installing it, and the installed version stays in place. -
#3433
4a5241aThanks @swissky! - Fixes the MCPcontent_duplicatetool letting contributors and authors copy content owned by other users. The tool now requires permission to edit the source item, matching the REST duplicate endpoint: authors can duplicate their own content, editors and above can duplicate anyone's, and contributors can no longer duplicate content through MCP. The copy is now attributed to the user who made it instead of the source item's author. -
#3067
2db6c98Thanks @swissky! - Media deletion no longer leaves files behind. The MCPmedia_deletetool and the pluginctx.media.delete()API now remove the stored file as well as the record, matching the admin API. When the storage delete fails,DELETE /_emdash/api/media/:idreportsstorageDeleted: falseinstead of a plain success, and the periodic cleanup retries the file deletion until it succeeds. -
#3413
e9b70ecThanks @danielmlr! - Fixes the Media Library Used in tab reporting no usage for a file selected as the site logo, favicon, or default social image. Those settings appear as a Site Settings result,GET /_emdash/api/media/{id}/usagelists them in a newsiteSettingsarray, andusage.countincludes them. An empty Used in tab now says "No tracked references found" and names what is not checked, such as custom rich text blocks, instead of stating that the file is not used in any content. -
#3395
14e9fddThanks @danielmlr! - Fixesastro buildfailing withRollup failed to resolve import "cloudflare:sockets"on Astro 6 sites that use@astrojs/nodeor another non-Cloudflare adapter, so EmDash's own import no longer needs avite.build.rollupOptions.external: [/^cloudflare:/]workaround. -
#3386
931b40dThanks @swissky! - Fixes passkey sign-in options revealing whether an email address has an account.POST /_emdash/api/auth/passkey/optionsnow ignores the optionalemailfield and returns the same options for every request, so the browser offers any passkey saved for the site. The default admin login is unaffected. Clients that postedemailto this endpoint to sign in with passkeys not stored on the authenticator (non-discoverable credentials, such as some older security keys) can no longer sign in with those keys. Register a passkey on an authenticator that supports discoverable credentials (most platform authenticators and current security keys), or sign in with a magic link or a configured OAuth provider. The@emdash-cms/adminPasskeyLogincomponent'sshowEmailInputprop is deprecated and no longer shows an email field; existing callers still type-check and can drop the prop. The previous email-scoped behavior cannot be restored. -
#3419
19488ecThanks @khoinguyenpham04! - Fixes click-to-edit for text on the page in Edit mode:- In the Playground, clicking an editable title or text field now edits it in place, and clicking an editable image opens the image picker. Previously these fields showed an outline on hover but did nothing when clicked.
- Multi-line text fields, such as excerpts, now edit in place when the page shows the field's stored text unchanged, and line breaks added while editing are saved. Previously, clicking a text field always opened the entry in the admin. It still does when the page shows the text transformed, for example Markdown rendered as HTML or a shortened excerpt, so an edit on the page can't overwrite the full stored text.
- Clicking an editable field inside a link, such as a title in a post card, edits the field instead of following the link, including when the field is clicked before the editor has finished loading.
-
#1745
6ebd4efThanks @swissky! - Fixes preview links 404ing on sites with a custom collectionurl_pattern. The content Preview button now resolves the collection'surl_pattern(the same route the sitemap and "View published" links use) instead of the hard-coded/{collection}/{id}, falling back to/{collection}/{id}only when no pattern is configured. An explicitpathPatternorEMDASH_PREVIEW_PATH_PATTERNstill takes precedence. -
#3403
b2f6c06Thanks @danielmlr! - Fixes registry plugin installs and updates on Node.js sites, along with the plugin icons, banners, and screenshots on the admin's registry plugin page. Since 0.36.0, every registry download on Node.js failed, and installs stopped with errors such asThe publisher DID document could not be fetched.Sites on Cloudflare Workers were not affected. -
#3399
e4b0d81Thanks @danielmlr! - Fixes the admin plugin registry showing "Handle unavailable" for every publisher and scrolling sideways, so verified publisher handles now appear and unresolved publisher identifiers stay inside their cards.A publisher whose handle no longer resolves back to its account now shows INVALID HANDLE, and installing its plugins from the registry detail page is disabled until the publisher fixes the handle. Plugins that are already installed keep running.
-
#3410
baf3010Thanks @danielmlr! - Reduces the D1 queries needed to apply a seed with sample content, such as through the setup wizard's Include sample content option, from 22 to 10 per published entry with plain fields, leaving more of a Worker invocation's D1 query limit for larger seeds. -
#3355
b804977Thanks @swissky! - Fixes seeds that declarecategoryortaglosing their labels and collection assignments. Every new database starts with built-in versions of these two taxonomy definitions, and seeding skipped them as existing. Until a site edits them, the built-in definitions are now replaced by the seed's in everyonConflictmode, including--on-conflict error, which no longer fails on them. Edited definitions are still kept by the defaultskipmode. Runningemdash seedagainst an existing site that never edited these two taxonomies now applies the seed's labels, collections, and hierarchy to them.applySeed()results includetaxonomies.skipped,emdash seedprints it, and the automatic first-boot seed logs a warning when it keeps any existing taxonomy definitions. -
#3401
4028ee4Thanks @danielmlr! - FixeskvCache()rate-limit (429) errors, logged as[object-cache] epoch bump failed, when a seed with sample content is applied on Cloudflare Workers, such as through the setup wizard's Include sample content option. Applying a seed now writes each invalidated cache key to KV once, when the seed finishes, instead of once per entry. -
#3356
90ed23fThanks @swissky! - Fixes public pages redirecting visitors and crawlers to/_emdash/admin/setupwhen the database has never been migrated. With the default automatic migration mode, the first public request now migrates the database and renders the page. If that fails, the page answers with a temporary uncached503that links to the setup wizard. Opening/_emdash/adminon a new site still leads to the setup wizard. -
#3357
66f50ddThanks @swissky! - Fixes anonymous public page requests making a second database connection attempt after the setup check on a fresh server instance has already failed to reach the database. The request now skips CMS runtime initialization and renders without plugin-contributed metadata and page fragments, as it already did when runtime initialization failed. Later requests retry the check, and a server instance whose runtime is already running keeps using it. -
#3368
4a69cc6Thanks @danielmlr! - Fixes term translations that could sit under a different parent than the term they translate. A translation created without a parent through the REST API, MCPtaxonomy_create_term, the plugincreateTerm()API, a seed file or a WordPress import now takes its term's parent and position instead of becoming a root term in its locale; sendingparentId: nulldoes the same as leaving it out. A translation created under a different parent moves the term in every locale, as changing the parent through any locale already did.On sites where earlier versions left a term's locales under different parents, saving the term's parent from any locale now moves every locale under it. The admin's term dialog sends the current parent on every save, so the first save of such a nested term, even a label change, settles its parent for all locales.
-
#3380
20858edThanks @swissky! - Malformed invite, signup, and magic-link tokens now return a clean "invalid token" error instead of a 500. Token hashing tolerates values that aren't valid base64url, so an unrecognized token misses the lookup like any other unknown token. -
#3405
d9f0d85Thanks @swissky! - Fixesctx.media.upload()in trusted (in-process) plugins accepting any content type and keeping the filename's extension, which let a public plugin route, such as the forms plugin's submit endpoint, store files like HTML pages that the site then served under their own type. Trusted plugins can now upload throughctx.media.upload()or request an upload URL throughctx.media.getUploadUrl()only for the types in the default media upload allowlist: PNG, JPEG, GIF, WebP, and AVIF images, video, audio, and PDF. Any other type, including SVG andapplication/octet-stream, throws aPluginRouteErrorwith status415 UNSUPPORTED_MEDIA_TYPE, and a malformed content type throws one with status400. A file stored byupload()or reserved bygetUploadUrl()gets an extension that matches its content type, so an upload namedpage.htmlwith typeimage/pngis stored and served as a PNG; when the type has no known extension, the filename's extension is kept only if it belongs to an allowed media type. Trusted plugins that uploaded other types must switch to one of the accepted types. -
#2889
86a33eeThanks @amasen02! - Generatedemdash-env.d.tsfiles now includeentry.data.byline?: BylineSummary | nullalongsidebylines, matching the byline data hydrated bygetEmDashEntryandgetEmDashCollectionat runtime. -
#3397
b5ad2feThanks @danielmlr! - Fixes the image popover in the visual editing toolbar: uploads no longer report "Save failed" and leave an unused copy in the media library on each attempt, the media library lists your images instead of "No images found", and alt text edits on an existing image are saved. -
#3051
ff0ef63Thanks @logelog! - Fixes Workerd plugins retaining access after they are disabled or unloaded, and keeps capabilities, allowed hosts, and storage declarations scoped to the loaded plugin version. Re-enabling a plugin issues fresh credentials. -
Updated dependencies [
973699a,5a9d822,c99bcd3,b8fae35,1f193b2,840a9d3,6f1b046,ed51c68,7df822b,21ee693,28dec10,1217386,ecef5a9,bf6b0a9,ad1465d,e9b70ec,931b40d,a30b110,7a4e1fd,e4b0d81,1796cd5,1796cd5,a733b90,20858ed]:- @emdash-cms/auth@0.40.0
- @emdash-cms/admin@0.40.0
- @emdash-cms/gutenberg-to-portable-text@0.40.0
- @emdash-cms/blocks@0.40.0