{"slug":"tako-gen-and-the-generated-tako-gen-ts","url":"https://tako.sh/blog/tako-gen-and-the-generated-tako-gen-ts/","canonical":"https://tako.sh/blog/tako-gen-and-the-generated-tako-gen-ts/","title":"tako generate and the generated tako.d.ts","date":"2026-04-18T01:01","description":"Tako generates a project-local tako.d.ts that types the tako runtime object from tako.sh — no app global, no silent typos.","author":null,"image":"75030c2f757f","imageAlt":null,"headings":[{"depth":2,"slug":"what-the-generated-file-gives-you","text":"What the generated file gives you"},{"depth":2,"slug":"what-tako-generate-generates","text":"What tako generate 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. App code imports `tako` from `tako.sh`, and `tako generate` writes a project-local `tako.d.ts` file that teaches TypeScript your secret keys, environment names, channel metadata, and workflow metadata. No app global, no guessing — just ES modules.\n\n## What the generated file gives you\n\nEvery Tako JS/TS project has a `tako.d.ts` managed by the CLI. App code does not import it directly; TypeScript includes it and uses it to augment the public `tako.sh` package.\n\n```ts\nimport { tako } from \"tako.sh\";\n\ntako.secrets.DATABASE_URL; // typed string\ntako.env; // \"development\" | \"production\" | undefined\ntako.isDev; // boolean\ntako.port; // number, assigned by Tako\ntako.dataDir; // persistent path, survives deploys\ntako.build; // deploy-time build ID\ntako.logger.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 kebab↔camel rule to remember.\n\n## What `tako generate` generates\n\n[`tako generate`](/docs/cli/) scans your project and writes a single file:\n\n| Source                           | What `tako generate` emits                                                      |\n| -------------------------------- | ------------------------------------------------------------------------------- |\n| `.tako/secrets.json` (encrypted) | `interface TakoSecrets { readonly DATABASE_URL: string; ... }`                  |\n| `tako.toml` envs                 | <code>type Env = \"development\" \\| \"production\" \\| \"staging\"</code>              |\n| Channel files                    | `interface TakoChannels { ... }` metadata for discovered channel definitions    |\n| Workflow files                   | `interface TakoWorkflows { ... }` metadata for discovered workflow definitions  |\n| Runtime env                      | `process.env` / `import.meta.env` declarations for Tako-provided runtime values |\n\nSecret names are plaintext in [`.tako/secrets.json`](/blog/secrets-without-env-files/) — the values aren't — so `tako generate` emits the type surface without ever touching your encryption key. When you add a secret with `tako secrets set`, `tako.d.ts` picks it up on the next `tako dev`, `tako deploy`, or `tako generate`.\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`tako.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(tako.secrets)` returns `\"[REDACTED]\"`. `JSON.stringify(tako.secrets)` returns `\"[REDACTED]\"`. Log the whole object by accident and no values leak.\n- **Type-only generation.** The generated file has declarations, not runtime code. Runtime state comes from `tako.sh`.\n- **Plain ES modules.** Import `tako` from `tako.sh`. Rename-safe, jumps-to-definition, mocks cleanly in tests.\n\n## Try it\n\n`tako generate` 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. `tako gen` and `tako g` are aliases. When you want types updated manually:\n\n```bash\ntako secrets set STRIPE_KEY --env production\ntako generate\n# Generated tako.d.ts\n```\n\nFor Go apps, `tako generate` 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 app global, just `tako.sh` plus a generated declaration file — is what `tako generate` is for."}