What the extension provides
- Six encrypted column types:
EncryptedString,EncryptedDouble,EncryptedBigInt,EncryptedDate,EncryptedBoolean,EncryptedJson. Each maps a plain JS type to an EQL ciphertext stored in a singleeql_v2_encryptedPostgres column. - Per-codec search-mode flags (
equality,freeTextSearch,orderAndRange,searchableJson) that drive which EQL search-config indices are emitted at migration time. All flags default totrue. - 13 predicate operators surfaced as column methods (e.g.
m.email.cipherstashEq(...),m.salary.cipherstashGt(...)). - 4 free-standing helpers for sort and JSON projection:
cipherstashAsc,cipherstashDesc,cipherstashJsonbPathQueryFirst,cipherstashJsonbGet. bulkEncryptMiddleware(sdk)— coalesces cipherstash parameters across rows into onebulkEncryptSDK round-trip per(table, column)group before wire encoding.decryptAll(rows)— reads a result set and coalesces every cipherstash envelope into onebulkDecryptSDK round-trip per(table, column)group.- EQL contract space — the extension ships the
eql_v2_configurationtable,eql_v2_encryptedcomposite types,eql_v2domains, and the EQL bundle SQL as a Prisma Next contract space so the framework manages it alongside your own schema.
The six encrypted column types
Each enabled flag maps to one EQL search-config index. The migration lifecycle hook emits
eql_v2.add_search_config(...) operations at field-added events and eql_v2.remove_search_config(...) operations when a flag is turned off between contract versions.
Setup
1
Install the package
2
Register the extension in prisma-next.config.ts
prisma-next.config.ts
3
Declare encrypted columns in your schema
true. To opt out of a specific index, set the flag to false explicitly — for example, encryptedString({ equality: false, freeTextSearch: false, orderAndRange: false }) for storage-only encryption with no queryable indices.4
Emit the contract and apply migrations
db init applies the bundled EQL baseline migration, which installs the eql_v2_configuration table, eql_v2_encrypted type, eql_v2 domains, and the EQL bundle SQL before your application migrations run.5
Wire the extension and middleware into the runtime
src/prisma/db.ts
Writing and reading encrypted data
Operator reference
Predicate operators (column methods)
Predicate operators return boolean and are available in.where() clauses. Each operator requires a specific search-mode flag to be enabled on the column codec.
The framework’s built-in operators (
eq, gt, ilike, etc.) are not available on cipherstash columns and will produce a compile-time error. EQL ciphertexts contain randomized nonces — SQL = or < against raw eql_v2_encrypted values always returns false. The cipherstash-namespaced operators route through the correct EQL search-config indices.Free-standing helpers (non-predicate)
These helpers take a column expression as input and return a sort item or a typed expression — they are not assignable to the column-method predicate surface.
The JSON helpers return the same type as their input column, so they chain into follow-on JSON helpers or predicates.
EQL search-config index types
Each search-mode flag maps to one EQL index family:Choosing the right codec
Subpath exports
The
./control, ./runtime, and ./middleware planes are tree-shakable — a runtime consumer never pulls the EQL bundle SQL or codec lifecycle hooks, and a control-plane consumer never pulls envelope classes, the SDK interface, or the middleware.
Security model
- Plaintext lifetime — the write-side envelope retains its plaintext slot post-encrypt. Treat envelope objects as plaintext-equivalent for the lifetime of the variable.
- Ciphertext routing — every read-side envelope carries the
(table, column)it was decoded from.decryptanddecryptAllroute bulk SDK calls by that key so the SDK can select the correct key material per column. - Plaintext redaction —
toJSON,toString,valueOf,Symbol.toPrimitive, andSymbol.for('nodejs.util.inspect.custom')all return[REDACTED]. Accidentalconsole.log,JSON.stringify, template-literal interpolation, andutil.inspectcannot leak plaintext. Explicit access is viaenvelope.expose(). - Cancellation — every internal SDK call accepts an
AbortSignal.
Known limitations
The following are explicitly deferred from the current implementation.
cipherstashJsonbPathExistsagainst the live EQL bundle — the framework currently binds the JSONpath as a plain textParamRefrather than the hashed selector the EQL bundle expects. Predicate queries return zero rows against rows that should match. The non-predicate helpers (cipherstashJsonbPathQueryFirst,cipherstashJsonbGet) work correctly. Workaround: project all rows with the SELECT-expression helpers and filter client-side.EncryptedBigIntcapped atNumber.MAX_SAFE_INTEGER— the CipherStash SDK’sJsPlaintextunion does not includebigint. Values beyondNumber.MAX_SAFE_INTEGERcannot be encrypted and will throw on overflow.- No encrypted timestamp / datetime — CipherStash offers only calendar-date encryption (
EncryptedDate). Encrypted timestamp support is deferred. - No re-encryption migration primitive — adopting cipherstash for an existing populated column requires re-encrypting row data. The codec lifecycle hook emits the correct search-config DDL but does not touch row data. Work around it with a hand-authored
dataTransformmigration.