Drizzle: Offset is outside the bounds of the DataView
Make findFirst({ with }) return your PostGIS points again: why only nested relations crash, and three fixes tested against PostGIS 3.6.
TL;DR#
A Drizzle relational query that loads a related table with a PostGIS geometry column fails with RangeError: Offset is outside the bounds of the DataView, while selecting that table on its own works. The report is drizzle-orm issue #2526. Inside a with, Relational Queries v1 pack the related row into JSON, PostgreSQL turns the geometry into a GeoJSON object, and Drizzle's parseEWKB only understands the hex string. You have three ways out: load the relation with a join, swap the column for a custom type that reads both shapes, or move to Relational Queries v2 on the Drizzle v1 release candidate.
The error, as the issue reports it:
RangeError: Offset is outside the bounds of the DataView
at DataView.getUint32 (<anonymous>)
at parseEWKBThe issue was filed on drizzle-orm 0.31.2. The bug is still present on drizzle-orm 0.45.4, the release the npm registry tagged latest on 10 October 2026. That was checked with a reproduction on PostgreSQL 17.11 with PostGIS 3.6.4 and Node 22.23.3, using the schema from the issue: a locations table with a geometry('geo_longitude_latitude', { type: 'point', mode: 'xy', srid: 4326 }) column, and a tests table with a one relation to it. This is what the run printed, with the Drizzle logger switched on:
drizzle-orm 0.45.4
Query: select "id", "geo_longitude_latitude" from "locations" "locations" limit $1 -- params: [1]
locations.findFirst(): {"id":1,"geoLongitudeLatitude":{"x":-0.1276,"y":51.5072}}
Query: select "tests"."id", "tests"."location_id", "tests_location"."data" as "location" from "tests" "tests" left join lateral (select json_build_array("tests_location"."id", "tests_location"."geo_longitude_latitude") as "data" from (select * from "locations" "tests_location" where "tests_location"."id" = "tests"."location_id" limit $1) "tests_location") "tests_location" on true limit $2 -- params: [1, 1]
RangeError: Offset is outside the bounds of the DataView
at DataView.prototype.getUint32 (<anonymous>)
at parseEWKB (file:///app/node_modules/drizzle-orm/pg-core/columns/postgis_extension/utils.js:22:25)The first query reads locations directly and returns the point. The second query, tests.findFirst({ with: { location: true } }), crashes.
Why it happens#
The two queries above ask PostgreSQL for the same column in two different ways, and they get back two different values.
A plain select returns hex EWKB. PostGIS's text output for a geometry is a hex-encoded EWKB string, and the pg driver hands that string to Drizzle unchanged. Drizzle's geometry column, in the 0.45.4 build, does return parseEWKB(value) in mapFromDriverValue. Its parseEWKB helper converts the hex into bytes and reads the geometry type with DataView.getUint32.
A nested relation goes through JSON. For a with, Drizzle's PostgreSQL dialect builds a lateral subquery that selects json_build_array(...) of the related columns, which you can see in the logged query above. The PostgreSQL JSON functions documentation describes how to_json converts a value, and json_build_array converts each argument the same way: "if there is a cast from the SQL data type to json, the cast function will be used to perform the conversion". PostGIS provides such a cast for geometry. On the test database, the same column came back in both forms:
plain column value: "0101000020E6100000DA1B7C613255C0BFFE43FAEDEBC04940"
same column through to_json(): {"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-0.1276,51.5072]}The pg driver parses the JSON column into a JavaScript object. Drizzle's relational mapper then calls each column's mapFromDriverValue on the nested values, so parseEWKB receives a GeoJSON object instead of a string. The helper loops over hex.length, which an object does not have. It ends up with an empty byte array, and the first getUint32 call reads past the end of it. Calling the exported helper directly with each shape shows it:
parseEWKB(hex string): [-0.1276,51.5072]
parseEWKB(GeoJSON object): RangeError: Offset is outside the bounds of the DataViewThis explains the observation in pull request #2778, which proposed a fix: mapFromDriverValue sometimes "recieves an object with the data already parsed, and some other times it recieves a string". That pull request is still open and was never merged. The mode and srid options make no difference here. A geometry('geo_longitude_latitude', { type: 'point' }) column in the default tuple mode fails the same way:
RangeError: Offset is outside the bounds of the DataViewFix#
Pick one of these three. The first two keep you on the current stable release.
1. Load the related row with a join#
The core query builder selects the geometry column at the top level, so PostgreSQL sends hex EWKB and the built-in column parses it. Rewrite the relational query as a leftJoin:
import pg from 'pg';
import { eq } from 'drizzle-orm';
import { drizzle } from 'drizzle-orm/node-postgres';
import * as schema from './schema.mjs';
const { tests, locations } = schema;
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool, { schema, logger: true });
const rows = await db
.select({ id: tests.id, location: locations })
.from(tests)
.leftJoin(locations, eq(tests.locationId, locations.id))
.limit(1);This is the least invasive fix when only a few queries load geometry through a relation. You lose the nested result shape of with for that query, so map the rows yourself if you need it.
2. Replace the column with a custom type that reads both shapes#
Issue commenter shellbear posted a workaround: a customType that decodes a string with Drizzle's own parseEWKB and reads the coordinates out of a GeoJSON object. In 0.45.4 that helper is a public subpath export of the package. Here is a trimmed version of the workaround, tested as plain ESM:
import { customType } from 'drizzle-orm/pg-core';
import { parseEWKB } from 'drizzle-orm/pg-core/columns/postgis_extension/utils';
export const point = customType({
dataType() {
return 'geometry(point, 4326)';
},
toDriver(value) {
return `SRID=4326;POINT(${value.x} ${value.y})`;
},
fromDriver(value) {
if (typeof value === 'string') {
const [x, y] = parseEWKB(value);
return { x, y };
}
if (value && value.type === 'Point' && Array.isArray(value.coordinates)) {
const [x, y] = value.coordinates;
return { x, y };
}
throw new Error(`Unexpected geometry value: ${JSON.stringify(value)}`);
},
});Then use point('geo_longitude_latitude') in the table definition in place of geometry(...). One difference from the comment: toDriver writes EWKT with the SRID (SRID=4326;POINT(x y)) rather than point(x y), so an insert into a geometry(point, 4326) column carries the column's SRID. Both the read and the write path were run against the test database:
tests.findFirst({ with }): {"id":1,"locationId":1,"location":{"id":1,"geoLongitudeLatitude":{"x":-0.1276,"y":51.5072}}}
insert().returning(): {"id":2,"geoLongitudeLatitude":{"x":2.3522,"y":48.8566}}If your schema stores SRID 3857 or another projection, change the two 4326 literals to match the column. Keep the throw at the end of fromDriver: a silent null would hide the next shape mismatch.
3. Upgrade to the Drizzle v1 release candidate and Relational Queries v2#
The maintainer closed the issue on 3 January 2026 with this comment: "This issue has been fixed in the beta tag, which serves as the release candidate for Drizzle v1". The comment does not name a version. On 10 October 2026 the rc tag on npm pointed at drizzle-orm 1.0.0-rc.4, and the upgrade guide installs it with:
npm i drizzle-orm@rc
npm i -D drizzle-kit@rcThe fix only applies through the new relational API. On 1.0.0-rc.4 with node-postgres, db._query (the old relational API) was undefined in the reproduction, so you also move your relations to defineRelations, as the Relational Queries v1 to v2 migration guide describes:
import pg from 'pg';
import { defineRelations } from 'drizzle-orm';
import { pgTable, serial, integer, geometry } from 'drizzle-orm/pg-core';
import { drizzle } from 'drizzle-orm/node-postgres';
const locations = pgTable('locations', {
id: serial('id').primaryKey(),
geoLongitudeLatitude: geometry('geo_longitude_latitude', { type: 'point', mode: 'xy', srid: 4326 }),
});
const tests = pgTable('tests', {
id: serial('id').primaryKey(),
locationId: integer('location_id').references(() => locations.id).notNull(),
});
const relations = defineRelations({ locations, tests }, (r) => ({
tests: {
location: r.one.locations({ from: r.tests.locationId, to: r.locations.id }),
},
}));
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle({ client: pool, relations, logger: true });
const row = await db.query.tests.findFirst({ with: { location: true } });The logged SQL shows why this version works. Relational Queries v2 cast the geometry to text inside the nested subquery, so the JSON carries the hex EWKB string instead of a GeoJSON object:
Query: select "d0"."id" as "id", "d0"."location_id" as "locationId", "location"."r" as "location" from "tests" as "d0" left join lateral(select row_to_json("t".*) "r" from (select "d1"."id" as "id", "d1"."geo_longitude_latitude"::text as "geoLongitudeLatitude" from "locations" as "d1" where "d0"."location_id" = "d1"."id" limit $1) as "t") as "location" on true limit $2 -- params: [1, 1]
RQB v2 tests.findFirst({ with }): {"id":1,"locationId":1,"location":{"id":1,"geoLongitudeLatitude":{"x":-0.1276,"y":51.5072}}}This is a major-version move: the two upgrade guides also change where and orderBy into objects and restructure the drizzle-kit migrations folder. Plan it as an upgrade, not a hotfix.
Verify the fix#
Turn on the logger, drizzle({ client: pool, schema, logger: true }) on 0.45.4 or the same with relations on the release candidate, and run the query that used to fail:
- With the join (fix 1), the logged SQL has a plain
left join "locations"and nojson_build_array. The geometry arrives as{ x, y }. - With the custom type (fix 2), the logged SQL still contains
json_build_array, but the result now holds the coordinates instead of throwing. - On the release candidate (fix 3), the nested subquery selects
"geo_longitude_latitude"::text, as in the log above.
Also check one record against PostGIS itself. SELECT ST_AsText(geo_longitude_latitude) FROM locations WHERE id = 1; should print the same longitude and latitude your application now reads. In this reproduction, x came back as the first coordinate stored (the longitude) and y as the second.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Installing the release candidate can stop with ERESOLVE#
In the same Node 22 container (npm 10.9.9), the first npm install drizzle-orm@1.0.0-rc.4 pg@8 into an empty project stopped on an optional peer dependency:
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: undefined@undefined
npm error Found: effect@undefined
npm error node_modules/effect
npm error
npm error Could not resolve dependency:
npm error peerOptional effect@">=4.0.0-beta.83 || >=4.0.0" from drizzle-orm@1.0.0-rc.4The same install with --legacy-peer-deps completed, and every result shown in fix 3 above ran on that install. The npm configuration reference says that flag makes npm ignore peer dependencies altogether, so treat it as a stopgap rather than a fix. Here the Found line shows that no copy of the optional peer was installed at all; the log does not say more about why npm refused, and the Drizzle docs do not mention it. When an ERESOLVE does name an installed version that misses a peer range, this npm ERESOLVE walkthrough shows how to read it for a Firebase release candidate and pick versions that satisfy the range.
If you stay on the 0.x line with drizzle-kit 0.31.x and drizzle-kit migrate stops after printing its driver line, see the drizzle-kit migrate hang, which shows how to run the migrator directly to get the real error. When a geometry write fails inside a transaction, the Drizzle rollback guide explains why catching tx.rollback() without rethrowing can still commit the other writes. For this error, the decision is quick: a join if the query is isolated, the custom type if with is used throughout the codebase, and Relational Queries v2 once you are ready to move to Drizzle v1.
Further reading: Drizzle TableLikeHasEmptySelection error in generic helpers. Also see Drizzle: Object literal may only specify known properties. Also see Drizzle: Cannot destructure property 'resourceArn' fix. Also see Drizzle db.query drops pgSchema: relation does not exist.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Drizzle: Cannot destructure property 'resourceArn' fix
Get drizzle-kit studio and migrate talking to Aurora through the AWS Data API again, and know which error comes next on current releases.
Drizzle db.query drops pgSchema: relation does not exist
Get db.query.findMany() and findFirst() reading your custom Postgres schema again: which drizzle-orm versions drop the schema, why, and three tested ways out.
drizzle-kit migrate: relation "users" already exists
Get drizzle-kit migrate working on a database that drizzle-kit push created: why the first CREATE TABLE runs again, how to mark the old migrations as applied, and why IF NOT EXISTS only hides the problem.


