windmill-labs/windmill · error · TypeError

Unknown portal target type: ${target === null ? 'null' : typ

Error message

Unknown portal target type: ${target === null ? 'null' : typeof target}. Allowed types: string (CSS selector) or HTMLElement.

What it means

Portal's target prop must be either a CSS selector string or an HTMLElement. update() throws a TypeError when target is anything else (null, undefined, number, object), because there is no valid DOM node to portal into.

Source

Thrown at frontend/src/lib/components/Portal.svelte:21

	export function portal(el, options) {
		let { target, name } = options
		let targetEl
		async function update(newTarget) {
			target = newTarget
			if (typeof target === 'string') {
				targetEl = document.querySelector(target)
				if (targetEl === null) {
					await tick()
					targetEl = document.querySelector(target)
				}
				if (targetEl === null) {
					throw new Error(`No element found matching css selector: "${target}"`)
				}
			} else if (target instanceof HTMLElement) {
				targetEl = target
			} else {
				throw new TypeError(
					`Unknown portal target type: ${
						target === null ? 'null' : typeof target
					}. Allowed types: string (CSS selector) or HTMLElement.`
				)
			}
			if (!el.classList.contains('windmill-app')) {
				el.classList.add('windmill-app')
			}
			if (name && !el.classList.contains(name)) {
				el.classList.add(name)
			}
			targetEl.appendChild(el)
			el.hidden = false
		}

		function destroy() {
			if (el.parentNode) {
				el.parentNode.removeChild(el)

View on GitHub (pinned to e474e8803c)

Solutions

  1. Only render <Portal> once the target variable is a string or HTMLElement (guard with {#if})
  2. Pass an HTMLElement via bind:this instead of a nullable lookup result
  3. Provide a default selector string, e.g. 'body'
  4. Fix the type of the value being passed — it must be string | HTMLElement

Example fix

// before
<Portal target={maybeEl}>...</Portal>
// after
{#if typeof maybeTarget === 'string' || maybeTarget instanceof HTMLElement}
  <Portal target={maybeTarget}>...</Portal>
{/if}
Defensive patterns

Strategy: type-guard

Validate before calling

const validTarget = typeof target === 'string' || target instanceof HTMLElement
if (!validTarget) target = 'body' // safe default

Type guard

function isPortalTarget(t: unknown): t is string | HTMLElement {
  return typeof t === 'string' || t instanceof HTMLElement
}

Try / catch

try {
  renderPortal(target)
} catch (e) {
  if (e instanceof TypeError && e.message.startsWith('Unknown portal target type')) {
    console.warn('Bad portal target, defaulting to body', target)
    renderPortal('body')
  } else throw e
}

Prevention

When it happens

Trigger: <Portal target={undefined}> because a variable is not yet assigned; target={null} from a failed lookup; passing a non-DOM object (e.g. a Svelte store value or config object) as target.

Common situations: Binding target to data that loads asynchronously so it is undefined at first render; refactoring from string selectors to elements (or vice versa) and leaving a wrong type; a parent component passing an optional prop straight through.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/8224b9dc1731fcd6. Report an issue: GitHub.