Type variants
Expose one Prisma model as several GraphQL types with the variant option.
One Prisma model often needs to appear in the schema as more than one GraphQL type: a public view and a private one, a full record and a lightweight card. Pothos calls these variants. Every model has one primary type (defined with a name, as on the objects page); each additional variant is defined with a variant option in its place.
This page assumes the generated types and builder are already wired up. The examples add an optional email column to the base Player model so a private variant has something to guard:
model Player {
id Int @id @default(autoincrement())
name String
email String?
// ...number, team, and stats as in the base schema
}Defining a variant
Give variant a type name instead of name. Here PlayerPrivateInfo is a second GraphQL type over the same Player model, exposing a field the public type shouldn't:
const PlayerPrivateInfo = builder.prismaObject('Player', {
variant: 'PlayerPrivateInfo',
fields: (t) => ({
id: t.exposeID('id'),
email: t.exposeString('email', { nullable: true }),
}),
});Linking variants together
t.variant adds a field that returns another variant of the same row. Reference the primary variant by its model name as a string; reference any other variant by the object ref it returned. An isNull callback can hide the variant when it shouldn't be visible. Here the private info resolves to null unless the parent player is the current viewer:
const PlayerPrivateInfo = builder.prismaObject('Player', {
variant: 'PlayerPrivateInfo',
fields: (t) => ({
email: t.exposeString('email', { nullable: true }),
// The model name references the primary variant.
player: t.variant('Player'),
}),
});
const Player = builder.prismaNode('Player', {
id: { resolve: (player) => String(player.id) },
fields: (t) => ({
name: t.exposeString('name'),
// Reference another variant by its ref, not the model name.
privateInfo: t.variant(PlayerPrivateInfo, {
// Hide private info unless the parent player is the current viewer.
isNull: (player, args, ctx) => player.id !== ctx.currentPlayerId,
}),
}),
});builder.prismaNode needs the relay plugin and takes an id option: either an id: { resolve } that computes the node id, or id: { field: 'id' } to point at a database column. A variant that doesn't need to be a Relay node can use builder.prismaObject instead.
Variants on relations
A relation field can return a variant rather than the related model's primary type. Pass the variant ref as the relation's type, and use query to scope which rows it loads. Here a team's schedule returns games through a CompletedGame variant, filtered to games already played:
const CompletedGame = builder.prismaNode('Game', {
variant: 'CompletedGame',
// Which database column backs the node id.
id: { field: 'id' },
fields: (t) => ({
playedAt: t.field({ type: 'DateTime', resolve: (game) => game.playedAt }),
homeTeam: t.relation('homeTeam'),
}),
});
const Team = builder.prismaObject('Team', {
variant: 'TeamSchedule',
fields: (t) => ({
id: t.exposeID('id'),
schedule: t.relation('homeGames', {
// Use the CompletedGame variant for this relation instead of the default Game.
type: CompletedGame,
query: { where: { playedAt: { lt: new Date() } } },
}),
}),
});Breaking circular references
Two prisma object refs that reference each other in their fields functions can trip TypeScript into a circular-type error. Split one side out with builder.prismaObjectField, which attaches a single field after both refs exist. It takes the ref, the field name, and a field function:
const PlayerPrivateInfo = builder.prismaObject('Player', {
variant: 'PlayerPrivateInfo',
fields: (t) => ({
email: t.exposeString('email', { nullable: true }),
}),
});
const Player = builder.prismaNode('Player', {
id: { resolve: (player) => String(player.id) },
fields: (t) => ({
name: t.exposeString('name'),
}),
});
// Attach the back-reference after both refs exist, breaking the cycle.
builder.prismaObjectField(PlayerPrivateInfo, 'player', (t) => t.variant(Player));The same workaround applies to relations that use variants: move the offending relation field into a prismaObjectField call.