{"id":29132159,"url":"https://github.com/alloc/drizzle-plus","last_synced_at":"2026-04-04T13:39:40.214Z","repository":{"id":301617977,"uuid":"1009836317","full_name":"alloc/drizzle-plus","owner":"alloc","description":"A collection of useful utilities and extensions for Drizzle ORM","archived":false,"fork":false,"pushed_at":"2025-06-27T20:31:55.000Z","size":0,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-27T20:37:08.637Z","etag":null,"topics":["drizzle-orm","javascript","nodejs","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alloc.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-27T19:53:42.000Z","updated_at":"2025-06-27T20:31:57.000Z","dependencies_parsed_at":"2025-06-27T20:47:41.584Z","dependency_job_id":null,"html_url":"https://github.com/alloc/drizzle-plus","commit_stats":null,"previous_names":["alloc/drizzle-plus"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/alloc/drizzle-plus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alloc%2Fdrizzle-plus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alloc%2Fdrizzle-plus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alloc%2Fdrizzle-plus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alloc%2Fdrizzle-plus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alloc","download_url":"https://codeload.github.com/alloc/drizzle-plus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alloc%2Fdrizzle-plus/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262496802,"owners_count":23320281,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["drizzle-orm","javascript","nodejs","typescript"],"created_at":"2025-06-30T06:16:25.125Z","updated_at":"2026-04-04T13:39:40.201Z","avatar_url":"https://github.com/alloc.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# drizzle-plus\n\n[![npm version][npm-version-src]][npm-version-href]\n[![npm downloads][npm-downloads-src]][npm-downloads-href]\n[![License][license-src]][license-href]\n\nA collection of useful utilities and extensions for Drizzle ORM.\n\n\u003e [!WARNING]\n\u003e This package only works with Drizzle v1.0.0 or later.\n\u003e\n\u003e That means you need `drizzle-orm@beta` installed.\n\u003e\n\u003e ```bash\n\u003e pnpm add drizzle-orm@beta\n\u003e ```\n\n#### Highlights\n\n- Support for 🐘 **Postgres**, 🐬 **MySQL**, and 🪶 **SQLite**\n- Added `upsert()` method to `db.query` for “create or update” operations\n- Added `updateMany()` method to `db.query` for updating many rows at once\n- Added `count()` method to `db.query` for easy counting of rows\n- Added `findUnique()` method to `db.query` for efficient lookups by primary key or unique constraint\n- Added `findManyAndCount()` method to `db.query` for convenient, parallel execution of `findMany()` and `count()` queries\n- Added `$cursor()` method to `db.query` for type-safe, cursor-based pagination\n- Nested subqueries with `nest()` helper\n- `CASE…WHEN…ELSE…END` with `caseWhen()` helper\n- JSON helpers like `jsonAgg()` and `jsonBuildObject()`\n- Useful types via `drizzle-plus/types`\n- …and more!\n\n**Contributions are welcome!** Let's make this a great library for everyone.\n\n\u003e [!NOTE]\n\u003e If you like what you see, please ⭐ this repository! It really helps to attract more contributors. If you have any ❓ **questions** or 💪 **feature requests**, do not hesitate to 💬 **open an issue**.\n\n## Installation\n\n- **PNPM**\n  ```bash\n  pnpm add drizzle-plus\n  ```\n- **Yarn**\n  ```bash\n  yarn add drizzle-plus\n  ```\n- **NPM**\n  ```bash\n  npm install drizzle-plus\n  ```\n\n## Usage\n\n### Upsert\n\nImport the `upsert` module to extend the query builder API with a `upsert` method.\n\n\u003e [!WARNING]\n\u003e 🐬 **MySQL** is not supported yet.\n\nThe `upsert` method intelligently infers the correct columns to update based on the primary key and unique constraints of the table. This means you're _not_ required to manually specify a `where` clause (as you would in Prisma).\n\n```ts\n// Choose your dialect\nimport 'drizzle-plus/pg/upsert'\nimport 'drizzle-plus/sqlite/upsert'\n\n// Now you can use the `upsert` method\nconst query = db.query.user.upsert({\n  data: {\n    id: 42,\n    name: 'Chewbacca',\n  },\n})\n\nquery.toSQL()\n// =\u003e {\n//   sql: `insert into \"user\" (\"id\", \"name\") values (?, ?) on conflict (\"user\".\"id\") do update set \"name\" = excluded.\"name\" returning \"id\", \"name\"`,\n//   params: [42, 'Chewbacca'],\n// }\n\n// Execute the query\nconst result = await query\n// =\u003e {\n//   id: 42,\n//   name: 'Chewbacca',\n// }\n```\n\n#### Returning clause\n\nBy default, `upsert` will return all columns of the upserted row. But you can specify a `returning` clause to return only the columns you want. Any SQL expression is allowed in the `returning` clause.\n\n```ts\nimport { upper } from 'drizzle-plus'\n\nconst result = await db.query.user.upsert({\n  data: {\n    id: 42,\n    name: 'Chewbacca',\n  },\n  // Pass a function to reference the upserted row, or pass a plain object.\n  returning: user =\u003e ({\n    id: true,\n    nameUpper: upper(user.name),\n    random: sql\u003cnumber\u003e`random()`,\n  }),\n})\n// =\u003e {\n//   id: 42,\n//   nameUpper: 'CHEWBACCA',\n//   random: 0.123456789,\n// }\n```\n\nSet `returning` to an empty object to return nothing.\n\n#### Upserting many rows\n\nYou may pass an array of objects to the `data` property to upsert many rows at once. For optimal performance and atomicity guarantees, the rows are upserted in a single query.\n\n```ts\nconst rows = await db.query.user.upsert({\n  data: [\n    { id: 42, name: 'Chewbacca' },\n    { id: 43, name: 'Han Solo' },\n  ],\n})\n// =\u003e [{ id: 42, name: 'Chewbacca' }, { id: 43, name: 'Han Solo' }]\n```\n\n#### Conditional updates\n\nIf a row should only be updated if it matches a certain condition, you can set the `where` option. This accepts the same object as the `where` clause of `db.query#findMany()`.\n\n```ts\nconst query = db.query.user.upsert({\n  data: {\n    id: 42,\n    handle: 'chewie',\n  },\n  where: {\n    emailVerified: true,\n  },\n})\n\nquery.toSQL()\n// =\u003e {\n//   sql: `insert into \"user\" (\"id\", \"handle\") values (?, ?) on conflict (\"user\".\"id\") do update set \"handle\" = excluded.\"handle\" where \"user\".\"email_verified\" = true returning \"id\", \"handle\"`,\n//   params: [42, 'chewie'],\n// }\n```\n\n#### Updating with different data\n\nIf the data you wish to _insert_ with is different from the data you wish to\n_update_ with, try setting the `update` option. This option can either be a function that receives the table as an argument, or a plain object. This feature works with both single and many upserts (e.g. when `data` is an array).\n\n```ts\nconst query = db.query.user.upsert({\n  data: {\n    id: 42,\n    loginCount: 0,\n  },\n  update: user =\u003e ({\n    // Mutate the existing count if the row already exists.\n    loginCount: sql`${user.loginCount} + 1`,\n  }),\n})\n\nquery.toSQL()\n// =\u003e {\n//   sql: `insert into \"user\" (\"id\", \"login_count\") values (?, ?) on conflict (\"user\".\"id\") do update set \"login_count\" = \"user\".\"login_count\" + 1 returning \"id\", \"login_count\"`,\n//   params: [42, 0],\n// }\n```\n\n#### Upserting relations\n\nThere are no plans to support Prisma’s `connect` or `connectOrCreate` features. It’s recommended to use `db.transaction()` instead.\n\n\u003e [!NOTE]\n\u003e Depending on the complexity of the relations, it may be possible to utilize\n\u003e _subqueries_ instead of using `db.transaction()`. Do that if you can, since it\n\u003e will avoid the round trip caused by each `await` in the transaction callback.\n\n```ts\nimport 'drizzle-plus/pg/upsert'\nimport { nest } from 'drizzle-plus'\n\nawait db.transaction(async tx =\u003e {\n  const { id } = await tx.query.user.upsert({\n    data: {\n      id: 42,\n      name: 'Chewbacca',\n    },\n    returning: {\n      id: true,\n    },\n  })\n  await tx.query.friendship.upsert({\n    data: {\n      userId: id,\n      friendId: nest(\n        tx.query.user.findFirst({\n          where: {\n            name: 'Han Solo',\n          },\n          columns: {\n            id: true,\n          },\n        })\n      ),\n    },\n  })\n})\n```\n\n### Update Many\n\nImport the `updateMany` module to extend the query builder API with a `updateMany` method.\n\nThe `updateMany` method has the following options:\n\n- `set`: **(required)** The columns to update. May be a function or a plain object.\n- `where`: A filter to only update rows that match the filter. Same API as `findMany()`.\n- `orderBy`: The order of the rows to update. Same API as `findMany()`.\n- `limit`: The maximum number of rows to update.\n- `returning`: The columns to return. Same API as `upsert()` above.\n\n```ts\n// Choose your dialect\nimport 'drizzle-plus/pg/updateMany'\nimport 'drizzle-plus/mysql/updateMany'\nimport 'drizzle-plus/sqlite/updateMany'\n\n// Now you can use the `updateMany` method\nconst query = db.query.user.updateMany({\n  // Pass a function to reference the updated row, or pass a plain object.\n  set: user =\u003e ({\n    name: sql`upper(${user.name})`,\n  }),\n  where: {\n    name: 'Jeff',\n  },\n})\n\nquery.toSQL()\n// =\u003e {\n//   sql: `update \"user\" set \"name\" = upper(\"user\".\"name\") where \"user\".\"name\" = ?`,\n//   params: ['Jeff'],\n// }\n```\n\nIf the `returning` option is undefined or an empty object, the query will return the number of rows updated. Otherwise, an array of objects will be returned.\n\n### Count\n\nImport the `count` module to extend the query builder API with a `count` method.\n\n```ts\n// Choose your dialect\nimport 'drizzle-plus/pg/count'\nimport 'drizzle-plus/mysql/count'\nimport 'drizzle-plus/sqlite/count'\n\n// Now you can use the `count` method\nconst count = db.query.foo.count()\n//    ^? Promise\u003cnumber\u003e\n\n// Pass filters to the `count` method\nconst countWithFilter = db.query.foo.count({\n  id: { gt: 100 },\n})\n\n// Inspect the SQL:\nconsole.log(countWithFilter.toSQL())\n// {\n//   sql: `select count(*) from \"foo\" where \"foo\".\"id\" \u003e 100`,\n//   params: [],\n// }\n\n// Execute the query\nconst result = await countWithFilter\n// =\u003e 0\n```\n\n### Find Unique\n\nImport the `findUnique` module to extend the query builder API with a `findUnique` method.\n\nThe only thing `findUnique()` does differently from `findFirst()` is that it\nrequires the `where` clause to match a primary key or unique constraint. Unfortunately, Drizzle doesn't have type-level tracking of primary keys or unique constraints, so `findUnique()` will only throw at runtime (no compile-time warnings).\n\n```ts\n// Choose your dialect\nimport 'drizzle-plus/pg/findUnique'\nimport 'drizzle-plus/mysql/findUnique'\nimport 'drizzle-plus/sqlite/findUnique'\n\n// Now you can use the `findUnique` method\nconst result = await db.query.user.findUnique({\n  where: {\n    id: 42,\n  },\n})\n// =\u003e { id: 42, name: 'Chewbacca' }\n```\n\nIf no matching record is found, `findUnique()` will resolve to `undefined`.\n\n### Find Many and Count\n\nImport the `findManyAndCount` module to extend the query builder API with a `findManyAndCount` method.\n\nThe `findManyAndCount` method accepts the same arguments as `findMany()`, and returns an object with `data` and `count` properties. The count is the total number of rows that would be returned by the `findMany` query, without any `limit` or `offset` applied.\n\n```ts\n// Choose your dialect\nimport 'drizzle-plus/pg/findManyAndCount'\nimport 'drizzle-plus/mysql/findManyAndCount'\nimport 'drizzle-plus/sqlite/findManyAndCount'\n\n// Now you can use the `findManyAndCount` method\nconst { data, count } = await db.query.foo.findManyAndCount({\n  where: {\n    age: { gt: 20 },\n  },\n  limit: 2,\n  columns: {\n    id: true,\n    name: true,\n    age: true,\n  },\n})\n// =\u003e {\n//   data: [\n//     { id: 1, name: 'Alice', age: 25 },\n//     { id: 2, name: 'Bob', age: 30 },\n//   ],\n//   count: 10,\n// }\n```\n\nThe two queries (`findMany` and `count`) are executed in parallel.\n\n\u003e [!WARNING]\n\u003e Your database connection _may not_ support parallel queries, in which case this method will execute the queries sequentially.\n\n### Cursor-Based Pagination\n\nImport the `$cursor` module to extend the query builder API with a `$cursor` method.\n\nWith `$cursor()`, you get the peace of mind knowing that TypeScript will catch any errors in your cursor-based pagination. No more forgotten `orderBy` clauses, mismatched cursor objects, or manually-written `where` clauses.\n\nJust give it your desired sort order and the cursor object, and it will generate the correct `where` clause.\n\n```ts\n// Step 1: Choose your dialect\nimport 'drizzle-plus/pg/$cursor'\nimport 'drizzle-plus/mysql/$cursor'\nimport 'drizzle-plus/sqlite/$cursor'\n\n// Step 2: Use the `$cursor` method\nconst cursorParams = db.query.foo.$cursor({ id: 'asc' }, { id: 99 })\n// =\u003e {\n//   where: { id: { gt: 99 } },\n//   orderBy: { id: 'asc' },\n// }\n\n// Step 3: Add the cursor parameters to your query\nconst results = await db.query.foo.findMany({\n  ...cursorParams,\n  columns: {\n    id: true,\n    name: true,\n    age: true,\n  },\n})\n```\n\n- **Arguments:**\n  - The first argument is the “order by” clause. This is used to determine the comparison operator for each column, and it's returned with the generated `where` filter. **Property order is important.**\n  - The second argument is the user-provided cursor object. It can be `null` or `undefined` to indicate the start of the query.\n- **Returns:** The query parameters that you should include in your query. You can spread them into the options passed to `findMany()`, `findFirst()`, etc.\n\n#### Cursors with multiple columns\n\nIn addition to type safety and auto-completion, another nice thing about `$cursor()` is its support for multiple columns.\n\n```ts\nconst cursorParams = db.query.user.$cursor(\n  { name: 'asc', age: 'desc' },\n  { name: 'John', age: 20 }\n)\n\ncursorParams.where\n// =\u003e { name: { gte: 'John' }, age: { lt: 20 } }\n\ncursorParams.orderBy\n// =\u003e { name: 'asc', age: 'desc' }\n```\n\n\u003e [!NOTE]\n\u003e The order of keys in the first argument to `$cursor()` is **important**, as it helps in determining the comparison operator for each column. All except the last key allow rows with equal values (`gte` or `lte`), while the last key is always either `gt` (for ascending order) or `lt` (for descending order).\n\nAlso of note, as of June 28 2025, Drizzle doesn't yet provide control over treatment of `NULL` values when using the Relational Query Builder (RQB) API. While this library _could_ implement it ourselves (at least, for the `$cursor` method), we'd prefer to wait for Drizzle to provide a proper solution.\n\n### Materialized CTEs\n\nImport the `$withMaterialized` module to extend the query builder API with `$withMaterialized()` and `$withNotMaterialized()` methods.\n\nThese methods add `MATERIALIZED` and `NOT MATERIALIZED` keywords to the CTEs, respectively, just after the `AS` keyword. You can learn more about materialized CTEs in the [PostgreSQL docs](https://www.postgresql.org/docs/current/queries-with.html#QUERIES-WITH-CTE-MATERIALIZATION).\n\n\u003e [!WARNING]\n\u003e This feature is only available in Postgres.\n\n```ts\nimport 'drizzle-plus/pg/$withMaterialized'\n\n// Same API as db.$with()\nconst cte1 = db.$withMaterialized(alias).as(subquery)\nconst cte2 = db.$withNotMaterialized(alias).as(subquery)\n```\n\n### Universal SQL functions\n\nThese functions are available in all dialects, since they're part of the SQL standard.\n\n- **Syntax:**\n  - `caseWhen`\n  - `nest`\n  - `toSQL`\n- **SQL functions:**\n  - `abs`\n  - `ceil`\n  - `coalesce`\n  - `concatWithSeparator`\n  - `currentDate`\n  - `currentTime`\n  - `currentTimestamp`\n  - `floor`\n  - `length`\n  - `lower`\n  - `mod`\n  - `nullif`\n  - `power`\n  - `round`\n  - `sqrt`\n  - `substring`\n  - `trim`\n  - `upper`\n\nImport them from the `drizzle-plus` module:\n\n```ts\nimport { caseWhen } from 'drizzle-plus'\n```\n\n### Timestamps\n\nAny `drizzle-plus` function that returns a timestamp will return a `SQLTimestamp` object, which extends the `SQL` class. Call the `toDate()` method to instruct Drizzle to parse it into a `Date` object (which is only relevant if the timestamp is used in a `select` or `returning` clause).\n\n```ts\nimport { currentTimestamp } from 'drizzle-plus'\n\nconst now = currentTimestamp()\n// =\u003e SQLTimestamp\u003cstring\u003e\n\nnow.toDate()\n// =\u003e SQL\u003cDate\u003e\n```\n\n### Dialect-specific SQL functions\n\nThese functions have differences between dialects, whether it's the name, the function signature, or its TypeScript definition relies on dialect-specific types.\n\n- **Postgres:**\n  - `concat`\n  - `jsonAgg`\n  - `jsonBuildObject`\n  - `position`\n  - `uuidv7`\n  - `uuidExtractTimestamp`\n- **MySQL:**\n  - `concat`\n  - `jsonArrayAgg`\n  - `jsonObject`\n  - `position`\n- **SQLite:**\n  - `concat`\n  - `instr`\n  - `jsonGroupArray`\n  - `jsonObject`\n\n```ts\n// Postgres imports\nimport { jsonAgg } from 'drizzle-plus/pg'\n\n// MySQL imports\nimport { jsonArrayAgg } from 'drizzle-plus/mysql'\n\n// SQLite imports\nimport { jsonGroupArray } from 'drizzle-plus/sqlite'\n```\n\n### Utility functions\n\nThe `drizzle-plus` package also has some functions that don't produce SQL expressions, but exist for various use cases.\n\n- `mergeFindManyArgs`\n  _Combines two configs for a `findMany` query._\n- `mergeRelationsFilter`\n  _Combines two `where` filters for the same table._\n\n```ts\nimport { mergeFindManyArgs, mergeRelationsFilter } from 'drizzle-plus'\n```\n\n### Type-safe query definitions\n\nImport the `$findMany` module to extend the query builder API with a `$findMany` method.\n\nThe `$findMany()` method is used to define a query config for a `findMany` query in a type-safe way. If you pass two configs, it will merge them. This is useful for **Query Composition**™, which is a technique for building complex queries by composing simpler ones.\n\n\u003e [!NOTE]\n\u003e This method **does not** execute the query. It only returns a query config.\n\n```ts\n// Choose your dialect\nimport 'drizzle-plus/pg/$findMany'\nimport 'drizzle-plus/mysql/$findMany'\nimport 'drizzle-plus/sqlite/$findMany'\n\n// Now you can use the `$findMany` method\nconst query = db.query.foo.$findMany({\n  columns: {\n    id: true,\n  },\n})\n\n// The result is strongly-typed!\nquery.columns\n//    ^? { readonly id: true }\n\n// You can also pass two configs to merge them\nconst query2 = db.query.foo.$findMany(\n  {\n    columns: {\n      id: true,\n    },\n  },\n  {\n    columns: {\n      name: true,\n    },\n  }\n)\n// =\u003e {\n//   columns: {\n//     id: true,\n//     name: true,\n//   },\n// }\n```\n\nWhen you pass two configs to `$findMany()`, it passes them to `mergeFindManyArgs()` and returns the result. Here's how the merging actually works:\n\n- The `columns`, `with`, and `extras` properties are merged one level deep.\n- The `where` property is merged using `mergeRelationsFilter()`.\n- Remaining properties are merged via spread syntax (e.g. `orderBy` is replaced, not merged).\n\n## Types\n\nHere are some useful types that `drizzle-plus` provides:\n\n```ts\n// Universal types\nimport {\n  InferWhereFilter,\n  InferFindManyArgs,\n  InferFindFirstArgs,\n} from 'drizzle-plus/types'\n\n// Pass the query builder to the type\ntype WhereFilter = InferWhereFilter\u003ctypeof db.query.foo\u003e\ntype FindManyArgs = InferFindManyArgs\u003ctypeof db.query.foo\u003e\ntype FindFirstArgs = InferFindFirstArgs\u003ctypeof db.query.foo\u003e\n```\n\n## License\n\nMIT\n\n\u003c!-- Badges --\u003e\n\n[npm-version-src]: https://img.shields.io/npm/v/drizzle-plus?style=flat\u0026colorA=080f12\u0026colorB=1fa669\n[npm-version-href]: https://npmjs.com/package/drizzle-plus\n[npm-downloads-src]: https://img.shields.io/npm/dm/drizzle-plus?style=flat\u0026colorA=080f12\u0026colorB=1fa669\n[npm-downloads-href]: https://npmjs.com/package/drizzle-plus\n[license-src]: https://img.shields.io/github/license/alloc/drizzle-plus.svg?style=flat\u0026colorA=080f12\u0026colorB=1fa669\n[license-href]: https://github.com/alloc/drizzle-plus/blob/main/LICENSE\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falloc%2Fdrizzle-plus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falloc%2Fdrizzle-plus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falloc%2Fdrizzle-plus/lists"}