Declarar bindings en wrangler.jsonc
El manifiesto del Worker es donde una capacidad pasa de idea a hecho: cada tipo de binding tiene su sección en wrangler.jsonc, con un nombre lógico que se vuelve una propiedad de env y un puntero al recurso real. La anatomía de cada sección, por qué las variables van aquí y los secretos no, y cómo el nombre se conecta al recurso concreto.
Un binding no existe hasta que lo declaras, y el lugar de esa declaración es wrangler.jsonc, el manifiesto del Worker. Aquí es donde una capacidad abstracta —quiero acceso a esta base de datos— se convierte en una entrada concreta que la plataforma sabrá resolver. Cada tipo de recurso tiene su propia sección con su propia forma, pero todas obedecen a un mismo patrón: un nombre lógico para tu código y un puntero al recurso real del mundo.
- Leer la anatomía de una declaración: el nombre del binding y el puntero al recurso.
- Localizar la sección de
wrangler.jsoncque corresponde a cada tipo de binding. - Conectar un nombre lógico como
env.DBcon un recurso real por suido su nombre. - Distinguir dónde viven las variables y por qué los secretos no van en el manifiesto.
La anatomía de una declaración
Toda declaración de binding tiene dos mitades. La primera es el nombre lógico —la clave binding, o name en el caso de los Durable Objects— que se convertirá en la propiedad homónima de env. La segunda es un puntero al recurso concreto: un id, un bucket_name, un database_id, según el tipo. Tu código habla siempre del nombre lógico; la config es la única que conoce el recurso real detrás.
{
// el nombre logico se vuelve env.MI_KV en tu codigo
"kv_namespaces": [
{ "binding": "MI_KV", "id": "a1b2c3d4e5f6" }
]
}
Empieza el manifiesto con la clave $schema: apunta al esquema que trae Wrangler y hace que tu editor autocomplete cada sección y valide cada clave. No es obligatoria, pero escribir bindings sin ella es renunciar a la red de seguridad que evita la mayoría de los errores de tipeo.
Una sección por tipo de recurso
Cada familia de bindings tiene su propia clave de nivel superior, y su forma refleja lo que la plataforma necesita para resolver el recurso. Este manifiesto reúne las secciones más frecuentes en un solo lugar para que veas el patrón repetirse:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "mi-worker",
"main": "src/index.ts",
"compatibility_date": "2026-07-15",
// KV: nombre logico + id del namespace
"kv_namespaces": [
{ "binding": "MI_KV", "id": "<ID_DEL_NAMESPACE>" }
],
// R2: nombre logico + nombre del bucket
"r2_buckets": [
{ "binding": "MI_BUCKET", "bucket_name": "mis-archivos" }
],
// D1: nombre logico + nombre e id de la base de datos
"d1_databases": [
{
"binding": "DB",
"database_name": "produccion",
"database_id": "<ID_DE_LA_DB>"
}
],
// Durable Objects: name + la clase que lo implementa
"durable_objects": {
"bindings": [
{ "name": "SALA", "class_name": "SalaDeChat" }
]
},
// Queues: productor (send) y consumidor (handler)
"queues": {
"producers": [
{ "binding": "MI_COLA", "queue": "trabajos" }
],
"consumers": [
{ "queue": "trabajos" }
]
},
// service binding: apunta a otro Worker por su nombre
"services": [
{ "binding": "AUTH", "service": "worker-de-auth" }
],
// Workers AI: basta el nombre del binding
"ai": { "binding": "AI" },
// Vectorize: nombre logico + nombre del indice
"vectorize": [
{ "binding": "VEC", "index_name": "embeddings-productos" }
],
// variables de texto plano, publicas y versionadas
"vars": { "MODO": "produccion" }
}
El patrón salta a la vista. Los almacenes se listan como arrays de objetos, cada uno con su binding y su puntero: id para KV, bucket_name para R2, database_name con database_id para D1. Los Durable Objects viven bajo durable_objects.bindings y usan name en lugar de binding, más class_name para señalar la clase de tu código que los implementa. Queues se parte en producers y consumers. Un service binding apunta a otro Worker por su service, y Vectorize a su índice por index_name.
Declarar el binding de un Durable Object le dice a la plataforma cómo llamarlo desde env, pero no basta para crearlo. La primera vez que introduces una clase, añades también una migración —la sección migrations— que anuncia esa clase nueva. Es el mecanismo por el que Cloudflare provisiona el almacenamiento del objeto. Sin la migración, el binding apunta a una clase que la plataforma aún no reconoce.
Del nombre lógico al recurso real
Esa separación entre nombre y puntero es la que hace portátil a tu código. Como env.DB es un nombre lógico, puedes apuntarlo a una base de datos en desarrollo y a otra en producción cambiando solo el database_id de la config —o usando entornos—, sin tocar una línea del Worker. El código pide una capacidad por su nombre; la config decide qué recurso concreto la satisface.
Las variables encajan aquí con naturalidad: la sección vars guarda pares de texto plano que son públicos y viajan versionados en el repositorio junto al manifiesto. Los secretos, en cambio, son la excepción deliberada. No van en wrangler.jsonc, porque todo lo que escribes ahí acaba en el control de versiones. Se suben por un canal aparte, cifrados, y solo entonces aparecen en env como cadenas.
# un secreto NO va en wrangler.jsonc: se sube cifrado, aparte
npx wrangler secret put MI_SECRETO
# .dev.vars — secretos solo para desarrollo local, fuera del control de versiones
MI_SECRETO=valor-solo-local
API_KEY=clave-de-pruebas
Es el error más caro y más fácil de cometer: meter una clave de API en vars porque es cómodo. Como wrangler.jsonc se versiona, ese secreto queda grabado para siempre en la historia de git, al alcance de cualquiera con acceso al repositorio. La regla es tajante: si un valor no puede aparecer en un repositorio público, es un secreto y se sube con wrangler secret put o se pone en .dev.vars, jamás en el manifiesto.
flowchart LR DECL[seccion en wrangler jsonc] --> NAME[nombre logico como binding o name] DECL --> PTR[puntero al recurso como id o nombre] NAME --> ENV[propiedad en env] PTR --> RES[recurso real de tu cuenta] ENV --> USO[tu codigo usa env.LO_QUE_SEA] RES --> USO style DECL fill:#89b4fa,color:#11111b style USO fill:#a6e3a1,color:#11111b
Es tentador leer wrangler.jsonc como un formulario que rellenas para que las cosas funcionen, pero conviene verlo como lo que de verdad es: la declaración completa y auditable de todo lo que tu Worker puede tocar en el mundo. Cada sección de bindings es una cláusula de un contrato, y ese contrato tiene tres virtudes que definen la disciplina de la plataforma. La primera es la explicitud: no hay acceso oculto: si un recurso no está declarado en el manifiesto, tu Worker no puede alcanzarlo, y por eso leer este fichero es leer, sin omisiones, el perímetro exacto de autoridad del código. La segunda es la indirección entre nombre y recurso, que parece un detalle y es una decisión de arquitectura: al separar el nombre lógico —env.DB— del puntero al recurso —database_id—, la config se vuelve la única pieza que cambia entre entornos, mientras el código permanece idéntico e ignorante de a qué base de datos concreta habla; eso es lo que hace que un mismo Worker corra en desarrollo, en pruebas y en producción sin recompilarse. La tercera es que el manifiesto sea código versionado: la infraestructura de tu Worker no es un estado que alguien configuró una tarde en un panel y nadie recuerda, sino un archivo cuya historia cuenta git, revisable en un pull request como cualquier otro cambio. Y la única excepción a esa regla —los secretos, que salen del manifiesto precisamente porque no deben versionarse— confirma el principio en lugar de romperlo: lo que puede ser público se declara y se versiona; lo que no, se aparta a un canal cifrado. Cuando entiendes que declarar un binding no es configurar una herramienta sino firmar una cláusula de acceso, empiezas a tratar este fichero con el cuidado que merece: cada línea que añades amplía, de forma explícita y auditable, lo que tu código tiene permitido hacer.
- Parte de un
wrangler.jsoncreal y añade un binding de KV y uno de D1; distingue en cada uno cuál es el nombre lógico y cuál el puntero al recurso. - Ejecuta
wrangler typesy comprueba que ambos bindings aparecen ya tipados enenv. - Añade una variable en
varsy un secreto conwrangler secret put; observa que solo la variable queda escrita en el manifiesto. - Cambia el
database_idde tu D1 sin tocar el código del Worker y razona por qué la indirección entre nombre y recurso hace esto posible.