sveltejs/kit · error

Invalid array key ${keys[i + 1]}

Error message

Invalid array key ${keys[i + 1]}

What it means

deep_set walks a path like a[0].b to place a value; when it transitions into an array index it checks that the existing container is actually an array (and vice versa). If the path says the next segment is numeric but the current value is an object (or the reverse), the structure is inconsistent and it throws 'Invalid array key'.

Source

Thrown at packages/kit/src/runtime/form-utils.js:521

 * Sets a value in a nested object using an array of keys, mutating the original object.
 * @param {Record<string, any>} object
 * @param {string[]} keys
 * @param {any} value
 */
export function deep_set(object, keys, value) {
	let current = object;

	for (let i = 0; i < keys.length - 1; i += 1) {
		const key = keys[i];

		check_prototype_pollution(key);

		const is_array = /^\d+$/.test(keys[i + 1]);
		const inner = Object.hasOwn(current, key) ? current[key] : undefined;
		const exists = inner != null;

		if (exists && is_array !== Array.isArray(inner)) {
			throw new Error(`Invalid array key ${keys[i + 1]}`);
		}

		if (!exists) {
			if (value === DELETE_KEY) {
				// don't create the nested structure if we want to delete the key anyway
				return;
			}
			current[key] = is_array ? [] : {};
		}

		current = current[key];
	}

	const final_key = keys[keys.length - 1];
	check_prototype_pollution(final_key);

	if (value === DELETE_KEY) {
		delete current[final_key];

View on GitHub (pinned to 03f1687fe6)

Solutions

  1. Make the path consistent: use the same container type for a given key across all fields (always array indices or always object keys).
  2. Declare the field as an array if multiple indexed values are submitted, so the container is created as an array.
  3. Fix the specific conflicting segment reported (keys[i + 1]) — either rename the field or switch to a numeric index consistently.

Example fix

// before
fields.as('items');
fields.as('items[0]'); // conflicts: 'items' as scalar and array
// after
fields.as('items', { array: true });
fields.as('items[0]');
Defensive patterns

Strategy: validation

Validate before calling

const containers = {};
for (const name of fieldNames) {
  const m = name.match(/^([^.\[]+)/);
  if (m) containers[m[1]] = (containers[m[1]] ?? 0) | (/^\w+\[\d+\]/.test(name) ? 1 : 2);
  if (containers[m[1]] === 3) throw new Error(`key '${m[1]}' used as both array and object`);
}

Prevention

When it happens

Trigger: Mixing bracket-numeric and dot notation against the same base key, e.g. one submission posts items[0] and another posts items.0-shaped fields with different container types; two fields whose paths collide, one treating 'items' as an array and the other as an object.

Common situations: Dynamically generated field names where the same prefix is sometimes indexed and sometimes not; form schema changes between requests (array field converted to scalar) with stale client markup; hand-crafted posts mixing notations.

Related errors


AI-assisted analysis of sveltejs/kit@03f1687fe6 (2026-09-02). Data as JSON: /api/errors/b408529036e7d384. Report an issue: GitHub.