middleware icon indicating copy to clipboard operation
middleware copied to clipboard

Cannot use imported v4 schema in createRoute

Open kotkoroid opened this issue 4 months ago • 9 comments

Which middleware has the bug?

@hono/zod-openapi

What version of the middleware?

1.1.0

What version of Hono are you using?

4.9.2

What runtime/platform is your app running on? (with version if possible)

Cloudflare Workers

What steps can reproduce the bug?

Consider a following schema to be imported from a library:

export const paginationSchema = new z.$ZodObject({
  type: 'object',
  shape: {
    page: new z.$ZodDefault({
      type: 'default',
      innerType: new z.$ZodNumber({
        type: 'number',
      }),
      defaultValue: () => 1,
    }),
    limit: new z.$ZodDefault({
      type: 'default',
      innerType: new z.$ZodNumber({
        type: 'number',
      }),
      defaultValue: () => 20,
    }),
  },
})

The library defined the schema using zod/v4/core, following the guidance in Zod docs. However, these core schemas don't have parsing methods like .parse() and .safeParse(). This raises a problem when trying to use the schema directly in createRoute function as it expects full Zod schema with all the parsing methods.

What is the expected behavior?

Basically, I'm stuck between two incompatible approaches: the library correctly follows Zod v4 patterns, but Hono's type system can't work with those patterns. I either have to recreate the entire schema myself, which defeats the purpose of using the library, or I lose all type safety in my route handlers. It seems like there should be a way to bridge these two approaches without sacrificing either reusability or type safety.

What do you see instead?

I tried creating a wrapper function that adds the parsing methods to the core schema, which lets me create the route successfully:

function withMethods<T extends zCore.$ZodObject>(coreSchema: T) {
  const methods = {
    parse: (data: unknown) => zCore.parse(coreSchema, data),
    safeParse: (data: unknown) => zCore.safeParse(coreSchema, data),
    parseAsync: (data: unknown) => zCore.parseAsync(coreSchema, data),
    safeParseAsync: (data: unknown) => zCore.safeParseAsync(coreSchema, data),
  }

  return Object.assign(Object.create(Object.getPrototypeOf(coreSchema)), {
    ...coreSchema,
    ...methods,
    _output: undefined as zCore.infer<T>,
  })
}

However, when I try to use the route handler with proper types, the inference completely breaks down as RouteHandler type can't figure out what the query parameters should be:

const { limit } = context.req.valid('query') // Argument of type '"query"' is not assignable to parameter of type 'never'. (ts 2345)

Additional information

No response

kotkoroid avatar Aug 25 '25 13:08 kotkoroid

Hi @kotkoroid

Where do you import z from? @hono/zod-openapi?

yusukebe avatar Aug 27 '25 07:08 yusukebe

Hi, in the Hono application I use following imports:

import type { RouteHandler } from '@hono/zod-openapi'
import { createRoute, z } from '@hono/zod-openapi'

In the library the schema (paginationSchema) is defined with this import: import * as z from 'zod/v4/core'

kotkoroid avatar Aug 28 '25 06:08 kotkoroid

@kotkoroid

I think you have to use z from @hono/zod-openapi.

yusukebe avatar Aug 28 '25 06:08 yusukebe

You mean in the library? That goes against the Zod guidelines: https://zod.dev/library-authors?id=how-to-support-zod-4

kotkoroid avatar Sep 01 '25 08:09 kotkoroid

@kotkoroid

I don't know if it should be called library, but paginationSchema should be defined with z imported from @hono/zod-openapi, not zod.

If you think this is caused by the version difference between v3 and v4 of Zod, can you try it with v3 and confirm that it does not have a problem?

yusukebe avatar Sep 01 '25 09:09 yusukebe

we have a package called @acme/schemas in our monorepo, where other apps import common zod schemas from. we have different applications which use frameworks other than hono. therefore we cannot enforce the usage of import {z} from @hono/zod-openapi in the entire project. all we can do is enforce the same zod version across the monorepo.

it would be nice if @hono/zod-openapi could import regular schemas created by regular zod

utkuturunc avatar Sep 05 '25 13:09 utkuturunc

@utkuturunc have you found a workaround as we're running into the exact same issue. Monorepo with a package containing schemas that are published and can be used in non-hono projects. So we don't want to depend on @hono/zod-openapi in them.

Using the extendZodWithOpenApi() function also doesn't resolve the issue for me..

b-vb avatar Oct 16 '25 07:10 b-vb

@b-vb i mostly ended up rewriting schemas any because i had a special use case that allowed for it. in 2 cases, i was able to use the .openapi method on schemas imported from other packages where they were defined with regular zod. it worked out, i haven't checked why, just taking the win.

i noticed this in the typings:

declare module 'zod' {
    interface ZodType<out Output = unknown, out Input = unknown, out Internals extends core.$ZodTypeInternals<Output, Input> = core.$ZodTypeInternals<Output, Input>> extends core.$ZodType<Output, Input, Internals> {
        openapi(metadata: Partial<ZodOpenAPIMetadata<Input>>, options?: OpenApiOptions): this;
        openapi(refId: string, metadata?: Partial<ZodOpenAPIMetadata<Input>>, options?: OpenApiOptions): this;
    }
}

so when i import schemas from other packages into files that have an import to this library, they also end up with the openapi method, and js behind the ts seems to support it as well.

utkuturunc avatar Oct 16 '25 14:10 utkuturunc

I manage to make it work with extendZodWithOpenApi https://github.com/asteasolutions/zod-to-openapi/blob/master/src/zod-extensions.ts

just put

import z from 'zod';
extendZodWithOpenApi(z);

in any file where your packages is, I wonder if this is more of a problem of zod-openapi or zod-to-openapi seeing how hono rely heavily on zod-to-openapi library

anton89 avatar Nov 03 '25 14:11 anton89