Hello there
My current technology stack: .NET 9, Python, TypeScript, and Azure.
I develop microservices and terraform of different sizes. Sharing my challenges and key learning.
About
The views expressed in this blog are my own and do not reflect my employer's. I am not responsible for any consequences of using the information provided. This blog is for educational purposes only, not for commercial use. Readers should apply their own judgment.
Pulumi TypeScript: Escaping the Input<Input<T>>[] | undefined Type Hell
Spoiler alert: Pulumi's type system is powerful... but when it goes rogue, it can ruin your deployment and your weekend.
When Infrastructure Becomes a Labyrinth
A few weeks ago, I was building a highly dynamic Azure Front Door setup using Pulumi and TypeScript. Nothing too fancy — just trying to create:
- Multiple custom domains
- Several endpoints
- Individual backend pools and origins per endpoint
- Custom route rules with caching policies
- Full-blown Web Application Firewall (WAF) integration
The goal? Take a simple configuration array and turn it into a full-fledged global load balancer with security baked in.
Everything was dynamic — the number of endpoints, their paths, their backend logic — and I was feeling good. Confident. Invincible.
Until I hit The Type Hell.
The Code That Broke My Sanity
Let’s say you have something like this:
const frontdoorConfigs = [
{ name: "web", enabled: true, host: "web.mysite.com", backend: "webapp" },
{ name: "api", enabled: false, host: "api.mysite.com", backend: "apigateway" },
];
const backends = frontdoorConfigs.map(cfg => cfg.enabled ? createBackend(cfg) : undefined);
frontdoorArgs.backends = backends;
Seems fine, right? But Pulumi throws this gem:
Type 'Input<Input<Backend>[]> | undefined' is not assignable to type 'Input<Input<Backend>[]>'.
Wait, what? Isn’t Input
Nope. This is Pulumi’s strict typing model screaming for help.
Understanding Pulumi's Input Type
Pulumi’s Input
What Pulumi expects:
Input<T[]>
What you often pass:
(Input<T> | undefined)[]
Or worse:
Input<Input<T>[]> | undefined
The type checker can’t resolve that cleanly — and neither can Pulumi at deployment time.
The Solution: Smart Filtering + Mapping
After lots of trial, error, and caffeine, I landed on a robust utility function that saved my setup:
export function mapFilterDefined<T, U>(
arr: Input<T[]>,
fn: (t: T) => U | undefined
): Input<U[]> {
return pulumi.output(arr).apply(list =>
list.map(fn).filter((x): x is U => x !== undefined)
);
}
Then I updated the logic to:
const backendInputs = mapFilterDefined(frontdoorConfigs, cfg => {
if (!cfg.enabled) return undefined;
return createBackend(cfg);
});
frontdoorArgs.backends = backendInputs;
Boom. Everything works. TypeScript stops yelling. Pulumi previews correctly. Deployments succeed.
Real Example: Dynamic Front Door with Pulumi
Here’s a simplified view of what I was doing:
const endpoints = mapFilterDefined(frontdoorConfigs, cfg => {
if (!cfg.enabled) return undefined;
return new azure.cdn.FrontdoorEndpoint(cfg.name, {
endpointName: cfg.name,
hostName: cfg.host,
...
});
});
const routes = mapFilterDefined(frontdoorConfigs, cfg => {
if (!cfg.enabled) return undefined;
return new azure.cdn.Route(`${cfg.name}-route`, {
...route logic...
});
});
// etc for policies, origins, etc
Each step filtered out disabled configs and ensured only clean, fully-resolved inputs were passed to Pulumi resources.
Key Lessons Learned
- Don’t let undefined sneak into your Pulumi arrays.
- Be wary of Input<Input
> — use pulumi.output().apply() to flatten and resolve values. - Build utility functions to avoid repeating logic and clean up complex flows.
- Always check the actual expected type in the Pulumi docs or source code — TS errors can be misleading.
Closing Thoughts
This isn’t just a TypeScript problem — it’s a Pulumi lifecycle problem. When you defer evaluation until deployment, your types need to be perfectly predictable.
In the end, I deployed a secure, scalable, and elegant Azure Front Door setup — but only after learning how to wrangle types as fiercely as infra.
If you're hitting a wall with Pulumi's types, remember: it’s not just you. Type Hell is real — but it’s also conquerable.
🧠 Happy infra building, and may your Input