Skip to content

Instantly share code, notes, and snippets.

@nik-neg
Last active January 11, 2025 19:09
Show Gist options
  • Save nik-neg/2ed21d78030412cd4286917472559410 to your computer and use it in GitHub Desktop.
Save nik-neg/2ed21d78030412cd4286917472559410 to your computer and use it in GitHub Desktop.
Discriminate Union with Zod and Prisma

Discriminated Union Arrays in Prisma Models

Prisma currently does not support union types for fields directly in its schema. For instance, this is not possible:

model Movie {
  id        String @id @default(uuid())
  title     String
  customers (Client | User)[] // <-- Not supported in Prisma
}

However, there are workarounds to achieve similar functionality:


1. Store as JSON Field

If you need a single field to hold polymorphic data, you can store it in a Json column. For example:

model Movie {
  id        String   @id @default(uuid())
  title     String
  customers Json?
}

In this case, you can store discriminated unions using libraries like Zod, but note that:

  • Prisma treats it as untyped JSON.
  • You lose relational features like foreign keys and constraints.

2. Use a Relational Approach with a Discriminator

Option 2A: Single Polymorphic Table

You can create a single table for "customers" with a type discriminator field:

model Movie {
  id        String @id @default(uuid())
  title     String
  customers PolymorphicCustomer[]
}

model PolymorphicCustomer {
  id          String @id @default(uuid())
  type        String // e.g., "Client" or "User"
  movieId     String
  movie       Movie  @relation(fields: [movieId], references: [id])
  // Additional fields for the specific types
}

Each row in PolymorphicCustomer behaves as either a "Client" or a "User," depending on the type value.


Option 2B: Separate Tables with a Union Table

Create separate models for Client and User, and use a bridging table for the union:

model Movie {
  id           String @id @default(uuid())
  title        String
  movieClients MovieClient[]
  movieUsers   MovieUser[]
}

// Bridging table for Clients
model MovieClient {
  movieId  String
  clientId String
  movie    Movie  @relation(fields: [movieId], references: [id])
  client   Client @relation(fields: [clientId], references: [id])
  @@id([movieId, clientId])
}

// Bridging table for Users
model MovieUser {
  movieId String
  userId  String
  movie   Movie @relation(fields: [movieId], references: [id])
  user    User  @relation(fields: [userId], references: [id])
  @@id([movieId, userId])
}

model Client {
  id     String @id @default(uuid())
  name   String
  movies MovieClient[]
}

model User {
  id     String @id @default(uuid())
  name   String
  movies MovieUser[]
}

With this approach:

  • Use separate queries to fetch movieClients and movieUsers.
  • Merge the results in application logic for a unified "customers" list.

Key Takeaways

Prisma does not directly support fields with union types (e.g., (Client | User)[]). To handle polymorphic data:

  1. Use a Json field for flexibility, or
  2. Use relational models with:
    • A single polymorphic table with a discriminator field.
    • Separate models and bridging tables for relational integrity.

You can then use libraries like Zod or application logic to enforce and handle discriminated unions at runtime.

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