|
| 1 | +# Zod v4 `stringbool()` Support |
| 2 | + |
| 3 | +Superforms now supports Zod v4's `z.stringbool()` type, which allows strict validation of boolean string values. |
| 4 | + |
| 5 | +## What is `z.stringbool()`? |
| 6 | + |
| 7 | +`z.stringbool()` is a Zod v4 feature that validates string inputs and converts them to booleans, but only accepts specific truthy/falsy string values. Unlike `z.boolean()` or `z.coerce.boolean()`, which accept any non-"false" value as truthy, `stringbool()` enforces strict validation. |
| 8 | + |
| 9 | +## Implementation Details |
| 10 | + |
| 11 | +Internally, `z.stringbool()` is implemented as a pipe: `string -> transform -> boolean` |
| 12 | + |
| 13 | +Superforms detects this pattern in the JSON Schema generation phase and: |
| 14 | + |
| 15 | +1. Marks the field with `format: "stringbool"` in the JSON Schema |
| 16 | +2. Keeps the value as a string during FormData parsing |
| 17 | +3. Lets Zod perform the validation and transformation |
| 18 | + |
| 19 | +## Usage Examples |
| 20 | + |
| 21 | +### Basic Example |
| 22 | + |
| 23 | +```typescript |
| 24 | +import { z } from 'zod/v4'; |
| 25 | +import { superValidate } from 'sveltekit-superforms'; |
| 26 | +import { zod } from 'sveltekit-superforms/adapters'; |
| 27 | + |
| 28 | +const schema = z.object({ |
| 29 | + acceptTerms: z.stringbool({ |
| 30 | + truthy: ['true'], |
| 31 | + falsy: ['false'], |
| 32 | + case: 'sensitive' |
| 33 | + }) |
| 34 | +}); |
| 35 | + |
| 36 | +// Server-side (+page.server.ts) |
| 37 | +export const actions = { |
| 38 | + default: async ({ request }) => { |
| 39 | + const form = await superValidate(request, zod(schema)); |
| 40 | + |
| 41 | + if (!form.valid) { |
| 42 | + return fail(400, { form }); |
| 43 | + } |
| 44 | + |
| 45 | + // form.data.acceptTerms is now a boolean (true or false) |
| 46 | + console.log(form.data.acceptTerms); // boolean |
| 47 | + |
| 48 | + return { form }; |
| 49 | + } |
| 50 | +}; |
| 51 | +``` |
| 52 | + |
| 53 | +### Client-side Form |
| 54 | + |
| 55 | +```svelte |
| 56 | +<script lang="ts"> |
| 57 | + import { superForm } from 'sveltekit-superforms'; |
| 58 | +
|
| 59 | + export let data; |
| 60 | +
|
| 61 | + const { form, errors, enhance } = superForm(data.form); |
| 62 | +</script> |
| 63 | +
|
| 64 | +<form method="POST" use:enhance> |
| 65 | + <input type="hidden" name="acceptTerms" value="true" /> |
| 66 | + <button type="submit">Accept Terms</button> |
| 67 | +</form> |
| 68 | +``` |
| 69 | + |
| 70 | +### Custom Truthy/Falsy Values |
| 71 | + |
| 72 | +You can customize which string values are considered truthy or falsy: |
| 73 | + |
| 74 | +```typescript |
| 75 | +const schema = z.object({ |
| 76 | + status: z.stringbool({ |
| 77 | + truthy: ['yes', 'on', '1'], |
| 78 | + falsy: ['no', 'off', '0'], |
| 79 | + case: 'insensitive' // Case-insensitive matching |
| 80 | + }) |
| 81 | +}); |
| 82 | +``` |
| 83 | + |
| 84 | +With this schema: |
| 85 | + |
| 86 | +- `"yes"`, `"YES"`, `"on"`, `"ON"`, `"1"` → `true` |
| 87 | +- `"no"`, `"NO"`, `"off"`, `"OFF"`, `"0"` → `false` |
| 88 | +- Any other value → Validation error |
| 89 | + |
| 90 | +## Benefits Over Regular Boolean |
| 91 | + |
| 92 | +| Feature | `z.boolean()` / `z.coerce.boolean()` | `z.stringbool()` | |
| 93 | +| --------------- | ------------------------------------------ | ------------------------------------------------ | |
| 94 | +| Validation | Accepts any value as truthy except "false" | Only accepts specified truthy/falsy values | |
| 95 | +| Type Safety | Less strict | More strict | |
| 96 | +| Error Detection | May miss invalid inputs | Catches invalid inputs | |
| 97 | +| Use Case | General boolean fields | Security-critical or strict validation scenarios | |
| 98 | + |
| 99 | +## Common Use Cases |
| 100 | + |
| 101 | +### Toggle Role Buttons |
| 102 | + |
| 103 | +```typescript |
| 104 | +const schema = z.object({ |
| 105 | + role: z.string(), |
| 106 | + hadRole: z.stringbool({ |
| 107 | + truthy: ['true'], |
| 108 | + falsy: ['false'], |
| 109 | + case: 'sensitive' |
| 110 | + }) |
| 111 | +}); |
| 112 | +``` |
| 113 | + |
| 114 | +Hidden form fields that track whether a user had a role when the page loaded, preventing unexpected toggles. |
| 115 | + |
| 116 | +### Checkbox-like Hidden Inputs |
| 117 | + |
| 118 | +```typescript |
| 119 | +const schema = z.object({ |
| 120 | + consent: z.stringbool({ |
| 121 | + truthy: ['true'], |
| 122 | + falsy: ['false'] |
| 123 | + }) |
| 124 | +}); |
| 125 | +``` |
| 126 | + |
| 127 | +For forms that look like buttons to the user but need to track boolean state. |
| 128 | + |
| 129 | +### Feature Flags |
| 130 | + |
| 131 | +```typescript |
| 132 | +const schema = z.object({ |
| 133 | + enabled: z.stringbool({ |
| 134 | + truthy: ['enabled', 'on'], |
| 135 | + falsy: ['disabled', 'off'], |
| 136 | + case: 'insensitive' |
| 137 | + }) |
| 138 | +}); |
| 139 | +``` |
| 140 | + |
| 141 | +## Testing |
| 142 | + |
| 143 | +Run the stringbool tests: |
| 144 | + |
| 145 | +```bash |
| 146 | +npm test -- stringbool |
| 147 | +``` |
| 148 | + |
| 149 | +## Related Issues |
| 150 | + |
| 151 | +- [Issue #610](https://github.com/ciscoheat/sveltekit-superforms/issues/610) - Original feature request |
| 152 | +- [Zod Issue #4821](https://github.com/colinhacks/zod/issues/4821) - Discussion about detecting stringbool in JSON Schema |
| 153 | + |
| 154 | +## Credits |
| 155 | + |
| 156 | +Thanks to [@fr33bits](https://github.com/fr33bits) for reporting the issue and [@colinhacks](https://github.com/colinhacks) for clarifying the stringbool implementation. |
0 commit comments