Describe the bug
.as('hidden', booleanValue) (added in #15802, shipped 2.60.0) renders fine, but the receive path was missed. convert_formdata only knows the 'on' literal that checkboxes emit, so <input value="true"> lands as false server-side. The schema validates either way, so it's silent — handler runs with the wrong data.
true always corrupts to false. false coincidentally lands as false. Number .as('hidden', n) is unaffected (parseFloat handles it).
Reproduction
No app needed — the bug is one function call:
import { convert_formdata } from '@sveltejs/kit/src/runtime/form-utils.js';
const fd = new FormData();
fd.append('b:flag', 'true'); // exactly what `.as('hidden', true)` puts in the DOM
console.log(convert_formdata(fd));
// → { flag: false } (expected: { flag: true })
End-to-end repro (bun create svelte, paste these two files, submit the form, watch the server log say false):
// src/routes/repro.remote.ts
import { form } from '$app/server';
import * as v from 'valibot';
export const submit = form(
v.object({ flag: v.boolean() }),
async (data) => { console.log('server:', data); return data; }
);
<!-- src/routes/+page.svelte -->
<script>
import { submit } from './repro.remote';
let flag = $state(true);
let result = $state();
</script>
<form {...submit.enhance(async ({ submit }) => { result = await submit(); })}>
<input {...submit.fields.flag.as('hidden', flag)} />
<button type="submit">submit (flag={flag})</button>
</form>
<pre>{JSON.stringify(result)}</pre>
Root cause
packages/kit/src/runtime/form-utils.js:
// emit path (added by #15802) — picks 'b:' prefix for boolean hidden inputs
function get_type_prefix(field_type, is_array, input_value) {
if (field_type === 'hidden' || field_type === 'submit') {
if (typeof input_value === 'boolean') return 'b:'; // → <input name="b:flag" value="true">
}
// ...
}
// receive path in convert_formdata — still assumes the only writer of `b:` is <input type="checkbox">
} else if (key.startsWith('b:')) {
values = values.map((v) => v === 'on'); // 'true' === 'on' → false
}
#15802 added a second writer for b: keys but didn't teach the receiver about it. The PR's added test (as-value/form.remote.ts) only checks that v.boolean() accepts the data — and it does, because false is a valid boolean — so the regression slipped through.
Suggested fix
values = values.map((v) => v === 'on' || v === 'true');
…plus a roundtrip test that asserts true stays true.
Workaround
Stay on .as('checkbox') + hidden attr for boolean fields — 'on' round-trips correctly. Schema needs v.optional(v.boolean(), false) because unchecked checkboxes are omitted from FormData.
System Info
System:
OS: Linux 6.17 Ubuntu 24.04.4 LTS
CPU: (4) x64 AMD EPYC-Genoa Processor
Binaries:
Node: 24.14.0
npm: 11.9.0
bun: 1.3.14
npmPackages:
@sveltejs/kit: 2.60.1 => 2.60.1
@sveltejs/vite-plugin-svelte: ^7.1.2 => 7.1.2
svelte: ^5.55.7 => 5.55.7
vite: 8.0.13 => 8.0.13
Severity
serious, can work around it
Describe the bug
.as('hidden', booleanValue)(added in #15802, shipped 2.60.0) renders fine, but the receive path was missed.convert_formdataonly knows the'on'literal that checkboxes emit, so<input value="true">lands asfalseserver-side. The schema validates either way, so it's silent — handler runs with the wrong data.truealways corrupts tofalse.falsecoincidentally lands asfalse. Number.as('hidden', n)is unaffected (parseFloathandles it).Reproduction
No app needed — the bug is one function call:
End-to-end repro (
bun create svelte, paste these two files, submit the form, watch the server log sayfalse):Root cause
packages/kit/src/runtime/form-utils.js:#15802 added a second writer for
b:keys but didn't teach the receiver about it. The PR's added test (as-value/form.remote.ts) only checks thatv.boolean()accepts the data — and it does, becausefalseis a valid boolean — so the regression slipped through.Suggested fix
…plus a roundtrip test that asserts
truestaystrue.Workaround
Stay on
.as('checkbox')+hiddenattr for boolean fields —'on'round-trips correctly. Schema needsv.optional(v.boolean(), false)because unchecked checkboxes are omitted from FormData.System Info
Severity
serious, can work around it