zod icon indicating copy to clipboard operation
zod copied to clipboard

Inferred type imported from library has all fields as optional

Open stefanlogue opened this issue 1 year ago • 5 comments

I've got a schema and inferred type in a library:

const schema = z.object({
  name: z.string(),
  address: z.string(),
});

type SchemaType = z.infer<typeof schema>;

I'm importing this inferred type into a project, and for some reason all of the fields are showing as potentially undefined. It's got an enum in there and it also shows undefined.

The type signature for the schema in the library looks like this:

const schema = z.ZodObject<{
  name: z.ZodString;
  addresss: z.ZodString;
}, "strip", z.ZodTypeAny, {
  name: string;
  address: string;
}, {
  ...;
}>

The type signature for the schema when imported into the project looks like this:

const schema = z.ZodObject<{
  name: z.ZodString;
  addresss: z.ZodString;
}, "strip", z.ZodTypeAny, {
  name?: string | undefined;
  address?: string | undefined;
}, {
  ...;
}>

I've got strict: true in both tsconfig.json files (the library and the project). strictNullChecks is not set in either.

Library: Zod 3.23.8 TS 5.4.5

Project: Zod 3.23.8 TS 4.9.5

Where am I going wrong?

stefanlogue avatar Aug 14 '24 09:08 stefanlogue

You need "strictNullChecks": true in your tsconfig.json.

https://github.com/colinhacks/zod/issues/43

colinhacks avatar Aug 15 '24 20:08 colinhacks

The comments there say I just need to ensure that strictNullChecks isn't set to false, which it isn't.

Still, even with this set in the consuming project, they show as undefined.

Weirdly, this is only an issue when I import a type from a library. If I define the type inline and infer it, it behaves as expected.

If I import the schema and .strip() it, the inferred type works as expected.

stefanlogue avatar Aug 15 '24 20:08 stefanlogue

I believe I'm running into the same issue. When I import the inferred type across projects the type gets a [x: string]: any property added as well as all my fields are not optional and my fields that were using z.nativeEnum have become any.

For me importing the schema instead and then trying to infer the type within my consuming project didn't work like it did for Stefan.

Edit: I was able to find a fix thanks to this SO post: https://stackoverflow.com/questions/74185198/typescript-losing-zod-and-trpc-types-across-monorepo-projects-types-result-in

Basically need to set "compilerOptions": { "composite": true } within the tsconfig of the project that contains the Zod schema definitions and type inferences. Then in the project using it you need to set "references": [ { "path": "path/to/other/tsconfig" }].

trey-yeager-FHR avatar Aug 21 '24 21:08 trey-yeager-FHR

Still getting this issue, I've got "strictNullChecks": true, "strict": true in both of my tsconfig.json files. When I look at the inferred type in the library, it is correct. When I import that type into my project, everything is now optional. Using the schema to safeParse leaves everything optional. Does anyone have any idea on how to fix this?

stefanlogue avatar Sep 11 '24 15:09 stefanlogue

I had a similar issue with a shared zod schema package in my Turborepo monorepo. The issue was that I was using the compiled type in the exports of package.json: "types": "./dist/config.d.ts",. Changing it to "types": "./src/config.ts", fixed the problem.

If it helps anyone, here is my full tsconfig and package.json.

// tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "module": "commonjs",
    "declaration": true,
    "noImplicitAny": false,
    "skipLibCheck": true,
    "noUnusedLocals": false,
    "importHelpers": true,
    "removeComments": false,
    "noLib": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "useUnknownInCatchVariables": false,
    "target": "ES2021",
    "sourceMap": false,
    "allowJs": false,
    "strict": true,
    "strictNullChecks": true,
    "types": ["node"],
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist", "test/**/*", "*.spec.ts"]
}
// package.json
{
  "name": "@repo/schemas",
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch"
  },
  "exports": {
    "./config": {
      "types": "./src/config.ts",     // <------
      "default": "./dist/config.js"
    }
  },
  "devDependencies": {
    "@repo/typescript-config": "*",
    "typescript": "latest"
  },
  "dependencies": {
    "zod": "^3.23.8"
  }
}

And the structure was simply just src/config.ts which exported zod schemas related to my configs.

isaackoz avatar Sep 29 '24 03:09 isaackoz

Hi @stefanlogue! I'm Dosu and I’m helping the zod team.

This is a TypeScript ecosystem issue, not a Zod bug. When you export inferred types from a library, TypeScript's .d.ts emission often can't preserve the complex conditional types Zod uses to track required/optional fields. This causes all fields to become optional or unions with undefined in the consuming project—even if your tsconfig is correct—because the type logic gets "flattened" during type emission and import. This is a well-known limitation with TypeScript and libraries like Zod that use advanced type inference logic (source).

The best workaround is to always import the schema itself into your consuming project and use z.infer<typeof schema> locally, rather than importing the inferred type from the library. If you must share types, set "composite": true in your library's tsconfig and use TypeScript project references (references) in your consumer's tsconfig. Also, make sure both projects use the same TypeScript version. These steps help TypeScript preserve more type information across package boundaries, but the most reliable approach is to infer types locally from the schema.

If you still see issues after trying this, a minimal reproduction (e.g., a small repo or CodeSandbox) would help @colinhacks investigate further. If this answers your question, please close the issue!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

dosubot[bot] avatar Jul 21 '25 23:07 dosubot[bot]