skip to content

A Vue 3.5 build fails with "Unresolvable type reference or unsupported built-in utility type" on `defineProps<Props>()`; what causes it, and how do you fix it?

level: seniorimportance: should knowfreq 30%

answer

  1. the compiler reads syntax, not types
  2. a short list of utility types
  3. relative imports versus packages
  4. conditional types for one prop only
  5. an ignore comment for extends

basics

~20 s

The SFC compiler builds runtime props by reading the type's syntax, not by running TypeScript, so only interfaces, type literals and a few utility types resolve. Rewrite the props type into such a shape or use a runtime declaration.

solid answer

~50 s

To generate runtime props, the SFC compiler has to resolve `Props` into a list of keys at build time, and it does so by walking the type's AST, not by asking the TypeScript checker. Since 3.3 that covers local and imported interfaces and type literals, intersections and unions, and the utility types `Partial`, `Required`, `Readonly`, `Pick` and `Omit`. Anything needing real type evaluation fails with that error: another utility type at the root, a conditional type for the whole props object, or a type the resolver cannot find. Imports from relative files are read directly; package or alias imports need `typescript` installed as a peer dependency. Fixes: rewrite the root type as an interface or a supported utility, keep a complex type on a single prop, where it only loses its runtime check, or switch that component to a runtime declaration with `PropType`. `vue-tsc` may pass meanwhile, because it uses the full checker.

go deeper

for a junior

Recall that defineProps types must be simple enough for the compiler: interfaces, type literals and a few utility types.

for a middle

Explain that runtime props are generated from the type's syntax and list which utility types are supported.

for a senior

Diagnose build-only failures on props types, choose between flattening, per-prop types and runtime declarations, and know @vue-ignore's cost.

for a principal

Set rules for shared props types in design-system packages so consuming apps can always resolve them.

## Why the compiler needs to understand the type With type-based `defineProps<Props>()`, the SFC compiler must still produce a runtime `props` declaration, because Vue needs the list of prop keys at runtime. That happens **at build time, inside the SFC compiler**, and it works by walking the **syntax tree** of the type. It does not run the TypeScript type checker. The docs put it plainly: the type-to-runtime conversion is AST-based, so types that require actual type analysis are not supported. ## What the compiler can resolve (3.3+) - **Type literals and interfaces**, local or imported, including `extends` chains it can follow. - **Intersections and unions** of those. - The utility types **`Partial`, `Required`, `Readonly`, `Pick` and `Omit`**. - **Imported types**: from relative paths the compiler reads the file itself; from a package or a path alias it uses TypeScript's module resolution, which requires `typescript` installed as a peer dependency. Vue 3.2 and earlier could only use a type literal or a local interface; the 3.3 resolver is what made imported types work. ## What produces the error | Root props type | Result | |---|---| | `interface Props { ... }` or an imported one | Resolved | | `Omit<BaseProps, 'id'> & { size?: string }` | Resolved | | `NonNullable<Props>` or `Awaited<Props>` at the root | `Unresolvable type reference or unsupported built-in utility type` | | `T extends X ? A : B` for the whole props object | Not supported | | An import from a package without `typescript` installed | `Failed to resolve import source "..."` with a peer-dependency hint | | `interface Props extends Base` where `Base` cannot be resolved | `Failed to resolve extends base type`, with a `/* @vue-ignore */` hint | A conditional type **for one prop's value** is fine: the compiler cannot infer a runtime constructor for it, so that prop simply gets no runtime type check, but the key is still known. ## Fixing it 1. **Flatten the root type** into an interface or a supported utility: replace `NonNullable<Props>` with an interface that states the fields. 2. **Move complexity down a level**: a computed type on a single prop keeps the build working and loses only that prop's runtime check. 3. **Make imports resolvable**: prefer a relative import of the props type, or install `typescript` so package and alias imports resolve. 4. **Ignore an unresolvable base**: `interface Props extends /* @vue-ignore */ Base {}` tells the compiler to skip the base. The compiler's own message warns that the base's properties are then treated as **fallthrough attributes** at runtime, not props, so use it knowingly. 5. **Fall back to a runtime declaration** for that component: `defineProps({ options: { type: Array as PropType<Option[]>, required: true } })` needs no type resolution at all. ## Why the editor can be green while the build is red The editor and `vue-tsc` type-check with the **full TypeScript checker**, which evaluates conditional and utility types without trouble. The SFC compiler's resolver is a separate, deliberately limited piece that only needs to find **prop keys and constructors**. So a props type can be perfectly valid TypeScript, show no errors in the editor, and still fail the build. The fix is never to disable the build step: it is to give the compiler a shape it can read. ## A worked example ```ts // fails at build time: NonNullable is not a supported root utility type Props = NonNullable<SelectInputProps> // works: a supported utility over an interface type Props = Required<Pick<SelectInputProps, 'options'>> & Omit<SelectInputProps, 'options'> ``` ## A checklist for shared props types When props types live in a shared package or a common `types.ts`, a short checklist prevents build-only failures: - Export props as **interfaces or type literals**, not as the output of computed types. - Limit root-level composition to **intersections** and the five supported utilities. - Keep computed or conditional types on **individual props** only. - Make sure every consuming app has **`typescript` installed**, since package imports resolve through it. - Add one small component to the package's own test build that uses each exported props type with `defineProps`, so an unsupported shape fails there first. ## Pitfalls - Assuming "it type-checks" means "it compiles" for props types. - Sprinkling `@vue-ignore` on `extends` without noticing the base props became attributes. - Sharing props types from a design-system package in a project that does not have `typescript` installed.

  • What does `/* @vue-ignore */` before an extends clause do in a Vue props interface?
    It tells the SFC compiler to skip resolving that base type, so the build no longer fails. The cost, stated in the compiler's own message, is that the base type's properties are treated as fallthrough attributes at runtime rather than declared props, although TypeScript still types them.
  • Why can a Vue project's editor show no errors while the build fails on defineProps?
    The editor and `vue-tsc` use the full TypeScript checker, but the SFC compiler converts the props type to runtime props by reading its syntax. A valid type that needs real evaluation, such as a root conditional or an unsupported utility, passes type checking and still fails compilation.

saying these in an interview costs you the question

  • The SFC compiler runs the TypeScript checker to resolve props types.
  • Any type that passes vue-tsc will also compile in defineProps.
  • Conditional types can never appear anywhere in a props type.
  • @vue-ignore on extends keeps the base properties as real props.
  • Imported props types have never been supported in defineProps.