# CipherStash + Prisma Next: Data Level Access Control, declared in your contract

*Published on 2026-05-21*

*By CJ Brewer, Will Madden*

Add searchable field-level encryption to a Prisma Next app the same way you'd add any other field — declared once in your contract, evolved the same way.

## Content

The CipherStash Stack makes encryption *in use* easy — so you can focus on building your application. Combined with Prisma Next's centralized data contract, **adding searchable encryption to a field is as easy as adding the field.**

Prisma Next is a [ground-up rewrite of the Prisma ORM](https://www.prisma.io/blog/the-next-evolution-of-prisma-orm). You write a data contract — your models and their types — and a deterministic framework keeps your database in sync with it as it changes. With the CipherStash extension, your per-field encryption policy is part of that contract; the framework maintains it alongside the rest of your schema.

## Adding searchable encryption to a field

This is what a data contract looks like in Prisma Next — the single source of truth for everything in your database:

```prisma
// contract.prisma
model User {
  id    String @id
  email cipherstash.EncryptedString() // <- You're adding this field
}
```

You add `email` to your contract as a `cipherstash.EncryptedString()` column. The value is encrypted before it leaves your application; once encrypted, it stays encrypted while Postgres filters, sorts, and joins on it — what CipherStash calls *[encryption in use](https://cipherstash.com/docs/stack/reference/planning-guide#understanding-encryption-types)*. The Prisma Next framework will manage adding the column to the table and configuring CipherStash's encrypted indexes for you.

All you need to do is install the [CipherStash extension from NPM](https://www.npmjs.com/package/@cipherstash/prisma-next) and register it in your Prisma Next config:

```ts
// prisma-next.config.ts
import cipherstash from '@cipherstash/prisma-next/control';

export default defineConfig({
  extensionPacks: [cipherstash],
});
```

Then plan a new migration with `pnpm migration:plan`. The migration planner reads your contract, sees the new `email` field and emits this [TypeScript migration](https://www.prisma.io/blog/typescript-migrations-in-prisma-next) for you:

```ts
// 20260508T1721_migration/migration.ts
import { cipherstashAddSearchConfig } from '@cipherstash/prisma-next/migration';
import { addColumn, Migration } from '@prisma-next/target-postgres/migration';

export default class M extends Migration {
  override get operations() {
    return [
      addColumn('public', 'User', {
        name: 'email', typeSql: 'eql_v2_encrypted', defaultSql: '', nullable: false,
      }),
      cipherstashAddSearchConfig({ table: 'User', column: 'email', index: 'unique' }),
      cipherstashAddSearchConfig({ table: 'User', column: 'email', index: 'match' }),
    ];
  }
}
```

The migration adds the encrypted `email` column to your `User` table and registers two CipherStash-specific indexes: an encrypted equality index and an encrypted free-text-search index. Apply it with `pnpm migration:apply`. From here on, every change to your contract follows the same loop: edit, plan, apply.

You can now write encrypted values into the field:

```ts
import { EncryptedString } from '@cipherstash/prisma-next/runtime';

await db.orm.User.create({
  id: 'user-0',
  email: EncryptedString.from('alice@example.com'),
});
```

And you can search by email without ever decrypting the values — Postgres operates on the ciphertext via [Searchable Encrypted Metadata](https://cipherstash.com/docs/stack/reference/cipher-cell) stored with each value:

```ts
const rows = await db.orm.User
  // exact equality
  .where(u => u.email.cipherstashEq('alice@example.com'))
  .all();

const matches = await db.orm.User
  // free text search
  .where(u => u.email.cipherstashIlike('@example.com'))
  .all();
```

Behind the scenes, Prisma Next connects to ZeroKMS — CipherStash's key-management service — to derive a unique encryption key for each value. The `email` column is never stored or queried in plaintext, not at rest, not in transit, not during search. And no data ever leaves your database. All you had to do was declare the field as an `EncryptedString` in your data contract.

When you actually need plaintext, decrypt explicitly. For a result set, bulk-decrypt first to coalesce the SDK round-trips:

```ts
import { decryptAll } from '@cipherstash/prisma-next/runtime';

await decryptAll(rows); // Bulk decrypt the result set.
const plaintext = await rows[0].email.decrypt(); // Then explicitly unwrap to reveal the plaintext.
```

That `decrypt()` call is the only path to plaintext. An `EncryptedString` serializes to an opaque placeholder when you `JSON.stringify` it; `console.log` redacts the value; embed it in an error message via concatenation and you get the default object representation. Plaintext can't accidentally end up in a JSON response, a log line, or an LLM prompt without someone deciding it should — **every decryption is a line of code someone wrote on purpose**.

One more thing worth surfacing — the way the migrations look on disk:

```
migrations/
├── 20260508T1721_migration/                 # your application migration
│   ├── migration.ts                         # addColumn + cipherstashAddSearchConfig × 2
│   └── ...
└── cipherstash/                             # the cipherstash extension's migration history
    └── 20260601T0000_install_eql_bundle/
        └── ...
```

Two migration histories, side-by-side. Your application's on top — the `addColumn` and the per-column setup for `email`. The cipherstash extension's underneath — its own migration history which sets up CipherStash's database tables and shared code.

The framework tracks application schema and extension schema as a single coherent history, which means upgrading the cipherstash extension later is just another migration — bump the version, plan, apply.

Adding searchable encryption to a field — as easy as adding the field.

## Your encryption policy, in code

```prisma
// contract.prisma
model User {
  id    String @id
  email cipherstash.EncryptedString()
}
```

Your contract is where your database schema is declared. With the CipherStash extension, it's also your Data Level Access Control (DLAC) policy — the declarative half. You can tell at a glance which fields are protected and which aren't. The active half lives next to it, in the explicit `decrypt()` calls your application code has to write on purpose.

Both halves are reviewable. Both are in version control. Both evolve through normal code review.

Adding an encrypted field, changing its search modes, removing encryption from a column — same workflow as any other schema change. Edit the contract; the planner emits the migration; you review the diff; CI/CD applies it. Your encryption policy isn't a document that lives in someone's head, or a runbook that drifts from the database — it's the contract, evolved the same way the rest of your application evolves.

Which means PR reviewers can see exactly when an encrypted field is added, or when encryption is *removed* from one (alarm bells ringing). Compliance and security teams have a single artifact to audit instead of chasing answers across migration files and runtime config.

Declared in your contract, enforced through explicit `decrypt()`, evolved through your normal CI/CD pipeline. No separate document, no tribal knowledge, no surprise.

## Safe with agents

The same property that makes your DLAC policy reviewable for humans is what makes it safe to maintain with an agent in the loop. You probably wouldn't trust an agent to write a raw migration, and you definitely wouldn't trust one to improvise encryption — and you don't have to. The agent edits the contract. The planner emits the migration. The migration is a reviewable diff that goes through your normal CI/CD before it touches the database.

Prisma Next guards every step: pre-checks and post-checks on every operation, migration history hashed against tampering, history verified against the live database before each apply.

Let your agent ship features with confidence. Whatever it changes about your encryption ends up in code on disk, reviewed the same way as a change you'd make yourself.

## Try it out

```sh
npx prisma-next init
npx stash auth login
npm add @cipherstash/prisma-next
```

Prisma Next is the easiest way to get started with CipherStash. Run the command above to scaffold Prisma Next config, or clone the [example app](https://github.com/cipherstash/stack/tree/main/examples/prisma). Try it against any Postgres, including [Prisma Postgres](https://www.prisma.io/postgres) or a local instance via `npx prisma dev`.

Tell us what you build — drop into the [CipherStash Discord](https://cipherstash.com/discord), or [`#prisma-next`](https://pris.ly/discord) on the Prisma Discord. We'd appreciate any feedback, good or bad. For the framework-side story of why integrations like this are now possible, see [the Prisma team's call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors).

On day 100, when a new joiner picks up your contract — or an agent acting on it — they should be able to add an encrypted column, change a search mode, or evolve the encryption policy with confidence. Because the contract is the policy and the planner is the way it gets applied, they can.

Prisma Next is the next major iteration of the Prisma ORM, currently in development; it'll become Prisma 8 when it's ready for general use. Today's recommendation for production Postgres apps remains [Prisma 7](https://www.prisma.io/orm). The CipherStash extension is real and runnable today against Prisma Next as it develops; this post is about the experience that exists right now.

## Encryption in use, wherever your data lives

The Prisma Next extension is one entry point into [CipherStash Stack](https://cipherstash.com/docs/stack). The [Encryption SDK](https://cipherstash.com/docs/stack/cipherstash/encryption) (`@cipherstash/stack`) brings the same encrypt-in-use to any TypeScript app — Drizzle, Supabase, DynamoDB, or plain `pg` — with the same indexes and query operators you've seen here. And [CipherStash Proxy](https://cipherstash.com/docs/stack/cipherstash/proxy) takes a different route again: sit it in front of your existing Postgres and your `INSERT` / `SELECT` / `WHERE` queries keep working unchanged, with encryption applied transparently to the columns you nominate. Same ZeroKMS underneath, same CipherCell on disk, same guarantee that every decryption is a deliberate line of code. [Sign up for free](https://dashboard.cipherstash.com/sign-up) and pick the integration that fits your team.

## Related blog posts

- [Introducing @cipherstash/stack](https://cipherstash.com/blog/introducing-cipherstash-stack.md) — Building blocks for Data Level Access Control in TypeScript

