emDash 1.4 was released on 29 October 2026. We moved emdashnow.com onto it the next morning. From the first backup to a verified production site took under two hours, and most of that was waiting for automated checks. The release had no database migrations and needed one line of code from us.
That makes it a good example of a routine upgrade, and routine is the point. The steps below are the ones we would follow for any release. They are also where the useful findings were: one change the release notes describe but most people will miss, one behaviour our tests caught that the notes don't mention, and one step after the upgrade that almost no one will know to take.
Before touching anything: a recovery point
An emDash site is recovered from four things kept together: the database, the media, the encryption key and the matching version of the code. We set out why in operating emDash safely. Before this upgrade we secured each of them, even though the release notes said no database changes were coming.
- The database. We recorded the current point-in-time bookmark of the D1 database, then ran our weekly off-platform backup by hand: an encrypted copy stored outside Cloudflare, about half a megabyte for this site. Together they took about three minutes.
- The code. We noted the version of the site running in production, so a rollback had a named target.
- The encryption key. Nothing in this release touched it. We confirmed the offline copies exist.
- The media. We skipped the bucket copy this time, a deliberate judgement: nothing in 1.4 writes to media storage. On a release that does, it would come first.
A release with no migrations does not strictly need an upgrade-specific recovery point; emDash's own upgrade guidance says as much. We take one anyway, because it costs minutes, and because the only upgrade you regret not backing up is the one that surprised you.
Let the updater write the work order
emDash ships an updater that does the mechanical part and, more usefully, writes a checklist for the rest. We ran it on a new branch, never on the main line, first as a dry run:
npx upgrade-emdash@latest --dry-runnpx upgrade-emdash@latest
The dry run listed what would change: emDash and its Cloudflare package from 1.3.0 to 1.4.0, the forms plugin from 0.2.10 to 0.2.12 and the embeds plugin from 0.1.56 to 0.1.58. The real run installed them, refreshed the emDash instructions for AI coding assistants, and wrote a work order, .emdash/UPGRADE.md. It holds every changelog entry the upgrade crosses, including the 1.3.1 patch release we had not installed separately, and a comparison of the database migrations before and after: none added.
The first run failed with a package "not found" error for a component that had been published minutes earlier. The package manager was reading an out-of-date cached copy of the registry. Clearing that cache for emDash's packages fixed it, exactly as it had for 1.3. If you see the same error, the cause is the cache, not the release.
Reading 61 entries without skimming
The work order listed 61 changes. Most describe fixes that need nothing from you. The skill is in finding the few that do, and the reliable way is to search your own code for the thing each entry touches rather than to judge from the description.
Five entries carried a "What should I do?" section. Here is how they applied to this site:
| Change | Applies here? | What we did |
|---|---|---|
| Cached pages no longer send a modification time | Yes | One code change, described below |
| Media usage missed in collections with a reference field named title or name | No such fields | Checked the tracking status anyway; see the last step |
| Search errors on searchable reference fields | No searchable reference fields | Nothing |
| Menu translation failures on D1 | No translated menus | Nothing |
| Sites applying migrations on the first request | No; our deployment applies them | Nothing |
The other entries got the same treatment, with a quick search rather than a judgement call. Do our plugins rely on the sandbox fixes? No sandboxed plugins here. Does anything send an empty string for an image field? Nothing does. Does the forms plugin's new Reply-To need our email plugin to pass it through? It does, and ours already did. That last one changed behaviour our editors would notice, so it went to the editor for a decision before we went on.
The one code change
Before 1.4, emDash stamped each cached page with the modification time of the entry it showed. As a side effect, the framework underneath also told browsers to check with the site before reusing their copy. 1.4 dropped the timestamp, for good reasons covered in caching emDash on Cloudflare, and the browser instruction went with it.
Nothing would have broken visibly. Pages would simply have reached browsers with no caching instruction at all, leaving each browser to guess. We restored the instruction ourselves, in a few lines of the site's own middleware, for public pages that don't set their own. In outline:
if (isPublicPage(pathname) && isHtml(response) && !response.headers.has("Cache-Control")) { response.headers.set("Cache-Control", "no-cache");}
The edge cache was unaffected: Cloudflare's own lifetime for our pages comes from a separate header. We also added the header to our automated checks, so a future release that changes it again fails the check rather than slipping through.
Check the database, not the release notes
The release notes said no migrations. The updater said none were added. We still checked the production database directly, because the question that matters is not what the release adds but whether the database matches what the new code expects. A site that skipped a release, or had a migration fail quietly months ago, can be out of step without anyone knowing.
The answer was clean: the production database held exactly the 90 migrations that emDash 1.4 knows about, none missing and none unexpected. Our deployment repeats that check on every release before the new code goes live. It stops on any migration it doesn't recognise, and applies pending ones only after a recovery point has been recorded.
What the preview caught
Every change to this site runs first on a private preview, with automated checks that publish content, edit widgets and read the pages back. On 1.4, one check failed: after reordering the widgets in the sidebar, the page still showed the old order.
The cause was a 1.4 performance change. emDash now keeps widget areas in its shared cache, so pages no longer query the database for them on every view. When a widget changes, the cache is marked out of date at once in the copy of the site that handled the change, but other running copies learn of it a few seconds later. On our preview the gap was 2.0 to 3.5 seconds. We changed the check to wait for it, and reported the timing to the emDash project, because on a site with page caching an unlucky visit inside that window can keep the old sidebar for the page's whole cache lifetime.
This is the case for running every upgrade through a preview with real checks. The release notes did not mention this behaviour, and nothing in production would have flagged it. A reader would have seen an old sidebar, an editor would have saved again, and the cause would never have been found.
Deploy, then prove it
The editor reviewed and merged the change. Our deployment then ran its fixed sequence: record a recovery point, confirm the database is the production one and list pending and unknown migrations (none), skip the apply step because nothing was pending, deploy, and confirm afterwards that nothing is pending. It took six minutes.
Then we checked production by hand rather than trusting the green tick:
- Pages and headers. The home page and articles loaded, carried the restored browser instruction and were served from the edge cache on repeat visits.
- Publishing. We republished one entry and confirmed the cached page was replaced at once.
- The version. The site's masthead, which reads the installed version, said 1.4.0.
- Forms. A test enquiry produced a notification whose Reply-To was the sender's address, the new behaviour the editor had approved.
The step almost everyone will miss
The release notes ask sites to check Settings → Media usage tracking after upgrading. That page shows whether emDash knows which entries use each image, which is what lets the media library warn you before you delete an image that is still in use.
Ours did not say Ready. It had been stuck since an earlier change on 24 October, with every collection waiting. Reading emDash's source explained why: the job that catches up on older entries only advances while an administrator has that settings page open. The scheduler does not run it. Ours had not taken a single step since 24 October.
Opening it fixed it. The editor left the page open and all nine collections finished in about two minutes. Nothing was broken and no repair command was needed, but nothing would have finished the job without someone opening that page.
Making upgrades routine
- Running an emDash site. Upgrade promptly, but never casually. The routine above is cheap enough to run on every release, and the one release where it matters will not announce itself in advance.
- Commissioning a site. Ask the builder to show you the upgrade procedure, not just the launch. A site that is easy to build but hard to keep current becomes expensive by its second year.
- Running an agency. The work order makes upgrades predictable enough to price. Standardise the routine across client sites, and add the media usage step to the handover notes; your clients' editors will not find it on their own.




