You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/src/content/docs/deployment/cloudflare.mdx
+10-15Lines changed: 10 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -454,29 +454,22 @@ the provider under **Settings → Email**.
454
454
455
455
## Environment Variables
456
456
457
-
### Recommended: encryption key
457
+
### Encryption key validation
458
458
459
-
`EMDASH_ENCRYPTION_KEY` is the key for encrypting plugin secrets at
460
-
rest (webhook tokens, Turnstile keys, etc.). The key is validated on
461
-
startup; plugin secret encryption uses it once enabled. Set it on
462
-
every deployment so secrets are protected without a later config
463
-
change.
459
+
`EMDASH_ENCRYPTION_KEY` does not currently encrypt plugin secrets. If the variable is set, EmDash
460
+
checks its format during startup, but plugin secret values remain unencrypted in the database.
464
461
465
-
The key is provided by you and never stored in the database; only
466
-
encrypted ciphertext is. Losing it means losing every secret encrypted
467
-
with it.
468
-
469
-
Generate a key and store it as a Worker secret with the following commands:
462
+
If you set the variable, generate a valid value and store it as a Worker secret with the following
463
+
commands:
470
464
471
465
```bash
472
466
npx emdash secrets generate
473
467
wrangler secret put EMDASH_ENCRYPTION_KEY
474
468
```
475
469
476
470
<Asidetype="caution">
477
-
Never commit the encryption key to your repository, and back it up
478
-
somewhere durable (a password manager, KMS, or your team's secret
479
-
store). The plain text value lives only in your environment.
471
+
Treat the database and its backups as sensitive because they contain plaintext plugin secrets.
472
+
Never commit secret values to your repository.
480
473
</Aside>
481
474
482
475
### Optional: stable-value overrides
@@ -493,7 +486,9 @@ needs to share the secret with your main site.
493
486
|`EMDASH_IP_SALT`| Override for the auto-generated commenter-IP hash salt. |
494
487
|`EMDASH_AUTH_SECRET`| Optional. If set, it is used as the IP-salt source (unless `EMDASH_IP_SALT` is also set, which takes precedence), keeping commenter-IP hashes stable for installs that already rely on it. Leave it unset for a new deployment. |
495
488
496
-
Access environment variables in your configuration using `import.meta.env` or the Cloudflare `env` binding.
489
+
Read EmDash secrets from `process.env` at runtime. Read Worker bindings from `env`, imported from
490
+
`cloudflare:workers`. Never read secrets through `import.meta.env`: Vite replaces those values at
491
+
build time and can write them into the server bundle.
497
492
498
493
For the complete inventory of every secret EmDash uses — including storage locations, rotation steps, and what breaks when a key is lost — see [Secrets & Key Management](/deployment/secrets/).
|[`EMDASH_ENCRYPTION_KEY`](#the-encryption-key)| Operator (`emdash secrets generate`) | Environment / Worker secret only |Encrypted plugin secrets become unrecoverable (once encryption at rest ships)|
14
+
|[`EMDASH_ENCRYPTION_KEY`](#the-encryption-key)| Operator (`emdash secrets generate`) | Environment / Worker secret only |No current data impact; EmDash only checks its format|
15
15
|[Preview secret](#preview-secret)| Auto-generated (env override) |`options` table (`emdash:preview_secret`) | Outstanding preview links stop working; new ones are fine |
|[Session & API tokens](#session-and-api-tokens)| Generated per session/token | Session store / database (hashes only) | Nothing — plaintext is never stored |
@@ -24,9 +24,12 @@ EmDash uses a small set of secrets across previews, comments, authentication, st
24
24
25
25
## The encryption key
26
26
27
-
`EMDASH_ENCRYPTION_KEY` is the site's key for encrypting plugin secrets at rest. It is **operator-provided and never stored in the database** — the database only ever holds ciphertext, so a leaked database backup does not expose the key.
27
+
`EMDASH_ENCRYPTION_KEY` does not currently encrypt plugin secrets or any other stored data. If the
28
+
variable is set, EmDash checks its format during startup. A malformed value produces an
29
+
operator-facing log message, but the site continues to handle requests.
28
30
29
-
Generate one and set it as an environment variable (or Worker secret):
31
+
The following command generates a correctly formatted value. Store it in the runtime environment
32
+
or as a Worker secret if your deployment uses this variable.
30
33
31
34
```bash
32
35
npx emdash secrets generate
@@ -36,23 +39,11 @@ npx emdash secrets generate
36
39
wrangler secret put EMDASH_ENCRYPTION_KEY
37
40
```
38
41
39
-
The format is `emdash_enc_v1_` followed by 32 random bytes as unpadded base64url. The key is validated at runtime startup; a malformed value logs an operator-facing error without taking down request paths.
42
+
The format is `emdash_enc_v1_` followed by 32 random bytes as unpadded base64url. The value is operator-provided and is not stored in the database. Losing it has no current data-recovery impact because no stored data depends on it.
40
43
41
44
<Asidetype="caution">
42
-
Back the key up somewhere durable (password manager, KMS, your team's secret store). Losing it
43
-
means losing every secret encrypted with it — the ciphertext in the database cannot be recovered.
44
-
</Aside>
45
-
46
-
### Rotation
47
-
48
-
The variable accepts a comma-separated list of keys. The **first** entry is the primary and is used for new writes; all entries are tried for decryption. Every encrypted value is tagged with an 8-character key fingerprint (the _kid_, printable via `emdash secrets fingerprint <key>`), so the runtime picks the right key automatically.
49
-
50
-
To rotate: generate a new key, prepend it to the list (`EMDASH_ENCRYPTION_KEY="new,old"`), redeploy, and drop the old key once existing values have been re-encrypted.
51
-
52
-
<Asidetype="note">
53
-
The encryption layer that consumes this key is not active yet — today the key is validated but
54
-
inert, and [plugin secrets](#plugin-secrets) are stored unencrypted. Set the key now so your
55
-
deployment is ready the moment plugin-secret encryption ships, without a config scramble.
45
+
Plugin secret values are stored as plaintext in the database. Setting
46
+
`EMDASH_ENCRYPTION_KEY` does not protect them. Restrict access to the database and its backups.
56
47
</Aside>
57
48
58
49
## Generated site secrets
@@ -106,8 +97,7 @@ Settings a plugin declares with `type: "secret"` (API keys for email providers,
106
97
<Asidetype="caution">
107
98
Plugin secrets are currently stored **unencrypted** in the database. Anyone with database access
108
99
(including backups) can read them. Prefer scoped, revocable API keys, and treat database backups
109
-
as sensitive. Encryption at rest using [`EMDASH_ENCRYPTION_KEY`](#the-encryption-key) is planned;
110
-
setting the key today requires no migration later.
100
+
as sensitive.
111
101
</Aside>
112
102
113
103
-**Rotation:** rotate the key at the provider and paste the new value into the plugin's settings page. Takes effect immediately.
@@ -134,7 +124,6 @@ The separate `emdash-plugin` CLI (package `@emdash-cms/plugin-cli`) targets the
EmDash gives you three layers of protection for your content, from zero-config point-in-time recovery on Cloudflare to downloadable archives you keep yourself.
8
+
Use a JSON backup when you need an offline copy of selected content data. Use a raw database backup
9
+
or point-in-time recovery when you need to restore the site.
9
10
10
11
## What's in a backup
11
12
12
-
A backup contains everything needed to reconstruct your site's content:
13
+
A JSON backup includes:
13
14
14
15
- All content entries, including drafts, scheduled posts, and trashed items
15
-
- Collection and field definitions (your content model)
16
-
- Taxonomies and term assignments
17
-
- Menus, widgets, sections, and SEO settings
18
-
- Revisions and media metadata
19
-
- Site settings (title, tagline, display preferences)
20
-
21
-
Backups deliberately **exclude**:
22
-
23
-
- User accounts, sessions, passkeys, and API tokens — auth data is neither portable nor safe in a downloadable file
- Media binaries — the actual files live in your storage bucket (R2, S3, or local); a backup carries their metadata so references stay intact
16
+
- Collection and field definitions that make up the content model
17
+
- Taxonomy definitions, terms, and the terms assigned to each entry
18
+
- Menus and menu items, sections, widget areas and widgets, SEO records, revision history, media
19
+
metadata, and database migration history
20
+
- Site settings such as the title, tagline, URL, locale, logo, display preferences, social profiles,
21
+
and SEO defaults. These come from the `site:`, `emdash:site_`, and `emdash:locale` setting groups.
22
+
23
+
It omits all other database tables, including:
24
+
25
+
- User accounts, sessions, passkeys, OAuth data, API tokens, and other authentication data
26
+
- Plugin storage and plugin settings, including plugin secrets
27
+
- Comments and reactions, redirects and 404 logs, bylines, content relations and references, audit logs, rate limits, and scheduled-task state
28
+
- Media folders, records of where media is used, incomplete or in-progress uploads, and the media
29
+
files themselves
30
+
- Other site options, including the preview signing secret and backup schedule
26
31
27
32
Backups are JSON files in the same snapshot format used by EmDash's preview system, versioned with the EmDash release that created them.
28
33
34
+
<Asidetype="caution">
35
+
EmDash does not implement restoring this JSON format. Do not rely on a JSON export as a recovery
36
+
point before a destructive operation. Use a raw database backup or point-in-time recovery instead.
37
+
</Aside>
38
+
29
39
## One-click download
30
40
31
41
Under **Settings → Backups** in the admin, the **Download backup** button generates a fresh backup and downloads it as a JSON file. Requires the admin role.
32
42
33
-
This is the right tool before risky operations: bulk imports, schema changes, or major upgrades.
43
+
The download is for inspection or custom migration tooling. Before bulk imports, schema changes, or
44
+
major upgrades, create a restorable database backup using one of the options below.
34
45
35
46
## Automatic backups to storage
36
47
@@ -78,12 +89,6 @@ Time Travel keeps 30 days of history on the paid plan (7 days on free) with minu
78
89
79
90
See the [D1 Time Travel documentation](https://developers.cloudflare.com/d1/reference/time-travel/) for details.
80
91
81
-
<Asidetype="tip">
82
-
Time Travel and EmDash backups complement each other: Time Travel is your disaster-recovery net
83
-
for the last 30 days; downloaded backups are user-holdable archives with no expiry that survive
84
-
account or database deletion.
85
-
</Aside>
86
-
87
92
## Offsite database dumps
88
93
89
94
For a complete SQL dump of the raw database (including users and auth tables), use Wrangler:
@@ -92,12 +97,15 @@ For a complete SQL dump of the raw database (including users and auth tables), u
On Node deployments, the database is a single SQLite file — copy it while the server is stopped, or use `sqlite3 emdash.db ".backup backup.db"` for a consistent online copy.
100
+
Restore that raw SQL dump with the following command:
On Node deployments, the database is a single SQLite file — copy it while the server is stopped, or use `sqlite3 emdash.db ".backup backup.db"` for a consistent online copy.
98
107
99
-
Restoring from a backup JSON is intentionally not exposed as a one-click admin action yet — overwriting a live database deserves more friction than a button. Current options:
108
+
## JSON restore
100
109
101
-
-**Cloudflare:** use D1 Time Travel (above) for point-in-time restore.
102
-
-**Full dumps:** import a `wrangler d1 export` dump with `npx wrangler d1 execute my-database --remote --file=backup.sql`.
103
-
-**Backup JSON:** the format matches EmDash's snapshot format; a guided CLI restore is planned. Track [Discussion #142](https://github.com/emdash-cms/emdash/discussions/142).
110
+
EmDash has no admin action, API endpoint, or CLI command for JSON restore. Use D1 Time Travel, a raw
111
+
D1 SQL dump, or a copy of the SQLite database as described above.
Copy file name to clipboardExpand all lines: docs/src/content/docs/plugins/creating-plugins/api-routes.mdx
+6-2Lines changed: 6 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -114,7 +114,7 @@ routes: {
114
114
115
115
Private routes require the `X-EmDash-Request: 1` CSRF header for cookie-authenticated requests. The admin UI sends it automatically; token-authenticated requests are exempt.
116
116
117
-
To opt a route out of auth and CSRF, mark it `public: true`:
117
+
To opt a route out of authentication, mark it `public: true`:
118
118
119
119
```typescript
120
120
routes: {
@@ -130,7 +130,11 @@ routes: {
130
130
```
131
131
132
132
<Asidetype="caution">
133
-
Public routes skip authentication, scope, and CSRF entirely. Anyone on the internet can call them. Use `public: true` only for endpoints that need to accept external traffic — webhooks, public-facing search endpoints — and validate the input carefully.
133
+
A public route skips authentication and token scope checks, but not protection against cross-origin
134
+
browser requests. For requests that can change data, EmDash accepts a matching site `Origin` or the
135
+
`X-EmDash-Request: 1` header; a different or malformed `Origin` returns 403. Webhooks and other
136
+
non-browser clients usually omit `Origin` and remain allowed, so anyone can call a public route.
137
+
Validate all input and verify the service's signature or token when available.
0 commit comments