This guide migrates Relational Queries v1 from drizzle-orm@0.x or early v1 betas to Relational Queries v2 in drizzle-orm@rc. Notes about changes from earlier release candidates are marked below.
Legacy relations imports
The before examples below use the 0.x import: import { relations } from 'drizzle-orm'. In early v1 betas that kept both relational APIs, the legacy function was available as import { relations } from 'drizzle-orm/_relations'. Both are starting points for this migration. The _relations module still exists in rc.5, but RQB v1 support has been removed from the database drivers. For rc.5, use import { defineRelations } from 'drizzle-orm'.
// Before: drizzle-orm@0.ximport { relations } from 'drizzle-orm';
// Before: early v1 betas with the legacy RQB v1 APIimport { relations } from 'drizzle-orm/_relations';
// After: drizzle-orm@rc (RQB v2)import { defineRelations } from 'drizzle-orm';
One of the biggest updates were in Relations Schema definition
The first difference is that you no longer need to specify relations for each table separately in different objects and
then pass them all to drizzle() along with your schema. In Relational Queries v2, you now have one dedicated place to
specify all the relations for all the tables you need.
The r parameter in the callback provides comprehensive autocomplete
functionality - including all tables from your schema and functions such as one, many, and through - essentially
offering everything you need to specify your relations.
// relations.tsimport * as schema from "./schema"import { defineRelations } from "drizzle-orm"export const relations = defineRelations(schema, (r) => ({ ...}));
// index.tsimport { relations } from "./relations"import { drizzle } from "drizzle-orm/..."const db = drizzle(process.env.DATABASE_URL, { relations })
What is different?
Schema Definition
import * as p from 'drizzle-orm/pg-core';export const users = p.pgTable('users', { id: p.integer().primaryKey(), name: p.text(), invitedBy: p.integer('invited_by'),});export const posts = p.pgTable('posts', { id: p.integer().primaryKey(), content: p.text(), authorId: p.integer('author_id'),});
In v1, if you wanted only the many side of a relationship, you had to specify the one side on the other end,
which made for a poor developer experience.
In v2, you can simply use the many side without any additional steps
import { defineRelations } from "drizzle-orm";import * as schema from "./schema";export const relations = defineRelations(schema, (r) => ({ users: { posts: r.many.posts({ from: r.users.id, to: r.posts.authorId, }), },}));
New optional option
optional: false removes null from the related value’s inferred type. It does not create a database constraint. Use it only when you are certain that the related row will always exist.
Before: drizzle-orm@0.x (RQB v1)
Was not supported in v1
✅ v2
import { defineRelations } from "drizzle-orm";import * as schema from "./schema";export const relations = defineRelations(schema, (r) => ({ posts: { author: r.one.users({ from: r.posts.authorId, to: r.users.id, optional: false, }), },}));
No modes in drizzle()
We found a way to use the same strategy for all MySQL dialects, so there’s no need to specify them
Before: drizzle-orm@0.x (RQB v1)
import * as schema from './schema'const db = drizzle(process.env.DATABASE_URL, { mode: "planetscale", schema });// orconst db = drizzle(process.env.DATABASE_URL, { mode: "default", schema });
✅ v2
import { relations } from './relations'const db = drizzle(process.env.DATABASE_URL, { relations });
from and to upgrades
We’ve renamed fields to from and references to to, and we made both accept either a single value or an array
drizzle/relations.ts includes an import of all tables from drizzle/schema.ts, which looks like this:
import * as schema from './schema'
You may need to change this import to a file where ALL your schema tables are located.
If there are multiple schema files, you can do the following:
import * as schema1 from './schema1'import * as schema2 from './schema2'...
Step 3
Change drizzle database instance creation and provide relations object instead of schema
Before: drizzle-orm@0.x (RQB v1)
import * as schema from './schema'import { drizzle } from 'drizzle-orm/...'const db = drizzle('<url>', { schema })
After
// should be imported from a file in Step 2import { relations } from './relations'import { drizzle } from 'drizzle-orm/...'const db = drizzle('<url>', { relations })
If you had MySQL dialect, you can remove mode from drizzle() as long as it’s not needed in version 2
Option 2: Manual migration
If you want to migrate manually, you can check our Drizzle Relations section for the complete API reference and examples of one-to-one, one-to-many, and many-to-many relations.
How to migrate queries from v1 to v2
Migrate where statements
You can check our Select Filters docs to see examples and a complete API reference.
With the new syntax, you can use AND, OR, NOT, and RAW, plus all the filtering operators that
were previously available in Relations v1.
select "d0"."id" as "id", "d0"."name" as "name", "d0"."age" as "age"from "users" as "d0"where ( (not ("d0"."id" > 10)) and ("d0"."name" like 'John%') )
// schema.tsimport { integer, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";export const users = pgTable("users", { id: integer().primaryKey(), name: text(), email: text().notNull(), age: integer(), createdAt: timestamp("created_at").defaultNow(), lastLogin: timestamp("last_login"), subscriptionEnd: timestamp("subscription_end"), lastActivity: timestamp("last_activity"), preferences: jsonb(), // JSON column for user settings/preferences interests: text().array(), // Array column for user interests});
SELECT "d0"."id" AS "id", "d0"."name" AS "name", "d0"."email" AS "email", "d0"."age" AS "age", "d0"."created_at" AS "createdAt", "d0"."last_login" AS "lastLogin", "d0"."subscription_end" AS "subscriptionEnd", "d0"."last_activity" AS "lastActivity", "d0"."preferences" AS "preferences", "d0"."interests" AS "interests"FROM "users" AS "d0"WHERE ( ( ( (LOWER("d0"."name") LIKE 'john%') OR ("d0"."name" ILIKE 'jane%') ) ) AND ( ( ("d0"."preferences" ->> 'theme' = 'dark') OR ("d0"."preferences" ->> 'theme' IS NULL) ) ) AND ("d0"."age" BETWEEN 25 AND 35) )
Handle optional filters explicitly
Starting with 1.0.0-rc.5, an explicit undefined in a relational filter throws instead of silently skipping the condition. Omit the field, or use EmptyFilter when skipping it is intentional:
Here, an absent userId leaves the query unfiltered. Only skip the condition if returning all users is intended. This change concerns db.query filters, not conditional filters in the SQL query builder.
Check whether a relation exists
Use true to require a related row and false to require its absence:
// Users with at least one postconst usersWithPosts = await db.query.users.findMany({ where: { posts: true },});// Users without postsconst usersWithoutPosts = await db.query.users.findMany({ where: { posts: false },});
Starting with 1.0.0-rc.5, where: { posts: {} } skips the relation filter. It does not check whether a post exists. The same applies when the object contains only empty conditions.
Migrate orderBy statements
You can use an object to specify each column and its sort direction (asc or desc). The callback form remains supported, so an existing orderBy: (users, { asc }) => [asc(users.id)] does not need to change.
For a field on a many relation, specify an aggregate, such as orderBy: { posts: { id: 'desc(max)' } }. For a one relation, use the field’s direction directly, such as orderBy: { author: { name: 'asc' } } when querying posts.
Migrate many-to-many queries
Relational Queries v1 had a very complex way of managing many-to-many queries.
You had to use junction tables to query through them explicitly, and then map those tables out, like this:
The following entities have been removed from drizzle-orm and drizzle-orm/relations. The original imports now
include new types used by Relational Queries v2, so make sure to update your imports if you intend to use the older types:
A list of all removed entities
Relations
TableRelationsKeysOnly
ExtractTableRelationsFromSchema
ExtractRelationsFromTableExtraConfigSchema
getOperators
FindTableByDBName
RelationalSchemaConfig
RelationConfig
extractTablesRelationalConfig
relations
createOne
createMany
NormalizedRelation
normalizeRelation
createTableRelationsHelpers
TableRelationsHelpers
In the same manner, ${dialect}-core/query-builders/query files were updated with RQB v2’s alternatives
Examples
import { RelationalQueryBuilder, PgRelationalQuery } from 'drizzle-orm/pg-core/query-builders/query';