sveltejs/svelte · warning · MigrationError

$$props is used together with named props in a way that cann

Error message

$$props is used together with named props in a way that cannot be automatically migrated.

What it means

Thrown when a component both consumes `$$props` (the legacy catch-all prop bag) and declares named props that have initializers or are mutated (`binding.updated`). The automigrator cannot decide how to merge a spread props object with explicit prop bindings, so it bails. The condition is `state.analysis.uses_props && (declarator.init || binding.updated)`.

Source

Thrown at packages/svelte/src/compiler/migrate/index.js:636

					// 	const value = path.expression;
					// 	if (binding.kind === 'bindable_prop' || binding.kind === 'rest_prop') {
					// 		state.props.push({
					// 			local: name,
					// 			exported: binding.prop_alias ? binding.prop_alias : name,
					// 			init: value
					// 		});
					// 		state.props_insertion_point = /** @type {number} */(declarator.end);
					// 	} else {
					// 		declarations.push(b.declarator(path.node, value));
					// 	}
					// }
				}

				const name = declarator.id.name;
				const binding = /** @type {Binding} */ (state.scope.get(name));

				if (state.analysis.uses_props && (declarator.init || binding.updated)) {
					throw new MigrationError(
						'$$props is used together with named props in a way that cannot be automatically migrated.'
					);
				}

				const prop = state.props.find((prop) => prop.exported === (binding.prop_alias || name));
				if (prop) {
					next();
					// $$Props type was used
					prop.init = declarator.init
						? state.str
								.snip(
									/** @type {number} */ (declarator.init.start),
									/** @type {number} */ (declarator.init.end)
								)
								.toString()
						: '';
					prop.bindable = binding.updated;
					prop.exported = binding.prop_alias || name;

View on GitHub (pinned to 20b341f100)

Solutions

  1. Stop reading `$$props` and switch to explicit prop declarations before migrating.
  2. Use `restProps` via `let { foo, ...rest } = $props()` after migration and spread `rest` instead of `$$props`.
  3. Migrate the file by hand: split the named props into `$props()` and forward the remainder via a rest binding.

Example fix

// before
<script>
  export let foo = 1;
  $: foo++;
</script>
<Child {...$$props} />

// after (manual)
<script>
  let { foo = $bindable(1), ...rest } = $props();
  foo++;
</script>
<Child {...rest} />
Defensive patterns

Strategy: validation

Validate before calling

// Detect the combined $$props + named/mutable prop pattern.
function usesPropsBagAndMutatedProps(source) {
  return /\$\$props/.test(source) && /export\s+let\s+\w+\s*=/.test(source);
}
if (usesPropsBagAndMutatedProps(componentSource)) {
  // decide between rest-props forwarding and explicit prop declarations
}

Try / catch

const { code } = migrate({ filename, source });
if (code.startsWith('<!-- @migration-task') && /\$\$props is used together with named props/i.test(code)) {
  // hand-migrate: `let { namedProp, ...rest } = $props()` and spread `rest`
}

Prevention

When it happens

Trigger: Running `migrate()` on a Svelte 4 component that combines `export let foo = ...` (or mutates a prop) with a read of `$$props` (e.g. forwarding unknown attributes: `<Child {...$$props} />`). The two patterns map to incompatible runes-mode translations.

Common situations: Attribute-forwarding components — a Svelte 4 idiom where `$$props` is spread onto a child while specific props are also declared and given defaults or reassigned. Wrapper/facade components around native elements frequently do this.

Related errors


AI-assisted analysis of sveltejs/svelte@20b341f100 (2026-08-12). Data as JSON: /api/errors/e6ba38d9ebf9f7fc. Report an issue: GitHub.