Skip to main content
Infrastructure configuration is rarely uniform across environments. A production database needs more storage, higher availability, and longer backup retention than a dev database — but it’s the same resource type, the same schema, the same stack. Duplicating entire stacks per environment is the wrong answer; parameterising every value as an input is tedious and leaks environment logic into the call site. Conditional expressions let you express environment-specific logic directly inside your .iac files, close to the resources they affect. ubx supports the null coalescing operator ??, if/else expressions that return a value, if without an else (returning null when false), and nested if/else chains for multi-branch logic. All of these are Resolved<T> — they evaluate at compile time from inputs and locals, producing plain values that get inlined into the generated code.

What you’ll learn

  • The ?? null coalescing operator — use an override if set, otherwise a default
  • if/else expressions that return a value based on a condition
  • if without an else — evaluates to null when the condition is false
  • Nested if/else for multi-branch logic (ubx does not support else if)

Why this matters

All conditional expressions in ubx are Resolved<T> — they evaluate at compile time from inputs and locals. This means the generated Pulumi code contains plain values, not runtime conditionals. The environment logic lives in your .iac source, not scattered across separate stack files or wrapper scripts.

The source code

unit "aws_rds_instance" "db" {
  allocated_storage       = local.allocated_storage
  engine                  = "postgres"
  engine_version          = "15"
  instance_class          = local.instance_class
  username                = "admin"
  password                = "placeholder"
  skip_final_snapshot     = true
  backup_retention_period = local.backup_retention
  multi_az                = if input.env == "prod" { true } else { false }
}

How it works

1

Expressions are evaluated in the IR pass

During AST → IRProgram, all conditional expressions are evaluated against the resolved input values. The result is a plain IRValue — a string, number, or bool. No conditional logic appears in the generated code.
2

?? short-circuits on null

input.override_class ?? "db.t3.micro" evaluates the left side first. If the value is non-null, it’s used. If it’s null (the default), the right side is used. This is the cleanest pattern for optional overrides.
3

if/else returns a value

Unlike many languages where if is a statement, ubx’s if/else is an expression — it always produces a value. Both branches must return the same type. The result is inlined directly into the attribute that uses it.
4

Nested if/else handles multiple branches

ubx does not have else if as a keyword. Multi-branch logic uses a nested if inside the else branch. The compiler flattens this into a simple ternary chain in the generated code.

What ubx generates

// Generated by ubx — do not edit
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const env = config.get("env") || "dev";
const overrideClass = config.get("overrideClass") ?? null;

const instanceClass = overrideClass ?? "db.t3.micro";
const allocatedStorage = env === "prod" ? 100 : 20;
const backupRetention = env === "prod" ? 30 : env === "staging" ? 7 : 1;

const db = new aws.rds.Instance("db", {
    allocatedStorage: allocatedStorage,
    engine: "postgres",
    engineVersion: "15",
    instanceClass: instanceClass,
    username: "admin",
    password: "placeholder",
    skipFinalSnapshot: true,
    backupRetentionPeriod: backupRetention,
    multiAz: env === "prod",
});

export const instanceClassUsed = instanceClass;

Common mistakes

ubx does not support else if as a keyword. Writing if cond1 { } else if cond2 { } is a parse error. Use a nested if inside the else branch instead: if cond1 { } else { if cond2 { } else { } }.
if without an else evaluates to null when the condition is false. This is useful for optional attributes — set them only when a condition is met, and the attribute is omitted entirely when null. Make sure the resource attribute accepts null before using this pattern.

Run it

ubx validate
ubx plan                           # dev defaults
ubx plan --var env=prod            # prod values
ubx plan --var override_class=db.r6g.large  # override instance class

What you learned

?? returns the left side if non-null, otherwise the right side — clean pattern for optional overrides
if/else is an expression in ubx — it returns a value and can be used anywhere a value is expected
if without else returns null when the condition is false
Multi-branch logic uses nested if/else, not else if

Next steps

for_each over a list

Create multiple resources from a list

Language reference: conditionals

Full conditional expression syntax