+
+
{t`Automatic backups need a storage backend (R2, S3, or local storage). Configure storage in your EmDash config to enable them.`}
{archive.name}
- {new Date(archive.lastModified).toLocaleString()} · {formatBytes(archive.size)}
+ {i18n.date(new Date(archive.lastModified), {
+ dateStyle: "medium",
+ timeStyle: "short",
+ })}{" "}
+ · {formatBytes(archive.size)}
diff --git a/packages/core/src/api/errors.ts b/packages/core/src/api/errors.ts
index 9a2a152c4d..d76e632819 100644
--- a/packages/core/src/api/errors.ts
+++ b/packages/core/src/api/errors.ts
@@ -293,6 +293,15 @@ export const ErrorCode = {
EMAIL_SETTINGS_READ_ERROR: "EMAIL_SETTINGS_READ_ERROR",
EMAIL_TEST_ERROR: "EMAIL_TEST_ERROR",
+ // Backups
+ BACKUP_SETTINGS_READ_ERROR: "BACKUP_SETTINGS_READ_ERROR",
+ BACKUP_SETTINGS_ERROR: "BACKUP_SETTINGS_ERROR",
+ BACKUP_EXPORT_ERROR: "BACKUP_EXPORT_ERROR",
+ BACKUP_LIST_ERROR: "BACKUP_LIST_ERROR",
+ BACKUP_CREATE_ERROR: "BACKUP_CREATE_ERROR",
+ BACKUP_DELETE_ERROR: "BACKUP_DELETE_ERROR",
+ BACKUP_DOWNLOAD_ERROR: "BACKUP_DOWNLOAD_ERROR",
+
// Search
SEARCH_ERROR: "SEARCH_ERROR",
STATS_ERROR: "STATS_ERROR",
diff --git a/packages/core/src/api/handlers/backup.ts b/packages/core/src/api/handlers/backup.ts
index 0bd4aedb42..5a8c87e292 100644
--- a/packages/core/src/api/handlers/backup.ts
+++ b/packages/core/src/api/handlers/backup.ts
@@ -24,6 +24,7 @@ import { OptionsRepository } from "../../database/repositories/options.js";
import type { Database } from "../../database/types.js";
import type { Storage } from "../../storage/types.js";
import { VERSION } from "../../version.js";
+import { ErrorCode } from "../errors.js";
import type { ApiResult } from "../types.js";
import { generateSnapshot } from "./snapshot.js";
@@ -164,7 +165,7 @@ export async function updateBackupSettings(
console.error("[backup] Failed to update settings:", error);
return {
success: false,
- error: { code: "BACKUP_SETTINGS_ERROR", message: "Failed to update backup settings" },
+ error: { code: ErrorCode.BACKUP_SETTINGS_ERROR, message: "Failed to update backup settings" },
};
}
}
@@ -196,7 +197,7 @@ export async function listBackupArchives(storage: Storage): Promise
{
if (error instanceof Error && error.message.toLowerCase().includes("not found")) {
return apiError("NOT_FOUND", "Archive not found", 404);
}
- return handleError(error, "Failed to download archive", "BACKUP_DOWNLOAD_ERROR");
+ return handleError(error, "Failed to download archive", ErrorCode.BACKUP_DOWNLOAD_ERROR);
}
};
diff --git a/packages/core/src/astro/routes/api/settings/backups/export.ts b/packages/core/src/astro/routes/api/settings/backups/export.ts
index bd5da98d53..6cc16fbce6 100644
--- a/packages/core/src/astro/routes/api/settings/backups/export.ts
+++ b/packages/core/src/astro/routes/api/settings/backups/export.ts
@@ -10,6 +10,7 @@ import type { APIRoute } from "astro";
import { requirePerm } from "#api/authorize.js";
import { apiError, handleError } from "#api/error.js";
+import { ErrorCode } from "#api/errors.js";
import { archiveNameForDate, generateBackupJson } from "#api/handlers/backup.js";
export const prerender = false;
@@ -37,6 +38,6 @@ export const GET: APIRoute = async ({ locals }) => {
},
});
} catch (error) {
- return handleError(error, "Failed to generate backup", "BACKUP_EXPORT_ERROR");
+ return handleError(error, "Failed to generate backup", ErrorCode.BACKUP_EXPORT_ERROR);
}
};
diff --git a/packages/core/src/astro/routes/api/settings/backups/index.ts b/packages/core/src/astro/routes/api/settings/backups/index.ts
index 7b15b505b0..cd5f017307 100644
--- a/packages/core/src/astro/routes/api/settings/backups/index.ts
+++ b/packages/core/src/astro/routes/api/settings/backups/index.ts
@@ -11,6 +11,7 @@ import { z } from "zod";
import { requirePerm } from "#api/authorize.js";
import { apiError, apiSuccess, handleError, unwrapResult } from "#api/error.js";
+import { ErrorCode } from "#api/errors.js";
import {
BACKUP_RETENTION_MAX,
BACKUP_RETENTION_MIN,
@@ -45,7 +46,11 @@ export const GET: APIRoute = async ({ locals }) => {
return apiSuccess({ settings, archives, storageAvailable });
} catch (error) {
- return handleError(error, "Failed to load backup settings", "BACKUP_SETTINGS_READ_ERROR");
+ return handleError(
+ error,
+ "Failed to load backup settings",
+ ErrorCode.BACKUP_SETTINGS_READ_ERROR,
+ );
}
};
From 2f9c8d7a414ca0fefc3fb01be1888c9ad3ce112e Mon Sep 17 00:00:00 2001
From: swissky <30409887+swissky@users.noreply.github.com>
Date: Sat, 11 Jul 2026 08:43:42 +0200
Subject: [PATCH 5/5] docs: drop the stacked note aside, keep the cron detail
as body text
---
docs/src/content/docs/guides/backups.mdx | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/docs/src/content/docs/guides/backups.mdx b/docs/src/content/docs/guides/backups.mdx
index c361cbdef7..07863992a0 100644
--- a/docs/src/content/docs/guides/backups.mdx
+++ b/docs/src/content/docs/guides/backups.mdx
@@ -57,12 +57,10 @@ Archives are stored under the `backups/` prefix in your bucket as `emdash-backup
through EmDash (or scope your public domain to the media prefix).
-
- Automatic backups piggyback on the scheduled maintenance tick (the same mechanism that powers
- scheduled publishing). On Cloudflare this is the Worker's cron trigger; on Node it's the built-in
- scheduler. If your deployment has no cron trigger configured, use **Back up now** or the download
- button instead.
-
+Automatic backups piggyback on the scheduled maintenance tick (the same mechanism that powers
+scheduled publishing) — on Cloudflare this is the Worker's cron trigger, on Node the built-in
+scheduler. If your deployment has no cron trigger configured, use **Back up now** or the download
+button instead.
## Point-in-time restore with D1 Time Travel