I'm using Cloudflare Pages with KV.

I'm using Cloudflare Pages with KV. My goal is to set up KV for different environments, to have: 1. one KV id for production, and 2. another KV id for all other environments (preview URLs, local dev with miniflare maybe, etc) The docs show two approaches: v1
kv_namespaces = [
{ binding = "KV", id = "abc123", preview_id = "xyz456" }
]
kv_namespaces = [
{ binding = "KV", id = "abc123", preview_id = "xyz456" }
]
OR v2 (using environments)
kv_namespaces = [
{ binding = "MY_KV", id = "abc123" }
]

[env.production]
route = "example.com/*"
kv_namespaces = [
{ binding = "MY_KV", id = "xyz456" }
]
kv_namespaces = [
{ binding = "MY_KV", id = "abc123" }
]

[env.production]
route = "example.com/*"
kv_namespaces = [
{ binding = "MY_KV", id = "xyz456" }
]
- which is preferred and why? - for the first version, how does Cloudflare differentiate when to use the KV represented by id or preview_id?
9 Replies
jason
jasonOP4mo ago
The docs & ChatGPT weren't helpful. To figure this out, I: 1. Added production and preview bindings to my Cloudflare Pages project using the Cloudflare dashboard (screenshot). 2. Generated a wrangler.toml using bunx wrangler@latest pages download config myproject, to see what it creates:
[[kv_namespaces]]
id = "abc123"
binding = "KV"

[[env.production.kv_namespaces]]
id = "xyz456"
binding = "KV"
[[kv_namespaces]]
id = "abc123"
binding = "KV"

[[env.production.kv_namespaces]]
id = "xyz456"
binding = "KV"
I believe that is equivalent toml to this:
kv_namespaces = [
{ binding = "KV", id = "abc123" }
]

[env.production]
kv_namespaces = [
{ binding = "KV", id = "xyz456" }
]
kv_namespaces = [
{ binding = "KV", id = "abc123" }
]

[env.production]
kv_namespaces = [
{ binding = "KV", id = "xyz456" }
]
1. I assume it only makes sense to use the same binding name for production and preview, so my code works consistently. 2. I assume production applies only when mycustomdomain.com and any *.pages.dev is preview. 3. Will the non-production kv_namespace be used for everything that is not production (e.g. CI, local tests, local dev?). Or should I add preview_id="abc123" to cover those scenarios? The docs could be improved on this topic.
No description
wilsonianb
wilsonianb4mo ago
https://developers.cloudflare.com/pages/functions/wrangler-configuration/#environment-specific-overrides
If you wanted to have configuration values apply to local and preview, but override production, your file would look like this:
name = "my-pages-site"
pages_build_output_dir = "./dist"

[[kv_namespaces]]
binding = "KV"
id = "<NAMESPACE_ID>"

[vars]
API_KEY = "1234567asdf"

[[env.production.kv_namespaces]]
binding = "KV"
id = "<PRODUCTION_NAMESPACE_ID>"

[env.production.vars]
API_KEY = "8901234bfgd"
name = "my-pages-site"
pages_build_output_dir = "./dist"

[[kv_namespaces]]
binding = "KV"
id = "<NAMESPACE_ID>"

[vars]
API_KEY = "1234567asdf"

[[env.production.kv_namespaces]]
binding = "KV"
id = "<PRODUCTION_NAMESPACE_ID>"

[env.production.vars]
API_KEY = "8901234bfgd"
jason
jasonOP4mo ago
Appreciate the link. Looks like that's how mine ended up above too. Thanks for the confirmation!
thomasgauvin
thomasgauvin4mo ago
@r2d2 The binding id is what is used when the worker is deployed. The preview_id is used when you're running wrangler dev with the --remote option. In your case, seems like you've gotten to the right conclusion that environments is the right tool. I'll make sure to clarify the preview_id in the docs (PR: https://github.com/cloudflare/cloudflare-docs/pull/15978)
jason
jasonOP3mo ago
Thanks @thomasgauvin One more question while you're here: what env is used for wrangler dev when the --remote option is not provided--a local or dev env I see from your PR? Is it possible to define bindings for local mock versions of R2 & D1 in my wrangler.toml ? Or is KV the only of these that has a local version?
(I'm new to wrangler dev, so probably a basic question. Not even sure how to get hot reloading yet)
thomasgauvin
thomasgauvin3mo ago
No problem, when you use wrangler dev, all storage resources will create a local only KV/D1/R2, which are associated to the ids in the binding (ie switching the id in wrangler.toml will switch the local version) You'll notice that all of the services (R2, D1, KV) have the notice regarding wrangler dev using the local copy https://developers.cloudflare.com/r2/api/workers/workers-api-usage/#4-access-your-r2-bucket-from-your-worker https://developers.cloudflare.com/kv/get-started/#4-access-your-kv-namespace-from-a-worker https://developers.cloudflare.com/d1/build-with-d1/local-development/#start-a-local-development-session
jason
jasonOP3mo ago
Thanks again @thomasgauvin Helpful! Idea: When running the wrangler pages dev command, it'd be informative--and reassuring--if it printed out what resource bindings are active. For example: wrangler pages dev Active bindings: - D1: FOO (local) - KV: BAR (local) - R2: BAZ (local) wrangler pages dev --remote Active bindings: - D1: FOO (remote: <foo-name>, <id>) - KV: BAR (remote: <bar-name>, <id>) - R2: BAZ (remote: <baz-name>, <id>) or No bindings. Add to wrangler.toml. Example: <linkShowingEveryResourceSetUpForRemoteAccess> (Duplicate a bulleted line if there are multiple bindings for a given resource type.) Another piece of DX feedback: Sticking with just name and id for all of these in wrangler.toml would be more consistent; otherwise I found myself needing to look up what the expected key name is, which isn't needed given each is already scoped by kv_namespaces, r2_buckets, d1_databases, etc.
kv_namespaces = [
{ binding = "KV", id = "abc123" }
]
r2_buckets = [
{ binding = "R2", bucket_name = "mysite-prod" }
]
d1_databases = [
{ binding = "D1", database_name = "mysite-prod", database_id = "xyz456" }
kv_namespaces = [
{ binding = "KV", id = "abc123" }
]
r2_buckets = [
{ binding = "R2", bucket_name = "mysite-prod" }
]
d1_databases = [
{ binding = "D1", database_name = "mysite-prod", database_id = "xyz456" }
I take it back--the CLI does output which bindings are active, in much the same way I suggested! lol (Although it doesn't show anything to indicate when no bindings are active, which would help.) It was just that none of my bindings were active until I passed bunx wrangler pages dev --d1 <binding> and saw this output for the first time. Thought the bindings would be picked up from my wrangler.toml like they are for other environments. Posting in case it helps other searchers: Summary of environments for Cloudflare Pages, as I've learned: In wrangler.toml: 1. [env.production] specifies bindings for production. 2. Other bindings not within [env.production] are for preview environment (i.e. non-production branch, preview URLs). These resources are also used if wrangler dev --remote flag is specified. But wrangler pages dev --remote does NOT work for remote resources for Pages projects for D1 as noted in docs (not sure about KV and R1 yet), and this is fine b/c I don't need or want remote access when a project is run locally. 3. I removed preview_database_id for D1, preview_id for KV, and preview_bucket_name for R2 b/c Pages cannot access a remote D1 database anyway as noted in docs. 5. Locally, run wrangler pages dev --d1 D1 --kv KV --r2 R2 --live-reload to bind local resources for KV, R2, and D1. Wrangler will create these after this command is run the first time and their data will be persisted in .wrangler/state/v3 between runs. (TBD on how to clear our these resources and seed them consistently each time, but I'll figure that out.)
kv_namespaces = [
{ binding = "KV", id = "<id>" }
]
r2_buckets = [
{ binding = "R2", bucket_name = "mysite-preview" }
]
d1_databases = [
{ binding = "D1", database_name = "mysite-preview", database_id = "<id>" }
]

[env.production]
kv_namespaces = [
{ binding = "KV", id = "<id>" }
]
r2_buckets = [
{ binding = "R2", bucket_name = "mysite-prod" }
]
d1_databases = [
{ binding = "D1", database_name = "mysite-prod", database_id = "<id>" }
]
kv_namespaces = [
{ binding = "KV", id = "<id>" }
]
r2_buckets = [
{ binding = "R2", bucket_name = "mysite-preview" }
]
d1_databases = [
{ binding = "D1", database_name = "mysite-preview", database_id = "<id>" }
]

[env.production]
kv_namespaces = [
{ binding = "KV", id = "<id>" }
]
r2_buckets = [
{ binding = "R2", bucket_name = "mysite-prod" }
]
d1_databases = [
{ binding = "D1", database_name = "mysite-prod", database_id = "<id>" }
]
I'd prefer if wrangler set up local bindings automatically based on those inn wrangler.toml instead of needing to pass them as options to wrangler pages dev --d1 D1 --r2 R2 --kv KV. But super happy to have figured this out! Thanks so much
thomasgauvin
thomasgauvin3mo ago
Thanks for all this feedback! I want to make some time to read this
jason
jasonOP3mo ago
TDLR on feedback: - wrangler pages dev should print a message when no bindings are active too, so it's clear to new devs when things are not yet set up properly but they're unaware. - In wrangler.toml, consider more consistent naming of id and name, within each resource, because they're already scoped within kb_namespaces, r2_buckets, & d1_databases. The current mixed property naming convention (below) means devs needs to check the docs to see what each expects because it's not as consistent/predictable as it could be. - KV id - D1 database_id, database_name - R2 bucket_name Thanks for the help earlier! Update for future searchers: the above env setup will eventually encounter a couple small edge cases. Use the following instead:
[[kv_namespaces]]
binding = "KV"
id = "<id>"
preview_id = "<id2>"

[[r2_buckets]]
binding = "R2"
bucket_name = "mysite-prod"
preview_bucket_name = "mysite-preview"

[[d1_databases]]
binding = "D1"
database_name = "mysite-prod"
database_id = "<uuid>"
preview_database_id = "<uuid2>"
migrations_dir = "./src/lib/server/database/migrations"
[[kv_namespaces]]
binding = "KV"
id = "<id>"
preview_id = "<id2>"

[[r2_buckets]]
binding = "R2"
bucket_name = "mysite-prod"
preview_bucket_name = "mysite-preview"

[[d1_databases]]
binding = "D1"
database_name = "mysite-prod"
database_id = "<uuid>"
preview_database_id = "<uuid2>"
migrations_dir = "./src/lib/server/database/migrations"
And target local/preview/production with these flags:
"list": "wrangler d1 migrations list D1 --local",
"list:preview": "wrangler d1 migrations list D1 --remote --preview",
"list:production": "wrangler d1 migrations list D1 --remote",
"list": "wrangler d1 migrations list D1 --local",
"list:preview": "wrangler d1 migrations list D1 --remote --preview",
"list:production": "wrangler d1 migrations list D1 --remote",
Want results from more Discord servers?
Add your server