{"slug":"typegen-and-the-ambient-tako-global","url":"https://tako.sh/blog/typegen-and-the-ambient-tako-global/","canonical":"https://tako.sh/blog/typegen-and-the-ambient-tako-global/","title":"tako typegen and the generated tako.gen.ts","date":"2026-04-18T01:01","description":"Tako generates a project-local tako.gen.ts with typed runtime state and a typed secrets bag — zero ambient globals, zero module augmentation, zero silent typos.","author":null,"image":"402b4ee3a4e4","imageAlt":null,"headings":[{"depth":2,"slug":"what-the-generated-file-gives-you","text":"What the generated file gives you"},{"depth":2,"slug":"what-tako-typegen-generates","text":"What tako typegen generates"},{"depth":2,"slug":"why-this-is-safer-than-processenv","text":"Why this is safer than process.env"},{"depth":2,"slug":"try-it","text":"Try it"}],"markdown":"Most runtime config is reached through APIs that lie to you. `process.env` pretends every variable is a string and returns `undefined` when you typo a name. `process.env.DATBASE_URL` is a syntactically valid read that fails silently, then explodes somewhere downstream — usually at 2am, usually in production.\n\nTako's JavaScript SDK ships a different shape. `tako typegen` writes a `tako.gen.ts` file into your project with typed exports for every secret, runtime value, and log handle you'll reach for. No ambient globals, no module augmentation, no guessing — just ES modules.\n\n## What the generated file gives you\n\nEvery Tako JS/TS project has a `tako.gen.ts` managed by the CLI. It's a real `.ts` file, not a `.d.ts` ambient declaration — you can open it, read it, and see exactly what's exported.\n\n```ts\n// Anywhere in your app\nimport { env, isDev, port, dataDir, build, logger, secrets } from \"../tako.gen\";\n\nsecrets.DATABASE_URL; // typed string\nenv; // \"development\" | \"production\" | undefined\nisDev; // boolean\nport; // number, assigned by Tako\ndataDir; // persistent path, survives deploys\nbuild; // deploy-time build ID\nlogger.info(\"hello\", { userId });\n```\n\nChannels and workflows aren't on the runtime context — they're regular modules you import from their own files:\n\n```ts\nimport sendEmail from \"../workflows/send-email\";\nimport chat from \"../channels/chat\";\n\nawait sendEmail.enqueue({ to });\nawait chat({ roomId }).publish({ type: \"msg\", data: { text, userId } });\n```\n\nSame shape on Bun and Node. No global install step, no runtime Proxy, no kebab↔camel rule to remember.\n\n## What `tako typegen` generates\n\n[`tako typegen`](/docs/cli) scans your project and writes a single file:\n\n| Source                           | What typegen emits                                                             |\n| -------------------------------- | ------------------------------------------------------------------------------ |\n| `.tako/secrets.json` (encrypted) | `interface Secrets { readonly DATABASE_URL: string; ... }` + `secrets` export  |\n| Runtime env                      | `env`, `isDev`, `isProd`, `port`, `host`, `build`, `dataDir`, `appDir` exports |\n| App infra                        | `logger` export (structured JSON logger bound to `source: \"app\"`)              |\n\nSecret names are plaintext in [`.tako/secrets.json`](/blog/secrets-without-env-files) — the values aren't — so typegen emits the type surface without ever touching your encryption key. When you add a secret with `tako secrets set`, typegen picks it up and rewrites `tako.gen.ts` on the next `tako dev`, `tako deploy`, or `tako typegen`.\n\nThe file lands somewhere TypeScript's default `include` will find: next to an existing copy if you have one, or inside `src/` or `app/` if those directories exist, or at the project root. No `tsconfig.json` edits needed.\n\n## Why this is safer than `process.env`\n\n`process.env` is fundamentally a `string → string` map. `process.env.DATBASE_URL` is a valid read; it just returns `undefined`. Your editor can't warn you because the shape of `process.env` isn't tied to your actual secrets.\n\n`secrets.DATBASE_URL` is a compile error. `import foo from \"../workflows/bar\"` where `bar.ts` doesn't exist is a compile error. If TypeScript sees your file, it'll catch these before they ever run.\n\nA few more guarantees:\n\n- **Redaction by default.** `String(secrets)` returns `\"[REDACTED]\"`. `JSON.stringify(secrets)` returns `\"[REDACTED]\"`. Log the whole object by accident and no values leak.\n- **Server-only.** The generated file is evaluated on the Node/Bun process that runs your entrypoint; the browser has its own universe. Browser code pulls from `tako.sh/client` or `tako.sh/react` instead.\n- **Plain ES modules.** No `declare global`, no Proxy, no module augmentation. Rename-safe, tree-shakeable, jumps-to-definition, mocks cleanly in tests.\n\n## Try it\n\n`tako typegen` runs automatically during [`tako init`](/docs/cli), [`tako dev`](/docs/development), [`tako deploy`](/docs/deployment), and `tako secrets ...`. Most of the time you don't think about it. When you want types updated manually:\n\n```bash\ntako secrets set STRIPE_KEY --env production\ntako typegen\n# Generated tako.gen.ts\n```\n\nFor Go apps, typegen emits a `tako_secrets.go` with a typed `Secrets` struct — same idea, same compile-time catch. See [the Go SDK post](/blog/the-go-sdk-is-here) for the shape of that side.\n\nTyped runtime config isn't a new idea. Getting it for secrets and runtime state with zero ceremony — no ambient globals, no module augmentation, just a `.ts` file you import — is what `tako typegen` is for."}