Skip to content

createServerFn type inference incompatible with Zod v4 z.looseObject()[x: string]: unknown vs [x: string]: {} #6626

@carry0987

Description

@carry0987

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions