Skip to content

Unions

Unions define a type that must be exactly one of several possible variants. There are two types of unions:

  • Union expressions
  • Named unions

Unnamed unions, or union expressions, can be declared by combining the variants using the | operator.

alias Breed = Beagle | GermanShepherd | GoldenRetriever;

In this example, Breed can be either a Beagle, a GermanShepherd, or a GoldenRetriever.

Named unions allow you to assign a name to the union and provide explicit variant references. Named unions are somewhat similar to enums, but instead of having string or numeric values, they use record models.

A named union can be declared with the union keyword. Its name must be an identifier.

union Breed {
beagle: Beagle,
shepherd: GermanShepherd,
retriever: GoldenRetriever,
}

The above example is equivalent to the Breed alias mentioned earlier, with the difference that emitters can recognize Breed as a named entity and also identify the beagle, shepherd, and retriever names for the options. This format also allows the application of decorators to each of the options.

A named union can declare a base type with the extends keyword. Every variant of the union must be assignable to that base type, otherwise a diagnostic is reported on the offending variant.

model Dog {
name: string;
}
model Beagle extends Dog {
huntingSkill: string;
}
model GermanShepherd extends Dog {
guardingSkill: string;
}
union Breed extends Dog {
beagle: Beagle,
shepherd: GermanShepherd,
}

This serves two purposes:

  • It prevents a common class of mistake where an unrelated type is accidentally added to a union.
  • It records the common base type in the type graph, which makes it easy for emitters to represent the union with a polymorphic base type in languages that don’t support unions natively.

The base type does not become a variant of the union. Breed above still has exactly two variants.

extends is a constraint, not a declaration of inheritance. A variant only needs to be assignable to the base type, it doesn’t have to explicitly extend it:

model Dog {
name: string;
}
model Beagle {
name: string;
huntingSkill: string;
}
// Ok: `Beagle` is assignable to `Dog` even though it doesn't explicitly extend it.
union Breed extends Dog {
beagle: Beagle,
}

The base expression must resolve to a model, scalar, enum, or union. This includes union, intersection, array, and template expressions that resolve to one of those data types. Anonymous model expressions cannot be used directly or through an alias.

union OperationStatus extends string {
"Running",
"Succeeded",
"Failed",
}

extends also has no interaction with the @discriminator decorator.

Declaration expressions are an experimental TypeSpec feature and are disabled by default. Using a model, enum, union, or scalar declaration in expression position is an error unless you enable them by adding declaration-expressions to the features list in your tspconfig.yaml:

kind: project
features:
- declaration-expressions

The union keyword can also be used anywhere a type expression is expected — for example as an alias value, a property type, a decorator or template argument, or a tuple element. Unlike a union expression built with the | operator, the keyword form can carry a name and named variants.

model Pet {
// anonymous keyword union in expression position
breed: union {
Beagle,
GermanShepherd,
};
// named keyword union in expression position
size: union Size {
small: "S",
medium: "M",
large: "L",
};
}

A keyword union used in expression position is marked as an expression and is not registered in the enclosing namespace, even when it is given a name. The name is kept on the resulting type for display purposes only — it cannot be referenced elsewhere.

Unlike the | operator, a keyword union used as an operand is not flattened into the surrounding union. For example, union { "a", "b" } | "c" produces a union of the nested union { "a", "b" } and "c", preserving the named variants.

A union in expression position can also constrain its variants with extends:

model Pet {
breed: union extends Dog {
Beagle,
GermanShepherd,
};
}

You can apply decorators and doc comments to the declaration inline, and augment it through a navigation reference such as ::type.