Skip to content

Start: Server-fn is not correctly detected when the handler is called with a type argument #5496

Closed

Description

@yuta-ike

Which project does this relate to?

Start

Describe the bug

Thank you so much for Tanstack Start.

When a type argument is passed to the handler function, as shown below, the server-fn code transformation doesn't occur. As a result, calling getNum from the server environment returns undefined.

const getNum = createServerFn({
  method: "GET",
}).handler<number>(() => 1);

Removing <number> from the handler call allows the code to be transformed correctly, and getNum will return 1.

While I haven't investigated in detail, I think this is due to the server-fn plugin's regular expression not matching. The regex /.\s*handler(/ doesn't account for type arguments.

include: [/\.\s*handler\(/, /\.\s*createMiddleware\(\)/],

Your Example Website or App

https://github.com/yuta-ike/tanstack-start-example

Steps to Reproduce the Bug or Issue

  1. npm install
  2. npm run dev
  3. Open http://localhost:3000/

You will see num: (type: undefined).

Expected behavior

num: 1 (type: number) is shown.

Screenshots or Videos

No response

Platform

  • Router / Start Version: 1.132.0
  • OS: macOS
  • Browser: Chrome
  • Browser Version: 140
  • Bundler: vite
  • Bundler Version: 7.1.7

Additional context

No response

Activity

  1. changed the title [-]Start: Server-Fn Not Correctly Detected in Some Cases[/-] [+]Start: Server-fn is not correctly detected when the handler is called with a type argument[/+] on Oct 16, 2025
  2. numToStr commented on Oct 16, 2025

    @numToStr

    I believe I'm facing the same issue, when I remove the type argument from the handler the app runs without any issue otherwise I get this error

    Query data cannot be undefined. Please make sure to return a value other than undefined from your query function. Affected query key: ["cart"]
    Error: ["cart"] data is undefined
        at Query.fetch (/app/node_modules/.pnpm/@tanstack+query-core@5.90.5/node_modules/@tanstack/query-core/src/query.ts:556:15)
        at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    Error: ["cart"] data is undefined
        at Query.fetch (/app/node_modules/.pnpm/@tanstack+query-core@5.90.5/node_modules/@tanstack/query-core/src/query.ts:556:15)
        at process.processTicksAndRejections (node:internal/process/task_queues:105:5) {
      cause: Error: ["cart"] data is undefined
          at Query.fetch (/app/node_modules/.pnpm/@tanstack+query-core@5.90.5/node_modules/@tanstack/query-core/src/query.ts:556:15)
          at process.processTicksAndRejections (node:internal/process/task_queues:105:5),
      status: 500,
      statusText: undefined,
      headers: undefined,
      data: undefined,
      body: undefined,
      unhandled: true
    }
  3. schiller-manuel commented on Oct 16, 2025

    @schiller-manuel
    Collaborator

    why would you need to supply a type parameter? this is inferred automatically
    the syntax you are using is not supported

  4. numToStr commented on Oct 16, 2025

    @numToStr

    why would you need to supply a type of parameter? this is inferred automatically the syntax you are using is not supported

    for me don't know why but inference is not working properly, and not just for this instance; it's in most serverfns if not all.

    1. Image
    2. Image
    3. Image

    and this with type param, although this fixes the const type signature (1) but does nothing for handler fn arg type error (2).

    Image

    also, after updating to rc even loader started giving type error

    Image
  5. schiller-manuel commented on Oct 16, 2025

    @schiller-manuel
    Collaborator

    whats the full type error you got?
    most likely you have something in your return type that is either unknown or not serializable, which will cause type errors as you must make sure this is serializable.

  6. numToStr commented on Oct 16, 2025

    @numToStr

    @schiller-manuel this is the error I'm getting for handler fn. I'm pretty sure it's not unknown and I don't know how to make it serializable as the type is coming from medusajs

    Argument of type '({ data }: ServerFnCtx<Register, Method, undefined, ZodObject<{ region: ZodString; collection: ZodOptional<ZodString>; }, $strip>>) => Promise<...>' is not assignable to parameter of type 'ServerFn<Register, Method, undefined, ZodObject<{ region: ZodString; collection: ZodOptional<ZodString>; }, $strip>, Promise<...>>'.
      Type 'Promise<StoreProductListResponse>' is not assignable to type 'Promise<{ limit: number; offset: number; count: number; estimate_count?: number | undefined; products: { collection?: { products?: ...[] | undefined; id: string; title: string; handle: string; created_at: string; updated_at: string; deleted_at: string | null; metadata: { ...; } | null; } | null | undefined; ... 29 m...'.
        Type 'StoreProductListResponse' is not assignable to type '{ limit: number; offset: number; count: number; estimate_count?: number | undefined; products: { collection?: { products?: ...[] | undefined; id: string; title: string; handle: string; created_at: string; updated_at: string; deleted_at: string | null; metadata: { ...; } | null; } | null | undefined; ... 29 more ...;...'.
          Types of property 'products' are incompatible.
            Type 'StoreProduct[]' is not assignable to type '{ collection?: { products?: ...[] | undefined; id: string; title: string; handle: string; created_at: string; updated_at: string; deleted_at: string | null; metadata: { [x: string]: {}; } | null; } | null | undefined; ... 29 more ...; metadata?: { ...; } | ... 1 more ... | undefined; }[]'.
              Type 'StoreProduct' is not assignable to type '{ collection?: { products?: ...[] | undefined; id: string; title: string; handle: string; created_at: string; updated_at: string; deleted_at: string | null; metadata: { [x: string]: {}; } | null; } | null | undefined; ... 29 more ...; metadata?: { ...; } | ... 1 more ... | undefined; }'.
                Types of property 'collection' are incompatible.
                  Type 'StoreCollection | null | undefined' is not assignable to type '{ products?: { collection?: ... | null | undefined; categories?: { products?: ...[] | undefined; parent_category: ... | null; category_children: ...[]; id: string; handle: string; description: string; ... 6 more ...; parent_category_id: string | null; }[] | null | undefined; ... 28 more ...; metadata?: { ...; } | .....'.
                    Type 'StoreCollection' is not assignable to type '{ products?: { collection?: ... | null | undefined; categories?: { products?: ...[] | undefined; parent_category: ... | null; category_children: ...[]; id: string; handle: string; description: string; ... 6 more ...; parent_category_id: string | null; }[] | null | undefined; ... 28 more ...; metadata?: { ...; } | .....'.
                      Types of property 'metadata' are incompatible.
                        Type 'Record<string, unknown> | null' is not assignable to type '{ [x: string]: {}; } | null'.
                          Type 'Record<string, unknown>' is not assignable to type '{ [x: string]: {}; }'.
                            'string' index signatures are incompatible.
                              Type 'unknown' is not assignable to type '{}'.ts(2345)
    
  7. schiller-manuel commented on Oct 16, 2025

    @schiller-manuel
    Collaborator

    StoreCollection seems to have a prop metadata of type Record<string, unknown>

  8. numToStr commented on Oct 16, 2025

    @numToStr

    ohh, i did not know that was the issue for type error. anyway to fix this?

  9. eyueldk commented on Apr 2, 2026

    @eyueldk

    I came across this issue as well. It seems that unknown being in the server function's return causes a type error.

  10. Joyzyy commented on Apr 2, 2026

    @Joyzyy

    I came across this issue too. The problem is with 3rd party libraries (e.g. medusajs/js-sdk) where the types from that library returns Record<string, unknown> or unknown. Using // ts-ignore then breaks the server function's return, since it returns any (and all my serverFn's are wrapped with ts-ignore).
    Hope this can get fixed!

  11. T0chi commented on Apr 27, 2026

    @T0chi

    StoreCollection seems to have a prop metadata of type Record<string, unknown>

    Happened to encounter this issue as well. Proceeded with a workaround for now.

    Hope it can help some of you :)

    Im using generated types for some api data that sometimes includes unknown values, so I had to make a utility type that replaces a desired target value with a replacement value, no matter the depth of the target value.

    My current workaround:

    Example usage:

    type User = {
        id: string;
        first_name: string;
        last_name: string;
        settings?: {
            customization?: {
                // we will target this
                custom_fields?: Record<string, unknown> | null;
            };
        };
    };
    
    type UserFixed = DeepReplace<
        User,
        { custom_fields?: Record<string, unknown> }, // target
        { custom_fields?: Record<string, any> }, // replacement
         // loose, strict or super-strict depending on
         // how thoroughly the target will be searched for
        'super-strict' // defaults to super-strict if not specified
    >;

    Unwrapped result:

    type UserFixed = {
        id: string;
        first_name: string;
        last_name: string;
        settings?: {
            customization?: {
                // we only replaced the target
                custom_fields?: Record<string, any> | undefined;
            } | undefined;
        } | undefined;
    }

    UserFixed now replaces the target value { custom_fields?: Record<string, unknown> } with { custom_fields?: Record<string, any> } resulting in this type:

    Image

    Utility type implementation:

    type Replace<
        T,
        Target,
        Replacement,
        Strictness extends 'loose' | 'strict' | 'super-strict' = 'super-strict',
        UseExact extends boolean = Strictness extends 'strict' ? true : false,
    > = ShouldReplaceValue<T, Target, Strictness, UseExact> extends true ? Replacement : T;
    
    type DeepReplace<
        T,
        Target,
        Replacement,
        Strictness extends 'loose' | 'strict' | 'super-strict' = 'super-strict',
        UseExact extends boolean = Strictness extends 'strict' ? true : false,
    > =
        ShouldReplaceValue<T, Target, Strictness, UseExact> extends true
            ? Replacement
            : T extends any[] | readonly any[]
              ? { [K in keyof T]: DeepReplace<T[K], Target, Replacement, Strictness, UseExact> }
              : T extends object
                ? {
                      [K in keyof T]: ShouldReplaceValue<
                          T[K],
                          Target,
                          Strictness,
                          UseExact
                      > extends true
                          ? Replacement
                          : NonNullable<T[K]> extends object
                            ? DeepReplace<T[K], Target, Replacement, Strictness, UseExact>
                            : T[K];
                  }
                : T;
    
    type IsExact<T, U> =
        (<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2 ? true : false;
    
    type MaybeExact<T, U, UseExact extends boolean> = UseExact extends true
        ? IsExact<T, U>
        : T extends U
          ? true
          : false;
    
    type MaybeExactInvariance<T, U, UseExact extends boolean> = UseExact extends true
        ? IsExact<U, T>
        : U extends T
          ? true
          : false;
    
    type ShouldReplaceValue<
        T,
        Target,
        Strictness,
        UseExact extends boolean,
    > = Strictness extends 'super-strict'
        ? MaybeExactInvariance<T, Target, UseExact>
        : MaybeExact<T, Target, UseExact>;
  12. schiller-manuel commented on Apr 30, 2026

    @schiller-manuel
    Collaborator

    you can now opt out of the type level serialization check via createServerFn({ strict: false }) which will also allow you to have unknown in there

    see #7277

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    typesChanges to the typescript types

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions