Which project does this relate to?
Start
Describe the bug
When using z.discriminatedUnion with z.looseObject() (the Zod v4 recommended replacement for z.object().passthrough()) as the inputValidator for createServerFn, TypeScript reports type errors because the inferred type includes [x: string]: unknown, which is not assignable to what createServerFn internally expects ([x: string]: {}).
Minimal Reproduction
import { z } from 'zod';
import { createServerFn } from '@tanstack/react-start';
const storageSettingsSchema = z.discriminatedUnion('provider', [
z.looseObject({ provider: z.literal('local') }),
z.looseObject({
provider: z.literal('s3'),
s3: z.object({
endpoint: z.string().min(1),
bucket: z.string().min(1),
}),
}),
]);
type StorageSettings = z.infer<typeof storageSettingsSchema>;
// Inferred type includes: { [x: string]: unknown; provider: "local" } | ...
// ❌ TS2345: Type 'Promise<{ [x: string]: unknown; ... }>'
// is not assignable to type 'Promise<{ [x: string]: {}; ... }>'
export const getSettings = createServerFn({ method: 'GET' })
.handler(async (): Promise<StorageSettings> => {
return { provider: 'local' };
});
// ❌ Same error on inputValidator + handler return
export const updateSettings = createServerFn({ method: 'POST' })
.inputValidator(storageSettingsSchema)
.handler(async ({ data }): Promise<StorageSettings> => {
return data;
});
Context
In Zod v4, z.looseObject() is the recommended way to allow extra keys to pass through (replacing the legacy .passthrough() method). It infers [x: string]: unknown by design. This is semantically correct since extra keys could hold any value including null or undefined.
The root cause appears to be a restrictive type constraint inside createServerFn's generics that narrows unknown → {}, which excludes null | undefined from the index signature's value type.
Your Example Website or App
https://stackblitz.com/edit/github-geldcamw?file=src%2Froutes%2Findex.tsx
Expected behavior
createServerFn should accept return types / input types with [x: string]: unknown index signatures, since this is what Zod v4's z.looseObject() infers.
Actual Behavior
TypeScript reports errors because somewhere in createServerFn's internal type chain, the index signature is constrained to [x: string]: {}, and unknown is not assignable to {}.
Screenshots or Videos
No response
Platform
@tanstack/react-start: 1.159.5
@tanstack/react-router: 1.159.5
zod: 4.3.6
typescript: 5.9.3
Additional context
No response
Which project does this relate to?
Start
Describe the bug
When using
z.discriminatedUnionwithz.looseObject()(the Zod v4 recommended replacement forz.object().passthrough()) as theinputValidatorforcreateServerFn, TypeScript reports type errors because the inferred type includes[x: string]: unknown, which is not assignable to whatcreateServerFninternally expects ([x: string]: {}).Minimal Reproduction
Context
In Zod v4,
z.looseObject()is the recommended way to allow extra keys to pass through (replacing the legacy.passthrough()method). It infers[x: string]: unknownby design. This is semantically correct since extra keys could hold any value includingnullorundefined.The root cause appears to be a restrictive type constraint inside
createServerFn's generics that narrowsunknown→{}, which excludesnull | undefinedfrom the index signature's value type.Your Example Website or App
https://stackblitz.com/edit/github-geldcamw?file=src%2Froutes%2Findex.tsx
Expected behavior
createServerFnshould accept return types / input types with[x: string]: unknownindex signatures, since this is what Zod v4'sz.looseObject()infers.Actual Behavior
TypeScript reports errors because somewhere in
createServerFn's internal type chain, the index signature is constrained to[x: string]: {}, andunknownis not assignable to{}.Screenshots or Videos
No response
Platform
@tanstack/react-start: 1.159.5@tanstack/react-router: 1.159.5zod: 4.3.6typescript: 5.9.3Additional context
No response