Opaque Type Aliases
Opaque type aliases are type aliases that hide their underlying type outside of the file in which they are defined.
1opaque type ID = string;Opaque types are Flow-only — TypeScript has no native equivalent. The common TS idiom is "branded types" (intersection with a private symbol-typed property), which is a userland pattern rather than a language feature. The boundary it enforces is weaker than Flow's file-scoped abstraction: TS brands are userland and forgeable with an as cast, and are structurally constructible when the brand key is exposed or string-keyed rather than a private unique symbol. See Flow's opaque types for the full comparison.
When to use this
Use opaque types over regular type aliases when you need to enforce abstraction boundaries across module boundaries — for example, preventing callers from treating an ID as a plain string. A regular type ID = string is transparent at every file boundary, so any consumer can treat an ID exactly like a string; opaque type ID = string keeps that transparency inside the defining file but seals the underlying type at the module boundary.
exports.js
export type TransparentID = string;
export opaque type OpaqueID = string;
export function makeOpaqueID(s: string): OpaqueID { return s; }
imports.js
1// The importer's view: `TransparentID` is still `string`; `OpaqueID` is nominal.2declare type TransparentID = string;3declare opaque type OpaqueID;4declare function makeOpaqueID(s: string): OpaqueID;5
6const a: TransparentID = "abc"; // Works — transparent alias is interchangeable with string7const b: string = a; // Works — round-trips with no boundary8
9const oid: OpaqueID = makeOpaqueID("abc"); // Works — the only way to obtain an OpaqueID10const c: OpaqueID = "abc"; // Error — outside the defining file, string is not an OpaqueIDincompatible-typeCannot assign "abc" to c because string [1] is incompatible with OpaqueID [2].11const d: string = oid; // Error — outside the defining file, OpaqueID is not a stringincompatible-typeCannot assign oid to d because OpaqueID [1] is incompatible with string [2].Reach for opaque type when callers must obtain a value only through a constructor function the defining module controls — for example IDs, sanitized strings, or units of measure. Keep type when the alias is documentation only and you're fine with consumers treating it as its underlying representation. Use the optional supertype constraint when consumers need partial access (e.g. reading an ID as a string but not creating one from a string).
Opaque Type Alias Syntax
Opaque type aliases are created using the words opaque type followed by its
name, an equals sign =, and a type definition.
opaque type Alias = Type;
You can optionally add a subtyping constraint to an opaque type alias by adding
a colon : and a type after the name.
opaque type Alias: SuperType = Type;
Any type can appear as the super type or type of an opaque type alias.
1opaque type StringAlias = string;2opaque type ObjectAlias = {3 property: string,4 method(): number,5};6opaque type UnionAlias = 1 | 2 | 3;7opaque type AliasAlias: ObjectAlias = ObjectAlias;8opaque type VeryOpaque: AliasAlias = ObjectAlias;Opaque Type Alias Type Checking
Within the Defining File
When in the same file the alias is defined, opaque type aliases behave exactly as regular type aliases do.
1opaque type NumberAlias = number;2
30 as NumberAlias;4
5function add(x: NumberAlias, y: NumberAlias): NumberAlias {6 return x + y;7}8function toNumberAlias(x: number): NumberAlias { return x; }9function toNumber(x: NumberAlias): number { return x; }Outside the Defining File
When importing an opaque type alias, it behaves like a nominal type, hiding its underlying type.
exports.js
export opaque type NumberAlias = number;
imports.js
1// In imports.js, NumberAlias is opaque — its underlying type (number) is hidden2declare opaque type NumberAlias;3
40 as NumberAlias; // Error: 0 is not a NumberAlias!incompatible-typeCannot cast 0 to NumberAlias because number [1] is incompatible with NumberAlias [2].5
6function convert(x: NumberAlias): number {7 return x; // Error: x is not a number!incompatible-typeCannot return x because NumberAlias [1] is incompatible with number [2].8}Subtyping Constraints
When you add a subtyping constraint to an opaque type alias, we allow the opaque type to be used as the super type when outside of the defining file.
exports.js
1export opaque type ID: string = string;imports.js
1// In imports.js, ID is opaque with a supertype constraint of string2declare opaque type ID: string;3
4function formatID(x: ID): string {5 return "ID: " + x; // OK! IDs are strings.6}7
8function toID(x: string): ID {9 return x; // Error: strings are not IDs.incompatible-typeCannot return x because string [1] is incompatible with ID [2].10}When you create an opaque type alias with a subtyping constraint, the type in the type position must be a subtype of the type in the super type position.
1opaque type Bad: string = number; // Error: number is not a subtype of stringincompatible-typenumber [1] is incompatible with string [2].2opaque type Good: {x: string, ...} = {x: string, y: number};Generics
Opaque type aliases can also have their own generics, and they work exactly as generics do in regular type aliases
1opaque type MyObject<A, B, C>: {foo: A, bar: B, ...} = {2 foo: A,3 bar: B,4 baz: C,5};6
7const val: MyObject<number, boolean, string> = {8 foo: 1,9 bar: true,10 baz: 'three',11};Library Definitions
You can also declare opaque type aliases in libdefs. There, you omit the underlying type, but may still optionally include a super type.
1declare opaque type Foo;2declare opaque type PositiveNumber: number;See Also
- Type Aliases — regular (transparent) type aliases
- Nominal & Structural Typing — opaque types are nominally typed, unlike regular type aliases
- Generics — opaque type aliases can be parameterized with generics
- Library Definitions — declaring opaque types in library definition files