Databases and storage
Open Project › Storage and choose Add storage. Pick what you need, keep the suggested name, and HostEdge creates it in a few seconds. Your next deployment can use it as env.<BINDING>; there are no connection strings or keys to copy.
| Type | Built on | Use it for |
|---|---|---|
| SQL database | SQLite | Users, orders, anything with rows and queries |
| Key-value store | Workers KV | Settings, feature flags, sessions, cached responses |
| File storage | Object storage | User uploads, images, exports. No download fees |
Each project gets its own resources. By default previews and production share them; give previews their own copy (below) to keep test data apart.
Connect it to your code#
Storage reaches the code that runs on HostEdge: a Worker, or the server side of Astro, SvelteKit, Nuxt, Remix or Next.js. A static site has no server code to use it.
After you add storage, the dashboard shows Redeploy to connect env.DB until a deployment includes it.
// SQL database (binding DB)
const { results } = await env.DB.prepare('SELECT * FROM users WHERE email = ?').bind(email).all()
await env.DB.prepare('INSERT INTO users (email) VALUES (?)').bind(email).run()
// Key-value store (binding CACHE)
await env.CACHE.put('flag:beta', 'on', { expirationTtl: 3600 })
const beta = await env.CACHE.get('flag:beta')
// File storage (binding FILES)
await env.FILES.put(`avatars/${userId}.png`, request.body)
const file = await env.FILES.get(`avatars/${userId}.png`)
Binding names use capital letters, numbers and underscores. They cannot clash with an environment variable of the same project.
Browse and edit data#
Developers, admins and owners can work with the data from the dashboard. Viewers see which storage exists, not what is in it.
- SQL console. Run any SQL, one statement or several separated by semicolons. Click a table to see its rows. Results show the first 500 rows of each statement. Press Ctrl + Enter to run. The download icon next to a table saves it as CSV, and Download .sql saves the whole database (schema, rows, indexes, views) as SQL you can load into an empty database with any SQLite tool or paste back into the console. Exports include up to 100,000 rows per table.
- Keys. Filter by prefix, open a key to read or edit its value (up to 1 MB here), set an expiry, or delete it.
- Files. Browse folders, upload files up to 25 MB each, download or delete them. Downloads always save as a file, so uploaded HTML never runs in the dashboard. Your code can write files of any size.
Preview copies#
On a database, key-value store or file storage page, Give previews their own copy creates an empty copy that preview deployments use under the same binding, so env.DB in a preview branch never touches production data. New preview deployments pick it up; production is unchanged. A Production / Preview copy switch at the top of the page moves between the two.
For a database, Copy production data here replaces the copy's contents with production's tables, rows (up to 100,000 per table), indexes and views, so you can test against real data. Delete preview copy sends previews back to production's data. Copies do not count against your plan's storage.
Restore a database to a point in time#
Admins and owners can roll a database back to any minute in the last 30 days under Restore to a point in time, for example after a bad migration. Everything written since that moment is replaced, for every deployment that uses the database. Right after a restore, Undo restore brings back the state from just before it. Restores are recorded in the audit log. This uses point-in-time recovery, so it is not available in local mode.
Connect your own Postgres#
Outgrown the built-in database? Connect Postgres on the Storage page takes a connection string from Neon, Supabase or any Postgres host and saves it, encrypted, as DATABASE_URL in every environment. Redeploy production to use it. HostEdge checks the string's format and warns about common mistakes (a Supabase direct host instead of the Transaction pooler, a Neon string without the pooler, SSL turned off); it does not open a connection.
import postgres from 'postgres'
const sql = postgres(env.DATABASE_URL, { prepare: false })
const users = await sql`select * from users limit 10`
Use the host's pooled connection string when many visitors arrive at once, since each request is a short-lived connection. A database on your own computer can't be reached.
Sign-in for your app#
A SQL database can also hold your app's users: see Authentication.
Limits#
| Plan | Storage per project |
|---|---|
| Free | 3 |
| Starter | 10 |
| Pro | 25 |
| Agency | 50 |
Storage counts every database, key-value store and file bucket together. Per-store platform limits apply too (for example, 10 GB per database).
Each plan also includes an amount of data stored (databases and file storage together) and key-value operations per month; see Limits. HostEdge measures them about once an hour and shows them under Settings › Billing › Usage this month, and each storage page shows its own size. Going over does not stop anything; admins get a notice at 80 % and 100 %.
Deleting#
Delete… on the storage page removes it with everything inside, including its preview copy. Deleting a project deletes its storage too. Code that still uses the binding fails until you remove it, so redeploy afterwards.