Quickstart
Declare, migrate, write, and query your first encrypted field with CipherStash and Prisma ORM 8.
This tutorial adds an encrypted, searchable email field to an existing Prisma ORM 8 release candidate project.
Prisma ORM 8 was formerly called Prisma Next. Its prerelease command remains prisma-next, while CipherStash initialization uses --prisma.
It is designed for a new table or empty column. To encrypt a populated column safely, use the Prisma deployment guide.
Before you start
You need:
- A Prisma ORM 8 release candidate project with
prisma-next.config.ts - A Postgres database and
DATABASE_URL - A CipherStash account; initialization signs you in
Initialize CipherStash
From the Prisma project, run:
npx stash init --prismastash init opens the CipherStash device login when necessary, creates the local developer profile, and installs compatible pinned versions of @cipherstash/stack, @cipherstash/stack-prisma, and the stash CLI.
It intentionally does not install EQL directly. The Prisma migration graph owns that step.
Agents can run the same setup. If no developer profile exists, the agent should start npx stash auth login --json --region <region> --prisma, present the returned verification URL, and wait for you to complete device login. It can then run npx stash init --prisma --region <region>.
Initialization records project context and can install the CipherStash Prisma, authentication, indexing, deployment, and CLI skills for supported coding agents.
Declare an encrypted field
Add the CipherStash type to your schema:
model User {
id String @id
email cipherstash.TextSearch()
}TextSearch() supports encrypted equality, range, ordering, and token matching. If you only need equality, prefer TextEq(). See Schema and queries for every column type.
Register the extension pack
Add CipherStash to your existing Prisma configuration:
import cipherstash from "@cipherstash/stack-prisma/control"
import { defineConfig } from "@prisma-next/cli/config-types"
import { prismaContract } from "@prisma-next/sql-contract-psl/provider"
import postgresPack from "@prisma-next/target-postgres/pack"
import { postgresCreateNamespace } from "@prisma-next/target-postgres/types"
export default defineConfig({
// Keep your existing family, target, driver, adapter, and database settings.
extensionPacks: [cipherstash],
contract: prismaContract("./prisma/schema.prisma", {
output: "src/prisma/contract.json",
target: postgresPack,
createNamespace: postgresCreateNamespace,
}),
migrations: { dir: "migrations" },
})If you already use extension packs, include cipherstash in the existing array. createNamespace is required by Prisma's 0.15 and later prereleases.
Emit and migrate
Generate the contract, plan the migration, and apply it:
npx prisma-next contract emit
npx prisma-next migration plan --name add-encrypted-user
npx prisma-next migrateThe final command installs EQL v3 and applies your application migration in one graph. Do not run stash eql install for this integration.
Connect the encrypted runtime
Create the database module from the emitted contract:
import "dotenv/config"
import { cipherstashFromStack } from "@cipherstash/stack-prisma/v3"
import postgres from "@prisma-next/postgres/runtime"
import type { Contract } from "./prisma/contract.d"
import contractJson from "./prisma/contract.json" with { type: "json" }
const cipherstash = await cipherstashFromStack({ contractJson })
export const db = postgres<Contract>({
contractJson,
extensions: cipherstash.extensions,
middleware: cipherstash.middleware,
})cipherstashFromStack uses the local developer profile automatically. For CI or a deployed application, see Credentials for the CLI and Dashboard options.
Write and query encrypted data
Wrap plaintext values at the write boundary, then use the generated eql* operators:
import {
decryptAll,
EncryptedString,
} from "@cipherstash/stack-prisma/runtime"
import { db } from "./db"
const runtime = await db.connect({ url: process.env.DATABASE_URL! })
try {
await db.orm.public.User.create({
id: "user-0",
email: EncryptedString.from("[email protected]"),
})
const rows = await db.orm.public.User
.where((user) => user.email.eqlEq("[email protected]"))
.all()
await decryptAll(rows)
console.log(await rows[0]?.email.decrypt())
// [email protected]
} finally {
await runtime.close()
}The write value and query operand are encrypted before reaching Postgres. decryptAll batches result decryption; .decrypt() returns the plaintext value.
Next steps
- Choose other encrypted field types and operators in Schema and queries.
- Add functional indexes using EQL indexes; keep their raw SQL operations in the Prisma migration graph.
- Follow the Prisma deployment guide before changing a populated production column.
- Continue with Prisma Postgres or Prisma Compute if you use Prisma's hosted platform.