Ask questionsGuidance on creating slight schema variants for varied contexts

I am interested in using zod for frontend, API, and backend validation.

For example, a browser might validate user inputs live as they are changed on the page. The user hits "submit" which sends the data to the API. The API runs its own validations on this data that may be somewhat different. The API changes the data slightly to conform with the backend needs. The backend has a similar schema, but there are some small differences.

Differences might be things like:

  • Adding a field deep in the schema
  • Swapping a foreign key ID for an object pulled from some other location
  • API allows partial inputs for updates, but the backend sometimes requires complete inputs. For example, take a blog post draft that can be missing a title field, but the backend requires a title to be persisted before publishing.

I'm not necessarily looking for changes to the library, but rather recommended patterns through documentation, blog posts, etc. I see that zod supports merging, masking, and extending, but am looking for higher level guidance on patterns for this type of use case. I can see schema sharing as something that could potentially make a codebase much more complex without due care. The guidance here might be to create separate schemas because the costs of coupling schemas across contexts is too high!

There may also be features that could be added to zod that make this use case easier.


Answer questions chrbala

Thinking about this one a bit more. I'm wondering if it might make sense to generally solve this type of thing on the application side like this:

// commonValidations.ts
import * as z from 'zod';

export const id = z.string().refine(val => val.length == 8, {
  message: "Foreign keys should be 8 characters",
// clientValidations.ts
import * as z from 'zod';
import * as commonValidations from './commonValidations';

  Client does not have any extra validations for ID, so it uses common directly
export const user = z.object({
// serverValidations.ts
import * as z from 'zod';

// model only exists on the server, not implemented here
import { isValidId } from './applicationModel';
import * as commonValidations from './commonValidations';

export const id =, {
  message: 'ID is invalid',

export const user = z.object({

What I like about this design is that everything is very explicit, clear, and avoids tight coupling between client and server. What I don't like is that it could get pretty verbose with lots of redundancy across the client and server as the schema gets more complex. That is, if user had a schema refinement, it would need to be shared across the client and server as well. Any kind of schema patching would be more concise in exchange for tighter coupling between client and server in this type of situation.


Related questions

No questions were found.
Github User Rank List