wandres.dev
BINDINGS · el modelo de acceso

Tipado con wrangler types

El comando wrangler types lee tus bindings y genera la interfaz Env en worker-configuration.d.ts, proyectando la infraestructura declarada en tipos de TypeScript. Cómo se lee el tipo generado, cómo tipar el handler y evitar la deriva entre config y código, y por qué las capacidades tipadas —no las credenciales— cierran el modelo de acceso del edge.

⏱ 15 min

Has declarado tus bindings en wrangler.jsonc y sabes que llegan a env como objetos vivos. Falta un último eslabón para cerrar el círculo: que TypeScript sepa exactamente qué contiene ese env. El comando wrangler types es quien tiende ese puente. Lee tu manifiesto y genera una interfaz Env que describe, con precisión, cada capacidad que tu Worker recibió. Tu infraestructura se convierte, literalmente, en tipos.

🎯 Al terminar esta lección sabrás
  • Ejecutar wrangler types y entender que genera la interfaz Env en worker-configuration.d.ts.
  • Leer la forma del tipo generado: el namespace Cloudflare y la interfaz Env.
  • Tipar el handler con Env y mantener los tipos sincronizados con el manifiesto.
  • Argumentar por qué las capacidades tipadas, y no las credenciales, cierran el modelo de acceso.

De wrangler.jsonc a la interfaz Env

El comando no adivina: lee tu wrangler.jsonc, recorre cada binding declarado y escribe un fichero de definiciones —worker-configuration.d.ts por defecto— con una interfaz Env cuyas propiedades son tus bindings, cada uno con el tipo que le corresponde. De paso, incluye los tipos del runtime de Workers según tu compatibility_date, de modo que APIs como Response o KVNamespace estén disponibles sin importar nada.

# lee wrangler.jsonc y (re)genera worker-configuration.d.ts
npx wrangler types

El resultado tiene una forma característica que conviene reconocer. Los bindings se declaran dentro de un namespace Cloudflare, y luego una interfaz Env global la extiende. Esa indirección permite que tanto tu código como las herramientas de la plataforma compartan la misma definición.

// worker-configuration.d.ts (generado, no editar a mano)
declare namespace Cloudflare {
  interface Env {
    MI_KV: KVNamespace;
    DB: D1Database;
    MI_BUCKET: R2Bucket;
    AI: Ai;
    MODO: string;
  }
}
interface Env extends Cloudflare.Env {}

Cada línea es la traducción de una sección del manifiesto: el binding de KV se vuelve un KVNamespace, el de D1 un D1Database, el de R2 un R2Bucket, el de AI un Ai, y la variable MODO una simple string. La correspondencia es exacta y automática: no escribes tú esta interfaz, la deriva la herramienta del manifiesto.

El tipo generado es, además, más fino de lo que sugiere el ejemplo. Un binding a un Durable Object no se tipa como un DurableObjectNamespace a secas, sino parametrizado con tu clase —DurableObjectNamespace de SalaDeChat—, de modo que el stub que obtienes conoce sus métodos. Un service binding hereda los tipos del Worker al que apunta, así que invocar su RPC autocompleta como si fuera código local. Toda esa riqueza sale del manifiesto sin que muevas un dedo.

ℹ️
De @cloudflare/workers-types a wrangler types

Si vienes de proyectos antiguos, quizá recuerdes instalar @cloudflare/workers-types y escribir la interfaz Env a mano. Ese enfoque quedó atrás. Hoy wrangler types genera tanto los tipos del runtime como el Env a partir de tu configuración, y los mantiene alineados con tu compatibility_date. Un manifiesto, un comando, una sola fuente de verdad para los tipos.

💡
Convierte la regeneración en un reflejo

Los proyectos generados por C3 traen un script cf-typegen que envuelve wrangler types. Ejecútalo cada vez que toques los bindings, y añádelo antes de cualquier tarea que dependa de TypeScript —el build, las pruebas, el chequeo de tipos—. Los tipos generados no son código que mantienes: son un artefacto derivado, tan desechable y regenerable como el resultado de una compilación.

Tipar el handler y evitar la deriva

Con la interfaz Env disponible, tipas tu Worker contra ella. El patrón canónico usa satisfies ExportedHandler<Env>, que ata la firma de tus handlers a tu entorno real y hace que env conozca cada binding por su nombre y su tipo.

export default {
  async fetch(request, env, ctx): Promise<Response> {
    // env.DB autocompleta y expone prepare; env.DBB no existe: error de compilacion
    const { results } = await env.DB.prepare('SELECT 1').all();
    return Response.json(results);
  },
} satisfies ExportedHandler<Env>;

Para que TypeScript encuentre el fichero generado, inclúyelo en el array types de tu tsconfig.json. A partir de ahí, escribir mal el nombre de un binding, o usar un método que ese recurso no ofrece, deja de ser un fallo silencioso en producción y pasa a ser un error rojo en tu editor.

{
  "compilerOptions": {
    "types": ["./worker-configuration.d.ts"]
  }
}

El riesgo que acecha a este esquema es la deriva: que cambies el manifiesto y olvides regenerar los tipos, dejando que el código crea en un env que ya no coincide con la realidad. Para blindarte, wrangler types ofrece un modo de verificación que falla si los tipos no reflejan el wrangler.jsonc actual. Colócalo en tu integración continua y ningún cambio de bindings pasará sin sus tipos al día.

# en CI: falla si worker-configuration.d.ts esta desactualizado
npx wrangler types --check
ℹ️
Todos los entornos, por defecto

Desde 2026, wrangler types agrega por defecto los bindings de todos los entornos definidos en tu manifiesto, no solo los del nivel superior. Así, un binding que solo existe en producción no provoca un error de tipos cuando lo referencias. Si prefieres restringirte a un entorno concreto, el flag --env recupera el comportamiento acotado.

Un matiz fino sobre las variables cierra el cuadro. Por defecto, wrangler types las tipa con su valor literal: si MODO vale produccion, su tipo no es un string cualquiera, sino esa cadena exacta, lo que convierte una comparación imposible en un error de compilación. Si tus variables cambian a menudo entre entornos y ese rigor estorba más de lo que ayuda, el flag --strict-vars false las relaja a string.

flowchart LR
CFG[wrangler jsonc con bindings] --> CMD[wrangler types]
CMD --> DTS[worker-configuration d ts con la interfaz Env]
DTS --> HANDLER[satisfies ExportedHandler de Env]
HANDLER --> SAFE[env tipado en tiempo de compilacion]
style CFG fill:#89b4fa,color:#11111b
style SAFE fill:#a6e3a1,color:#11111b

Por qué capacidades y no credenciales

Ahora puedes ver el modelo completo de este nivel en una sola imagen. Una credencial es un secreto que tu código posee y debe custodiar; una capacidad tipada es un objeto que la plataforma te entrega, que no puedes falsificar y que tu compilador conoce por su nombre. La diferencia no es de comodidad, sino de garantías: con capacidades tipadas no hay secreto que filtrar, no hay acceso que no esté declarado y no hay binding que puedas invocar sin que exista de verdad.

El tipo es la prueba de que capacidad venció a credencial

Recorre el arco entero de este nivel y verás que el tipado no es un adorno de última hora, sino la pieza que demuestra por qué el modelo de capacidades es superior al de credenciales. Hay tres momentos en la vida de un binding, y cada uno cierra una fuga que el modelo tradicional dejaba abierta. En la declaración, escribes en el manifiesto qué recursos necesitas: eso convierte el acceso en algo explícito y auditable, frente a la credencial que flotaba en el entorno sin que nadie hubiera declarado quién la usaba ni para qué. En la resolución, la plataforma inyecta en env un objeto vivo en lugar de un secreto: eso hace la referencia infalsificable y elimina la cosa misma que se podía filtrar, porque tu código nunca llega a tener la credencial. Y en la proyección —lo que hace wrangler types—, esa realidad se traduce a un tipo que tu compilador comprueba: eso lleva la seguridad al momento de escribir el código, donde nombrar un binding inexistente o llamar a un método que el recurso no ofrece deja de ser una bomba de relojería en producción y se vuelve un error que no compila. Junta las tres y entiende lo que has ganado. La interfaz Env es, a la vez, documentación y contrato y auditoría: leerla es leer con exactitud todo lo que el Worker puede tocar, ni más ni menos, porque no puedes teclear una capacidad que no declaraste sin que TypeScript te detenga, ni ejercerla sin que la plataforma te la haya concedido, ni filtrarla porque nunca fue un secreto en tus manos. Ese es, al final, el sentido de todo el nivel: en el edge no gestionas credenciales, ejerces capacidades; y cuando esas capacidades vienen tipadas, la seguridad deja de depender de tu disciplina para custodiar secretos y pasa a estar garantizada por la estructura misma del sistema —por la config que las declara, por la plataforma que las resuelve y por el compilador que las verifica—. Diseñar así es dejar de preguntar dónde guardo la llave y empezar a confiar en que solo tienes las puertas que pediste.

⚔️ Cierra el círculo del tipado
  1. Ejecuta wrangler types en un proyecto con varios bindings y abre worker-configuration.d.ts; identifica el tipo que se generó para cada uno.
  2. En tu handler, escribe mal el nombre de un binding —env.DBB— y confirma que el compilador lo marca como error.
  3. Añade un nuevo binding al manifiesto, compila sin regenerar los tipos y observa la deriva; luego ejecuta wrangler types y verifica que desaparece.
  4. Añade wrangler types --check a tu integración continua y razona, en una frase, cómo ese chequeo convierte la disciplina manual en una garantía automática.