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.
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.
- Ejecutar
wrangler typesy entender que genera la interfazEnvenworker-configuration.d.ts. - Leer la forma del tipo generado: el namespace
Cloudflarey la interfazEnv. - Tipar el handler con
Envy 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.
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.
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
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.
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.
- Ejecuta
wrangler typesen un proyecto con varios bindings y abreworker-configuration.d.ts; identifica el tipo que se generó para cada uno. - En tu handler, escribe mal el nombre de un binding —
env.DBB— y confirma que el compilador lo marca como error. - Añade un nuevo binding al manifiesto, compila sin regenerar los tipos y observa la deriva; luego ejecuta
wrangler typesy verifica que desaparece. - Añade
wrangler types --checka tu integración continua y razona, en una frase, cómo ese chequeo convierte la disciplina manual en una garantía automática.