# CipherStash Documentation
CipherStash is Data Level Access Control for Postgres. Encrypt fields, query ciphertext, bind keys to identities, and audit every access. Zero-knowledge by design.
```bash
npx stash init
```
One command. Device-based authentication, no environment variables for local dev. See the [Getting started](/stack/quickstart) guide.
Choose your path [#choose-your-path]
Two integration paths. Same key hierarchy, same encryption, same audit trail.
| | TypeScript SDK | CipherStash Proxy |
| ---------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **How it works** | Encryption SDK and CLI available as an NPM package | Postgres Encryption - Postgres wire protocol Proxy built on top of the Encryption SDK to transparently encrypt data in Postgres |
| **Best for** | Teams moving quickly and building TypeScript apps | More complex app architectures or non TypeScript applications using Postgres |
| **Language** | TypeScript / JavaScript (Node.js, Deno, Bun) | Any language (connects via PostgreSQL wire protocol) |
| **Setup** | `npx stash init` | Docker container or binary |
Most teams start with the **SDK** for the best developer experience.
Get started [#get-started]
Products [#products]
Operations [#operations]
How it fits together [#how-it-fits-together]
Everything builds on [ZeroKMS](/stack/cipherstash/kms) and its core primitive, the [Keyset](/stack/cipherstash/kms/keysets). A keyset is the unit of isolation.
* **[Encryption](/stack/cipherstash/encryption)** uses keysets for [tenant isolation](/stack/cipherstash/encryption/configuration#keysets). One keyset per customer. Provable cryptographic separation.
* **Secrets (coming soon)** uses keysets for environment isolation. Production, staging, and development secrets can never cross boundaries.
* **[Proxy](/stack/cipherstash/proxy)** encrypts data transparently via PostgreSQL, backed by the same ZeroKMS key hierarchy.
Understand the architecture [#understand-the-architecture]
# Quickstart
CipherStash encrypts your data at the field level. Every value gets its own key, bound to an identity. A breach, a compromised agent, a curious insider — they all see ciphertext with no key.
Here's how to set it up. Works with any Postgres — Supabase, Neon, RDS, a Docker container, whatever.
Set up your project [#set-up-your-project]
The CLI handles install, database setup, and encryption scaffolding as three explicit save-points.
Step 1: Initialize [#step-1-initialize]
```bash
npx stash init
```
This opens a browser for device-based authentication. No shared secrets, no environment variables for local dev. Init:
1. Authenticates your device and connects to your workspace
2. Resolves your database connection and installs the EQL extension
3. Scaffolds an encryption client at `./src/encryption/index.ts`
4. Installs `@cipherstash/stack` and `stash` if not already present
5. Writes `.cipherstash/context.json` with detected facts about your project
When init finishes, it prompts: `Continue to stash plan now?` (default-yes).
Step 2: Draft a plan [#step-2-draft-a-plan]
```bash
npx stash plan
```
Hands off to a coding agent (Claude Code, Codex, or others) which reads your project and writes `.cipherstash/plan.md`. The plan lists the tables and columns to encrypt and whether each column is new or needs migration from plaintext. Review the plan before proceeding.
Step 3: Implement [#step-3-implement]
```bash
npx stash impl
```
Reads the plan, shows a summary panel, asks you to confirm, then dispatches to an agent to make the changes.
Define your encryption schema [#define-your-encryption-schema]
After `stash plan` produces the plan, the agent edits `./src/encryption/index.ts` based on your project. The file defines which columns to encrypt:
```typescript filename="src/encryption/index.ts"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack"
export const users = encryptedTable("users", {
email: encryptedColumn("email")
.equality() // WHERE email = ?
.freeTextSearch() // WHERE email LIKE '%alice%'
})
```
Each column gets its own encryption key. The index methods (`.equality()`, `.freeTextSearch()`, `.orderAndRange()`) define what queries work on the encrypted data.
Encrypt a value [#encrypt-a-value]
```typescript filename="src/encrypt.ts"
import { Encryption } from "@cipherstash/stack"
import { users } from "./encryption/schema"
const enc = await Encryption({ schemas: [users] })
const encrypted = await enc.encrypt("alice@example.com", {
column: users.email,
table: users,
})
// encrypted.data is ciphertext — store it in your database
```
Store it [#store-it]
Use any database client, ORM, or raw SQL:
```sql
INSERT INTO users (email) VALUES ($1)
-- $1 is encrypted.data (ciphertext)
```
Query encrypted data [#query-encrypted-data]
Encrypt your search term, then query as usual:
```typescript filename="src/query.ts"
const query = await enc.encryptQuery("alice@example.com", {
column: users.email,
table: users,
})
// Use query.data in a WHERE clause — Postgres searches ciphertext
```
```sql
SELECT * FROM users WHERE cs_match_v1(email) = $1
-- $1 is query.data
```
The database never sees plaintext. The query runs over encrypted indexes.
Decrypt [#decrypt]
```typescript filename="src/decrypt.ts"
const result = await enc.decrypt(row.email, {
column: users.email,
table: users,
})
console.log(result.data) // "alice@example.com"
```
What you just built [#what-you-just-built]
You encrypted a field, stored it, queried it without decrypting, and got the plaintext back. Here's what happened under the hood:
* **ZeroKMS** derived a unique encryption key for the value. The key was created on your device and never stored anywhere.
* **Your client key** — the unit of identity — is the only thing that can decrypt it. Another client key, a stolen credential, an AI agent running on application credentials — none of them have the key.
* **Your keyset** — the unit of isolation — defines the cryptographic boundary. Data encrypted under one keyset cannot be decrypted with another. Use keysets to isolate tenants, environments, or services.
* **The encrypted index** lets Postgres evaluate queries without decrypting. The index terms are themselves encrypted — they reveal nothing about the plaintext.
This is Data Level Access Control. The rules aren't configured in a policy file. They're enforced in the cryptography.
Next steps [#next-steps]
**Using Drizzle ORM?** The [Drizzle integration](/stack/cipherstash/encryption/drizzle) adds encrypted column types and query operators so your Drizzle code looks normal.
**Using the Supabase JS SDK?** The [Supabase integration](/stack/cipherstash/encryption/supabase) wraps the Supabase client with automatic encryption.
**Secrets (coming soon).** End-to-end encrypted config without .env files. [Join the waitlist](https://cipherstash.com/stack/secrets).
**Ready to deploy?** [Going to production](/stack/deploy/going-to-production) covers the switch from device auth to environment variables.
**Manage your workspace.** [Open the dashboard](https://dashboard.cipherstash.com/workspaces) to view keysets, create client keys, manage access keys, and check usage.
# CipherStash
CipherStash is one product with four capabilities:
* **[Encryption](/stack/cipherstash/encryption)**: Searchable field-level encryption. The core primitive. Every value encrypted with its own unique key. Range queries, exact match, free-text search, and JSON queries over ciphertext.
* **[Secrets (coming soon)](https://cipherstash.com/stack/secrets)**: Secrets without the .env. End-to-end encrypted config with cryptographically isolated environments.
* **[Proxy](/stack/cipherstash/proxy)**: Transparent encryption for existing PostgreSQL databases. Zero code changes. Also useful as a DevOps tool for inspecting encrypted data.
* **[ZeroKMS](/stack/cipherstash/kms)**: The key management layer. Unique key per value, derived on demand, never stored. Powers everything else.
* **[CLI](/stack/cipherstash/cli)**: CLI tools for managing EQL installation, encryption schemas, and database setup.
Start with the [Quickstart](/stack/quickstart) to encrypt your first fields in 15 minutes.
# PostgreSQL
PostgreSQL [#postgresql]
CipherStash provides field-level encryption for PostgreSQL databases. You choose the integration depth. All paths use the same key management ([ZeroKMS](/stack/cipherstash/kms)), the same searchable encryption primitives ([EQL](/stack/reference/eql-guide)), and produce the same encrypted storage format.
Choose your integration [#choose-your-integration]
| If you want... | Use |
| ----------------------------------------------------------------- | -------------------------------------------------------- |
| Zero application code changes, transparent encryption at the wire | [Proxy](/stack/cipherstash/proxy) |
| Application-level control, ORM-agnostic | [Encryption SDK](/stack/cipherstash/encryption) |
| First-class Drizzle ORM integration | [Drizzle adapter](/stack/cipherstash/encryption/drizzle) |
| First-class Supabase JS SDK integration | [Supabase wrapper](/stack/cipherstash/supabase) |
Proxy [#proxy]
[CipherStash Proxy](/stack/cipherstash/proxy) sits between your application and PostgreSQL. Your application connects to the proxy with a standard PostgreSQL connection string. The proxy encrypts and decrypts fields transparently using a policy file. No application code changes are required.
Best for: existing applications you cannot modify, or teams who want encryption with zero SDK dependencies.
Encryption SDK [#encryption-sdk]
The [Encryption SDK](/stack/cipherstash/encryption) is a Node.js library that encrypts values before they reach the database and decrypts them on the way back. You call `encrypt` and `decrypt` explicitly in your data layer. The SDK is ORM-agnostic and works with any PostgreSQL client.
Best for: new applications or teams who want explicit control over which fields are encrypted and when.
Drizzle adapter [#drizzle-adapter]
The [Drizzle adapter](/stack/cipherstash/encryption/drizzle) wraps the Encryption SDK with Drizzle-native types and operators. Define encrypted columns with `encryptedType` in your Drizzle schema. Query them with `encryptionOps.eq`, `encryptionOps.ilike`, and other typed operators that mirror the standard Drizzle API.
Best for: teams already using Drizzle ORM who want type-safe encrypted queries without writing raw SQL.
Supabase wrapper [#supabase-wrapper]
The [Supabase wrapper](/stack/cipherstash/supabase) wraps the Supabase JS client with transparent encryption on mutations and decryption on selects. Queries read identically to standard Supabase queries. It uses the Encryption SDK internally.
Best for: teams using the Supabase JS client who want minimal query-layer changes.
What is EQL? [#what-is-eql]
EQL (Encrypt Query Language) is the PostgreSQL extension that makes encrypted queries possible. It provides the `eql_v2_encrypted` column type and the functions that index and compare encrypted values without decrypting them. Every integration path above relies on EQL.
See [Encrypt Query Language](/stack/reference/eql-guide) for the full reference.
Index setup [#index-setup]
Encrypted columns require indexes for fast queries. Index syntax differs between self-hosted PostgreSQL (full EQL with operator classes) and managed databases like Supabase (no superuser, no operator families). See [Setting up indexes](/stack/cipherstash/encryption/indexes) for the complete setup guide, including the right index form for each deployment.
How these compose [#how-these-compose]
Proxy and the Encryption SDK are not mutually exclusive. A single PostgreSQL database can serve both:
* One application uses the Proxy (legacy service with no code changes)
* Another application uses the Encryption SDK directly (new service with full control)
Both write to the same `eql_v2_encrypted` columns and use the same keysets. Data encrypted by one path is readable by the other.
The Drizzle and Supabase adapters sit on top of the Encryption SDK. They are not separate encryption implementations. Swapping between the raw SDK and an adapter does not change how data is stored or which keys are used.
```
Your application
│
├── Drizzle adapter ──┐
├── Supabase wrapper ─┤
└── Raw SDK ──────────┤
▼
Encryption SDK
│
▼
ZeroKMS
│
▼
PostgreSQL
(eql_v2_encrypted)
```
The Proxy is a separate path that does not use the SDK, but it writes the same encrypted format and reads from the same keysets.
Next steps [#next-steps]
* [Quickstart](/stack/quickstart): Encrypt your first fields in 15 minutes
* [Setting up indexes](/stack/cipherstash/encryption/indexes): Create PostgreSQL indexes for encrypted queries
* [Searchable encryption queries](/stack/cipherstash/encryption/queries): Equality, match, and range query patterns
* [Planning guide](/stack/reference/planning-guide): Architecture decisions and integration path selection
# Supabase
Field-level encryption that works with your Supabase project out of the box.
Connect from the dashboard [#connect-from-the-dashboard]
You can connect Supabase from the [CipherStash Dashboard](https://dashboard.cipherstash.com) instead of starting entirely from the CLI.
Open workspace integrations [#open-workspace-integrations]
In your workspace, go to **Settings → Integrations** and click **Connect Supabase**.
Authorize Supabase OAuth [#authorize-supabase-oauth]
Approve access for the CipherStash OAuth app. Tokens are stored encrypted on the workspace.
Use the setup hub [#use-the-setup-hub]
Select a Supabase project, verify EQL and OIDC readiness, configure Supabase as an OIDC provider with one click, and copy Stack onboarding commands plus a `.env.local` snippet tailored to that project.
You can also install CipherStash from the **Supabase Marketplace** — the dashboard supports both simple and signed redirect install flows.
Full reference: [Supabase dashboard integration](/stack/reference/dashboard-supabase-integration)
How this works [#how-this-works]
What is EQL? [#what-is-eql]
EQL (Encrypted Query Language) is a Postgres extension CipherStash installs into your database. It provides the `eql_v2_encrypted` column type and the internal functions that make encrypted search possible. You don't write EQL directly. The SDK handles it.
Why your columns must be eql_v2_encrypted [#why-your-columns-must-be-eql_v2_encrypted]
Encrypted values aren't strings or plain JSONB. They're structured ciphertext objects that hold the ciphertext plus optional search indexes. Postgres needs the column type so the SDK and Postgres agree on which functions handle inserts and queries.
What the CLI installs on Supabase [#what-the-cli-installs-on-supabase]
`db install --supabase` uses a Supabase-compatible EQL variant. It omits `CREATE OPERATOR FAMILY` (which requires superuser), and grants `USAGE`, table, routine, and sequence permissions on the `eql_v2` schema to `anon`, `authenticated`, and `service_role`.
Packages [#packages]
CipherStash splits its functionality across two packages: a runtime SDK that your application imports, and a CLI for setup and schema management.
| Package | Role | Install as |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- |
| `@cipherstash/stack` | Runtime encryption and decryption. Your application imports this to encrypt values before they're written to Supabase and decrypt them on the way back. | dependency |
| `stash` | Setup and schema management. Installs EQL into your database, scaffolds your encryption client, and generates migrations when your encryption schema changes. Comparable to Prisma CLI or Drizzle Kit. | devDependency |
Setup [#setup]
Install the packages [#install-the-packages]
Install the runtime SDK as a dependency and the CLI as a dev dependency.
```bash
npm install @cipherstash/stack
npm install -D stash
```
Run init --supabase [#run-init---supabase]
Init runs nearly silently, with prompts only when it can't make a sensible default choice:
* Authenticates you with CipherStash via your browser (only when you aren't already logged in).
* Resolves your database connection via `DATABASE_URL`.
* Generates an encryption client at `./src/encryption/index.ts`. Only prompts you if a file already exists at that path.
* Installs `@cipherstash/stack` and `stash` if either is missing — one combined prompt, skipped entirely when both are already present.
* Installs EQL into your database.
The `--supabase` flag tailors the next-steps output to Supabase users.
```bash
npx stash init --supabase
```
Control how EQL is installed (optional) [#control-how-eql-is-installed-optional]
`stash init` installs EQL automatically. If you need to control the install method — for example, to write a Supabase migration file instead of pushing directly — run `stash db install` with explicit flags.
The CLI prompts you to choose how EQL is installed. If a `supabase/migrations/` directory is detected, the migration-file option is pre-selected.
As a migration (recommended) [#as-a-migration-recommended]
Pass `--migration` to write the EQL SQL into a Supabase migration file, or choose "Create a Supabase migration file" at the prompt.
```bash
npx stash db install --supabase --migration
```
The CLI writes the EQL SQL to:
```
supabase/migrations/00000000000000_cipherstash_eql.sql
```
The all-zero timestamp prefix ensures this migration runs before any user migrations that reference `eql_v2_encrypted`. Run `supabase db reset` or `supabase migration up` to apply it.
To write the migration file to a different directory, use `--migrations-dir`:
```bash
npx stash db install --supabase --migration --migrations-dir ./db/migrations
```
`--migration`, `--direct`, and `--migrations-dir` all require `--supabase` to be passed explicitly. Passing them without `--supabase` will error.
Direct push [#direct-push]
Pass `--direct` to push EQL directly to the database without creating a migration file.
```bash
npx stash db install --supabase --direct
```
Direct-push installs do not survive `supabase db reset`. The reset command drops the database and reruns only files in `supabase/migrations/`. EQL installed directly is not in migrations and will be wiped. Use the migration-file path for projects that use `supabase db reset`. See [Supabase db reset](/stack/cipherstash/cli/troubleshooting#supabase-db-reset).
If you hit issues with `supabase db reset` wiping EQL, see [Supabase db reset removes EQL](/stack/cipherstash/cli/troubleshooting#supabase-db-reset).
Database schema [#database-schema]
Encrypted columns must use the `eql_v2_encrypted` type. Define them in your Supabase SQL editor or a migration file.
```sql filename="supabase/migrations/20240101000001_create_users.sql"
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email eql_v2_encrypted NOT NULL,
name eql_v2_encrypted NOT NULL
);
```
Choose your integration [#choose-your-integration]
Three paths. Same encryption, same key management, same searchable queries. Pick the one that fits your stack.
Encryption SDK (any Postgres client) [#encryption-sdk-any-postgres-client]
Works with any client that connects to your Supabase Postgres database. Define a schema, encrypt fields, store and query with raw SQL or any library.
```typescript
import { Encryption, encryptedTable, encryptedColumn } from "@cipherstash/stack"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch(),
})
const enc = await Encryption({ schemas: [users] })
// Encrypt
const encrypted = await enc.encrypt("alice@example.com", {
column: users.email, table: users,
})
// Store with any client
await sql`INSERT INTO users (email) VALUES (${encrypted.data})`
// Query — search ciphertext without decrypting
const query = await enc.encryptQuery("alice@example.com", {
column: users.email, table: users,
})
```
This is the universal approach. It works regardless of which client library you use to connect to Supabase.
Full reference: [Storing encrypted data](/stack/cipherstash/encryption/storing-data)
Supabase JS SDK [#supabase-js-sdk]
If you're using the Supabase JavaScript client, the `encryptedSupabase` wrapper gives you automatic encryption with the same API you already know.
```typescript
import { encryptedSupabase } from "@cipherstash/stack/supabase"
const eSupabase = await encryptedSupabase(supabaseUrl, supabaseKey)
// Insert — automatically encrypted
await eSupabase.from("users").insert({
email: "alice@example.com",
})
// Query — automatically encrypts the search term
const { data } = await eSupabase
.from("users")
.select()
.eq("email", "alice@example.com")
```
The wrapper handles encryption, decryption, type casting, and search term formatting. Your queries look identical to standard Supabase queries.
Full reference: [Supabase JS SDK integration](/stack/cipherstash/encryption/supabase)
Drizzle ORM [#drizzle-orm]
If you're using Drizzle ORM with Supabase, define encrypted columns directly in your Drizzle schema.
```typescript
import { encryptedType } from "@cipherstash/stack/drizzle"
export const users = pgTable("users", {
id: serial("id").primaryKey(),
email: encryptedType("email"),
})
```
Encrypted query operators work like standard Drizzle operators.
Full reference: [Drizzle ORM integration](/stack/cipherstash/encryption/drizzle)
What you get [#what-you-get]
1. **Searchable encryption.** Equality, free-text search, range, ordering, and JSON queries over ciphertext. Your Postgres indexes work on encrypted data.
2. **Works alongside Row Level Security.** CipherStash encryption and Supabase RLS are complementary. RLS controls who can access rows. CipherStash controls who can decrypt values. Defense in depth.
3. **Schema-first.** Define encrypted columns once. Type-safe across your entire application.
4. **Identity-bound keys.** Tie encryption to a user's identity. Only that user can decrypt their data.
5. **Zero-knowledge.** CipherStash never sees your plaintext data. Keys are derived on your device and never stored.
Encrypted columns on Supabase require functional indexes and a specific query form to avoid silent sequential scans. See [Setting up indexes](/stack/cipherstash/encryption/indexes) for the correct `CREATE INDEX` statements and query patterns.
Going to production [#going-to-production]
Local development uses device-based authentication. Production uses environment variables. See [Going to production](/stack/deploy/going-to-production).
Next steps [#next-steps]
* [Supabase dashboard integration](/stack/reference/dashboard-supabase-integration) — OAuth connect, setup hub, Marketplace install, and production config
* [Quickstart](/stack/quickstart) — Encrypt your first fields in 15 minutes
* [Supabase JS SDK reference](/stack/cipherstash/encryption/supabase) — Full API for the encryptedSupabase wrapper
* [Drizzle ORM reference](/stack/cipherstash/encryption/drizzle) — Encrypted column types and operators
* [Searchable encryption](/stack/cipherstash/encryption/searchable-encryption) — How queries over ciphertext work
* [Supabase db reset removes EQL](/stack/cipherstash/cli/troubleshooting#supabase-db-reset) — Fix for direct-push installs
* [Dashboard](https://dashboard.cipherstash.com/workspaces) — Manage keysets, clients, and access keys
# Deploying to AWS ECS
Deploying CipherStash Proxy to AWS ECS [#deploying-cipherstash-proxy-to-aws-ecs]
Prerequisites [#prerequisites]
* **AWS Account**: An active [AWS account](https://aws.amazon.com/account/)
* **AWS CLI**: Installed and configured with appropriate permissions ([install guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html))
* **Docker**: [Installed](https://docs.docker.com/engine/install/) if you need to push the image to ECR
* **CipherStash Proxy config**: Refer to the [Proxy config reference](https://github.com/cipherstash/proxy/blob/main/docs/reference/index.md)
* **AWS RDS instance**: A [PostgreSQL RDS instance](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html) in the same VPC as your ECS cluster
Set up CipherStash credentials [#set-up-cipherstash-credentials]
You need CipherStash credentials for your production Proxy deployment. Create an application client key and access key in the Dashboard — see [Going to production](/stack/deploy/going-to-production) for the full guide.
You will need:
* `CS_WORKSPACE_CRN` — your workspace identifier
* `CS_CLIENT_ID` — application client ID
* `CS_CLIENT_KEY` — application client key
* `CS_CLIENT_ACCESS_KEY` — access key with the **member** role
Note these credentials. You need them in later steps.
Prepare your Docker image [#prepare-your-docker-image]
Docker images are available from:
* [AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-x4kffsfxcrreg)
* [Docker Hub](https://hub.docker.com/r/cipherstash/proxy)
If using Docker Hub, push the image to Amazon ECR:
```bash
# Ensure you have set these environment variables:
# export AWS_ACCOUNT_ID=111222333444
# export AWS_REGION=ap-southeast-2
set -u
aws ecr create-repository --repository-name cipherstash-proxy
aws ecr get-login-password | docker login \
--username AWS \
--password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
docker tag cipherstash/proxy:latest \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/cipherstash-proxy:latest
docker push \
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/cipherstash-proxy:latest
```
Create secrets [#create-secrets]
Using the credentials from Step 1, create `cipherstash-proxy-secrets.json`:
```json
{
"CS_WORKSPACE_ID": "...",
"CS_CLIENT_ID": "...",
"CS_DEFAULT_KEYSET_ID": "...",
"CS_CLIENT_KEY": "...",
"CS_CLIENT_ACCESS_KEY": "...",
"CS_DATABASE__PASSWORD": "..."
}
```
The value of `CS_DATABASE__PASSWORD` is the password of your PostgreSQL RDS instance.
Create the secret in Secrets Manager:
```bash
aws secretsmanager create-secret \
--name cipherstash-proxy \
--secret-string file://cipherstash-proxy-secrets.json
```
Note the ARN. You need it for the task definition.
Set up IAM roles and permissions [#set-up-iam-roles-and-permissions]
Trust policy [#trust-policy]
Create `ecs-tasks-trust-policy.json`:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
```
Create the IAM role:
```bash
aws iam create-role \
--role-name ecsTaskExecutionRole \
--assume-role-policy-document file://ecs-tasks-trust-policy.json
```
Inline policy [#inline-policy]
Create `cipherstash-proxy-ecs-policy.json`, substituting the ARN of your Secrets Manager secret:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"kms:Decrypt"
],
"Resource": [
"ARN_OF_SECRETSMANAGER_SECRET"
]
}
]
}
```
Attach the inline policy:
```bash
aws iam put-role-policy \
--role-name ecsTaskExecutionRole \
--policy-name CipherStashProxyECSPolicy \
--policy-document file://cipherstash-proxy-ecs-policy.json
```
Managed policy [#managed-policy]
Attach the ECS task execution managed policy:
```bash
aws iam attach-role-policy \
--role-name ecsTaskExecutionRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
```
Create an ECS task definition [#create-an-ecs-task-definition]
Create `cipherstash-proxy-task-def.json`, replacing placeholders with values from previous steps:
```json
{
"family": "cipherstash-proxy",
"networkMode": "awsvpc",
"executionRoleArn": "ARN_OF_ROLE",
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "cipherstash-proxy",
"image": "IMAGE_FROM_STEP_2",
"essential": true,
"portMappings": [
{ "containerPort": 6432, "hostPort": 6432 },
{ "containerPort": 9930, "hostPort": 9930 }
],
"environment": [
{ "name": "CS_DATABASE__USERNAME", "value": "RDS_USERNAME" },
{ "name": "CS_DATABASE__NAME", "value": "RDS_DATABASE_NAME" },
{ "name": "CS_DATABASE__HOST", "value": "RDS_HOSTNAME" },
{ "name": "CS_DATABASE__PORT", "value": "RDS_PORT" },
{ "name": "CS_PROMETHEUS__ENABLED", "value": "true" },
{ "name": "CS_DATABASE__INSTALL_EQL", "value": "true" }
],
"secrets": [
{ "name": "CS_WORKSPACE_ID", "valueFrom": "SECRET_ARN:CS_WORKSPACE_ID::" },
{ "name": "CS_CLIENT_ID", "valueFrom": "SECRET_ARN:CS_CLIENT_ID::" },
{ "name": "CS_DEFAULT_KEYSET_ID", "valueFrom": "SECRET_ARN:CS_DEFAULT_KEYSET_ID::" },
{ "name": "CS_CLIENT_KEY", "valueFrom": "SECRET_ARN:CS_CLIENT_KEY::" },
{ "name": "CS_CLIENT_ACCESS_KEY", "valueFrom": "SECRET_ARN:CS_CLIENT_ACCESS_KEY::" },
{ "name": "CS_DATABASE__PASSWORD", "valueFrom": "SECRET_ARN:CS_DATABASE__PASSWORD::" }
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "cipherstash-proxy",
"awslogs-region": "AWS_REGION",
"awslogs-stream-prefix": "cipherstash-proxy"
}
}
}
],
"requiresCompatibilities": ["FARGATE"],
"runtimePlatform": {
"operatingSystemFamily": "LINUX",
"cpuArchitecture": "ARM64"
}
}
```
Register the task definition:
```bash
aws ecs register-task-definition \
--cli-input-json file://cipherstash-proxy-task-def.json
```
Create an ECS cluster and service [#create-an-ecs-cluster-and-service]
Create the cluster and log group:
```bash
aws ecs create-cluster --cluster-name ecs-app
aws logs create-log-group --log-group-name cipherstash-proxy
```
Create an ECS service (ensure you have the subnet and security group of your RDS instance):
```bash
# Ensure you have set these environment variables:
# export SUBNETS=subnet-xxx
# export SECURITY_GROUP=sg-xxx
aws ecs create-service \
--cluster ecs-app \
--service-name CipherStashProxy \
--task-definition cipherstash-proxy \
--desired-count 1 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[$SUBNETS],securityGroups=[$SECURITY_GROUP],assignPublicIp=ENABLED}"
```
Verify the deployment [#verify-the-deployment]
Check the service and task status:
```bash
# List services running in the cluster
aws ecs list-services --cluster ecs-app
# Show details of CipherStashProxy service
aws ecs describe-services --cluster ecs-app --services CipherStashProxy
# Tail the logs
aws logs tail --since 6h --follow cipherstash-proxy
```
Notes and considerations [#notes-and-considerations]
* **Security**: Store secrets (keys and passwords) in AWS Secrets Manager or Parameter Store, not in environment variables or task definitions.
* **Networking**: Configure security groups and subnets so your ECS tasks can reach your RDS instance.
* **Scaling**: Monitor ECS service metrics and adjust `desired-count` based on load.
# Bundling
`@cipherstash/stack` is a native Node.js module that relies on native `require` to load platform-specific binaries at runtime. It cannot be processed by bundlers like webpack or esbuild. You must exclude it from bundling.
**You must exclude `@cipherstash/stack` from bundling.**
`@cipherstash/stack` uses Node.js-specific features and requires the [native Node.js `require`](https://nodejs.org/api/modules.html#requireid). Bundlers that attempt to inline or transform it will break the native addon loading.
Next.js [#nextjs]
Next.js bundles Server Components by default. You need to tell Next.js to skip `@cipherstash/stack` and use native `require` instead.
Next.js 15+ [#nextjs-15]
Add `@cipherstash/stack` to `serverExternalPackages` in your `next.config.ts`:
```typescript filename="next.config.ts"
const nextConfig = {
serverExternalPackages: ["@cipherstash/stack"],
}
export default nextConfig
```
See the [Next.js serverExternalPackages documentation](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages) for more details.
Next.js 14 [#nextjs-14]
Use the experimental configuration in `next.config.mjs`:
```javascript filename="next.config.mjs"
const nextConfig = {
experimental: {
serverComponentsExternalPackages: ["@cipherstash/stack"],
},
}
export default nextConfig
```
See the [Next.js 14 serverComponentsExternalPackages documentation](https://nextjs.org/docs/14/app/api-reference/next-config-js/serverComponentsExternalPackages) for more details.
webpack [#webpack]
Configure `externals` in your `webpack.config.js`:
```javascript filename="webpack.config.js"
module.exports = {
externals: {
"@cipherstash/stack": "commonjs @cipherstash/stack",
},
}
```
This tells webpack to leave `require('@cipherstash/stack')` as-is in the output instead of trying to resolve and bundle the module.
esbuild [#esbuild]
Use the `--external` flag:
```bash
esbuild app.js --bundle --external:@cipherstash/stack --platform=node
```
Or in your build configuration:
```javascript filename="build.js"
require("esbuild").build({
entryPoints: ["app.js"],
bundle: true,
external: ["@cipherstash/stack"],
platform: "node",
outfile: "out.js",
})
```
SST and AWS Lambda [#sst-and-aws-lambda]
When deploying with [SST](https://sst.dev/), configure esbuild externals and install the package into the Lambda deployment bundle:
```typescript filename="sst.config.ts"
{
nodejs: {
esbuild: {
external: ["@cipherstash/stack"],
},
install: ["@cipherstash/stack"],
},
}
```
The `external` option prevents esbuild from bundling the package. The `install` option ensures it gets installed into the Lambda deployment artifact so it's available at runtime.
See the [SST Function documentation](https://sst.dev/docs/component/aws/function/#nodejs) for more details.
Troubleshooting Linux deployments [#troubleshooting-linux-deployments]
Some npm users experience deployment failures on Linux (e.g., AWS Lambda, Docker containers) when their `package-lock.json` was created on macOS or Windows.
Who is affected [#who-is-affected]
* You use `npm ci` in CI/CD
* Your `package-lock.json` is version 3 and was generated on macOS/Windows
* You deploy on Linux (Lambda, containers, EC2, etc.)
Symptoms [#symptoms]
Build succeeds, but the app fails to start on Linux with an error like:
* `failed to load native addon`
* `module not found` related to the native engine
Why this happens [#why-this-happens]
With `package-lock.json` version 3, npm only records optional native binaries for the platform that created the lockfile. Linux builds can miss the native engine that `@cipherstash/stack` needs at runtime.
Solutions [#solutions]
Option 1: Use bun (recommended) [#option-1-use-bun-recommended]
bun installs the correct native binaries for each platform automatically:
```bash
bun install --frozen-lockfile
```
Option 2: Generate lockfile on Linux in CI [#option-2-generate-lockfile-on-linux-in-ci]
Ensure the Linux build records what Linux needs:
```bash
rm -f package-lock.json
npm install --package-lock-only --ignore-scripts \
--no-audit --no-fund --platform=linux --arch=x64
npm ci
```
Alternative using environment variables:
```bash
npm_config_platform=linux npm_config_arch=x64 \
npm install --package-lock-only --ignore-scripts --no-audit --no-fund
npm ci
```
Option 3: Pin lockfile to version 2 [#option-3-pin-lockfile-to-version-2]
Keep using npm but pin lockfile v2 (npm 8):
Locally:
```bash
npm install --package-lock-only --lockfile-version=2
```
In CI:
```bash
npm i -g npm@8
npm ci
```
Quick verification [#quick-verification]
Before deploying to Linux, verify the native engine loads by running your app inside a Linux container or CI job.
# Going to production
Local development uses device-based authentication — each developer is uniquely identified via `npx stash init`.
Production and CI/CD environments don't have a physical device or interactive login, so they use environment variables instead.
How auth differs between environments [#how-auth-differs-between-environments]
| Environment | Auth mechanism | How it works |
| ------------------ | --------------------- | --------------------------------------------------------------------------- |
| Local development | Device-based | `npx stash init` creates a device and client key. No env vars needed. |
| CI/CD pipelines | Environment variables | Application client key identified by `CS_CLIENT_ID` and `CS_CLIENT_KEY`. |
| Production hosting | Environment variables | Same as CI/CD. Application client keys have keyset access without a device. |
Setting up production credentials [#setting-up-production-credentials]
Create an application client key [#create-an-application-client-key]
In the [CipherStash Dashboard](https://dashboard.cipherstash.com), navigate to **Clients** and create a new client key.
This is an **application client key** — it has no device attached and is identified solely by its client ID and key.
Save the **Client ID** and **Client Key**. The client key is shown only once.
Create an access key [#create-an-access-key]
Navigate to **Access Keys** and create a new access key.
Use the **member** role for production workloads. This follows the principle of least privilege and only allows encrypt/decrypt operations.
| Role | Use case |
| ----------- | ---------------------------------------------------------- |
| **Member** | Application workloads — encrypt, decrypt, list keysets |
| **Control** | Infrastructure automation — manage keysets and client keys |
| **Admin** | Full access. Not recommended for production. |
Save the **access key**. It is shown only once.
See [Access keys](/stack/cipherstash/kms/access-keys) for details on roles and scopes.
Grant keyset access [#grant-keyset-access]
Ensure the application client key has access to the appropriate keyset(s).
By default, new client keys are granted access to the **default keyset**.
For multi-tenant deployments, grant access to specific keysets as needed.
See [Keysets](/stack/cipherstash/kms/keysets) for details on managing keyset access.
Set environment variables [#set-environment-variables]
In your hosting platform or CI/CD system, set the following environment variables:
```bash
CS_WORKSPACE_CRN=crn:region.aws:your-workspace-id
CS_CLIENT_ID=your-application-client-id
CS_CLIENT_KEY=your-application-client-key
CS_CLIENT_ACCESS_KEY=your-access-key
```
These four variables are required for all CipherStash products (Encryption SDK, Secrets, and Proxy) in production.
Set a writable config path (if needed) [#set-a-writable-config-path-if-needed]
In serverless or containerized environments, the default config directory may not be writable.
Set `CS_CONFIG_PATH` to a writable location:
```bash
CS_CONFIG_PATH=/tmp/.cipherstash
```
This has been tested on Vercel, AWS Lambda, and Docker containers.
CI/CD pipeline examples [#cicd-pipeline-examples]
GitHub Actions [#github-actions]
```yaml filename=".github/workflows/deploy.yml"
env:
CS_WORKSPACE_CRN: ${{ secrets.CS_WORKSPACE_CRN }}
CS_CLIENT_ID: ${{ secrets.CS_CLIENT_ID }}
CS_CLIENT_KEY: ${{ secrets.CS_CLIENT_KEY }}
CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_CLIENT_ACCESS_KEY }}
```
Docker [#docker]
```bash filename=".env.production"
CS_WORKSPACE_CRN=crn:region.aws:your-workspace-id
CS_CLIENT_ID=your-application-client-id
CS_CLIENT_KEY=your-application-client-key
CS_CLIENT_ACCESS_KEY=your-access-key
```
```yaml filename="docker-compose.yml"
services:
app:
env_file:
- .env.production
```
Never commit `.env.production` or any file containing credentials to version control.
Add it to your `.gitignore`.
Avoid using environment variables locally [#avoid-using-environment-variables-locally]
Setting `CS_*` environment variables for local development is an anti-pattern.
Device-based auth provides:
* **Per-developer identity** — audit trails show who performed each operation
* **No shared secrets** — each developer has their own device credentials
* **Automatic session management** — no manual credential rotation
If you have `CS_*` variables in a local `.env` file, remove them and run `npx stash init` instead.
Bundler configuration [#bundler-configuration]
Production deployments often require bundler configuration to ensure `@cipherstash/stack` is not bundled with your application code.
See [Bundling](/stack/deploy/bundling) for setup instructions for Next.js, webpack, esbuild, and SST.
Next steps [#next-steps]
* [Bundling for production](/stack/deploy/bundling) — Configure your bundler for deployment
* [Deploying Proxy to AWS ECS](/stack/deploy/aws-ecs) — Run CipherStash Proxy in production
* [Access keys](/stack/cipherstash/kms/access-keys) — Understand access key roles and scopes
* [Team onboarding](/stack/deploy/team-onboarding) — Set up per-developer access for your team
# Deploy
Local dev uses device-based authentication. Production uses environment variables and application client keys. This section covers the transition.
* **[Going to production](/stack/deploy/going-to-production)** — Switch from device auth to environment variables.
* **[Team onboarding](/stack/deploy/team-onboarding)** — Set up per-developer access.
* **[Bundling](/stack/deploy/bundling)** — Configure webpack, esbuild, Next.js, and other build tools.
* **[SST](/stack/deploy/sst)** — SST and serverless configuration.
* **[Testing](/stack/deploy/testing)** — Test applications that use encryption.
* **[AWS ECS](/stack/deploy/aws-ecs)** — Deploy CipherStash Proxy to ECS.
* **[Troubleshooting](/stack/deploy/troubleshooting)** — Common issues and fixes.
# SST
When deploying `@cipherstash/stack` in serverless functions with [SST](https://sst.dev/), you need to exclude the package from esbuild bundling and install it into the Lambda deployment artifact.
Configure `nodejs.esbuild.external` and `nodejs.install` in your `sst.config.ts`:
```typescript title="sst.config.ts"
{
nodejs: {
esbuild: {
external: ["@cipherstash/stack"],
},
install: ["@cipherstash/stack"],
},
}
```
The `external` option prevents esbuild from bundling the package. The `install` option ensures it gets installed into the Lambda deployment artifact so it's available at runtime.
See the [SST Function documentation](https://sst.dev/docs/component/aws/function/#nodejs) for more details.
For other bundler configurations (webpack, esbuild, Next.js) and Linux deployment troubleshooting, see [Bundling](/stack/deploy/bundling).
# Team onboarding
Every developer on your team gets their own device and client key when they initialize CipherStash.
This means every encrypt, decrypt, and secret access operation is traceable to a specific developer.
How team access works [#how-team-access-works]
When a developer runs `npx stash init`, CipherStash creates:
* A **device** tied to that developer's user account and device
* A **client key** associated with the device, used for cryptographic operations
* Automatic access to the **default keyset** in your workspace
No credentials are shared between developers.
Each person has their own identity, and access can be granted or revoked individually.
Adding a team member [#adding-a-team-member]
Invite them to your organization [#invite-them-to-your-organization]
In the [CipherStash Dashboard](https://dashboard.cipherstash.com), navigate to **Members** in the organization sidebar.
Add the new team member's email address.
They will receive an invitation to create a CipherStash account (or join with an existing one).
Grant workspace access [#grant-workspace-access]
Once they accept the invitation, grant them access to the relevant workspace(s) in **Settings** for your workspace.
Developer runs init [#developer-runs-init]
The new team member runs the init command on their device:
```bash
npx stash init
```
This authenticates them via the browser, creates their unique device and client key, and grants access to the default keyset.
They are now ready to develop locally with full CipherStash functionality — no environment variables needed.
Managing team access [#managing-team-access]
Viewing active client keys [#viewing-active-client-keys]
Navigate to **Clients** in the Dashboard to see all active client keys, including device-backed client keys created by `npx stash init`.
Each client key shows which developer and device it belongs to.
Revoking a developer's access [#revoking-a-developers-access]
To remove a developer's access:
1. Remove their client key from keyset access in the Dashboard
2. Remove them from the workspace
3. Optionally, remove them from the organization
Revoked client keys can no longer perform encrypt, decrypt, or secret operations.
Multiple devices [#multiple-devices]
If a developer works on more than one device (e.g., a laptop and a desktop), they run `npx stash init` on each device.
Each device gets its own device identity and client key.
This is by design — you can revoke access per device without affecting their other devices.
Relationship to production [#relationship-to-production]
Team devices are for **local development only**.
Production environments use separate [application client keys](/stack/deploy/going-to-production) configured with environment variables.
This separation ensures that:
* Developer credentials never appear in production infrastructure
* Production access is controlled independently from developer access
* Revoking a developer's device does not affect production systems
Next steps [#next-steps]
* [Getting started](/stack/quickstart) — The init guide to share with new team members
* [Going to production](/stack/deploy/going-to-production) — Set up application client keys for deployment
* [Access keys](/stack/cipherstash/kms/access-keys) — Understand access key roles
* [Client keys](/stack/cipherstash/kms/clients) — Learn more about device-backed and application client keys
# Testing
This guide covers strategies for testing applications that use CipherStash encryption.
Test environment setup [#test-environment-setup]
CipherStash encryption requires valid credentials to derive keys via ZeroKMS. For testing, you have two options:
Option 1: Use a dedicated test workspace (recommended) [#option-1-use-a-dedicated-test-workspace-recommended]
Create a separate CipherStash workspace for testing. This gives you real encryption behavior with isolated keys that don't affect production.
```bash filename=".env.test"
CS_WORKSPACE_CRN=crn:ap-southeast-2.aws:your-test-workspace-id
CS_CLIENT_ID=your-test-client-id
CS_CLIENT_KEY=your-test-client-key
CS_CLIENT_ACCESS_KEY=your-test-access-key
```
This approach tests the full encryption path including ZeroKMS key derivation.
Option 2: Mock the encryption client [#option-2-mock-the-encryption-client]
For unit tests where you don't need real encryption, mock the client to return predictable values:
```typescript filename="__mocks__/encryption.ts"
import type { InferEncrypted } from "@cipherstash/stack/schema"
export function createMockClient() {
return {
encrypt: async (plaintext: unknown) => ({
data: { __mock: true, plaintext },
}),
decrypt: async (encrypted: unknown) => ({
data: (encrypted as any).plaintext,
}),
encryptModel: async (model: unknown) => ({
data: model,
}),
decryptModel: async (model: unknown) => ({
data: model,
}),
bulkEncrypt: async (items: any[]) => ({
data: items.map(i => ({ id: i.id, data: { __mock: true, plaintext: i.plaintext } })),
}),
bulkDecrypt: async (items: any[]) => ({
data: items.map(i => ({ id: i.id, data: i.plaintext })),
}),
}
}
```
Mocking bypasses all encryption. Use this for testing business logic, not for validating that encryption works correctly. Always run integration tests with a real workspace.
Integration tests with PostgreSQL [#integration-tests-with-postgresql]
For integration tests that verify searchable encryption queries, you need:
1. A PostgreSQL database with [EQL](/stack/reference/eql-guide) installed (see [CipherStash CLI](/stack/cipherstash/cli))
2. A test CipherStash workspace
3. Your schema definitions
```typescript filename="test/setup.ts"
import { Encryption } from "@cipherstash/stack"
import { users } from "../src/schema"
import { Pool } from "pg"
let client: Awaited>
let pool: Pool
beforeAll(async () => {
pool = new Pool({ connectionString: process.env.TEST_DATABASE_URL })
// EQL must be installed before running tests.
// Run `npx stash db install` against your test database first.
// See: /stack/cipherstash/cli
// Initialize encryption client with test credentials
// Encryption() throws on failure, so wrap in try/catch
try {
client = await Encryption({ schemas: [users] })
} catch (error) {
throw new Error(`Test setup failed: ${(error as Error).message}`)
}
})
afterAll(async () => {
await pool.end()
})
```
Testing encrypt and decrypt round-trips [#testing-encrypt-and-decrypt-round-trips]
```typescript filename="test/encryption.test.ts"
test("encrypt and decrypt returns original value", async () => {
const original = "alice@example.com"
const encrypted = await client.encrypt(original, {
column: users.email,
table: users,
})
expect(encrypted.failure).toBeUndefined()
const decrypted = await client.decrypt(encrypted.data)
expect(decrypted.failure).toBeUndefined()
expect(decrypted.data).toBe(original)
})
```
Testing searchable queries [#testing-searchable-queries]
```typescript filename="test/search.test.ts"
test("equality search finds encrypted record", async () => {
// Encrypt and store a value
const encrypted = await client.encrypt("alice@example.com", {
column: users.email,
table: users,
})
await pool.query(
"INSERT INTO users (email) VALUES ($1::jsonb)",
[JSON.stringify(encrypted.data)]
)
// Encrypt the search term
const query = await client.encryptQuery("alice@example.com", {
column: users.email,
table: users,
queryType: "equality",
})
// Query the database
const result = await pool.query(
"SELECT * FROM users WHERE email @> $1::jsonb",
[JSON.stringify(query.data)]
)
expect(result.rows.length).toBe(1)
})
```
CI/CD setup [#cicd-setup]
GitHub Actions [#github-actions]
Store your test credentials as GitHub Actions secrets and expose them as environment variables:
```yaml filename=".github/workflows/test.yml"
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: test
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: bun install --frozen-lockfile
- run: bun test
env:
CS_WORKSPACE_CRN: ${{ secrets.CS_TEST_WORKSPACE_CRN }}
CS_CLIENT_ID: ${{ secrets.CS_TEST_CLIENT_ID }}
CS_CLIENT_KEY: ${{ secrets.CS_TEST_CLIENT_KEY }}
CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_TEST_ACCESS_KEY }}
TEST_DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
```
Use a dedicated test workspace with limited access keys. Never use production credentials in CI.
Docker-based tests [#docker-based-tests]
If your CI uses Docker, ensure the native addon loads correctly by using a Linux-compatible lockfile. See [Bundling: Linux deployments](/stack/deploy/bundling#troubleshooting-linux-deployments) for details.
Schema builders in test code [#schema-builders-in-test-code]
The `@cipherstash/stack/client` subpath provides schema builders without the native FFI module. This is useful for importing schemas in client-side test code or test utilities that don't perform encryption:
```typescript filename="test/utils.ts"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/client"
// This import works without the native addon
const users = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch(),
})
```
# Troubleshooting
Client initialization fails [#client-initialization-fails]
Missing environment variables [#missing-environment-variables]
The SDK reads `CS_*` environment variables by default. If any are missing, initialization fails with a `ClientInitError`.
```text
ClientInitError: Missing workspace CRN
```
**Fix**: Set all required environment variables:
```bash
CS_WORKSPACE_CRN=crn:ap-southeast-2.aws:your-workspace-id
CS_CLIENT_ID=your-client-id
CS_CLIENT_KEY=your-client-key
CS_CLIENT_ACCESS_KEY=your-access-key
```
Or pass them explicitly in config:
```typescript filename="explicit-config.ts"
const client = await Encryption({
schemas: [users],
config: {
workspaceCrn: "crn:ap-southeast-2.aws:your-workspace-id",
clientId: "your-client-id",
clientKey: "your-client-key",
accessKey: "your-access-key",
},
})
```
ZeroKMS connection timeout [#zerokms-connection-timeout]
If the SDK cannot reach ZeroKMS, initialization times out.
**Check**:
* Your application has outbound HTTPS access
* No firewall or security group is blocking connections to `*.viturhosted.net`
* The region in your `CS_WORKSPACE_CRN` matches your workspace's actual region
No schemas provided [#no-schemas-provided]
The `Encryption()` function requires at least one schema:
```typescript filename="schema-required.ts"
// This will fail
const client = await Encryption({ schemas: [] })
// Provide at least one schema
const client = await Encryption({ schemas: [users] })
```
Decryption errors [#decryption-errors]
Wrong keyset [#wrong-keyset]
Decryption fails if the client uses a different keyset than the one used during encryption. Keysets provide cryptographic isolation.
**Fix**: Ensure the client is configured with the same keyset that was used during encryption. Check the `keyset` option in your config or verify which keyset your client is associated with in the [dashboard](/stack/cipherstash/kms/clients).
Lock context mismatch [#lock-context-mismatch]
If data was encrypted with [identity-aware encryption](/stack/cipherstash/encryption/identity), it can only be decrypted with the same user's identity.
```text
DecryptionError: Lock context mismatch
```
**Fix**: Ensure you're using the same user's JWT for decryption that was used during encryption. The lock context binds encryption to the identity claims in the JWT.
Corrupted or tampered ciphertext [#corrupted-or-tampered-ciphertext]
If the stored ciphertext has been modified, decryption fails. AES-GCM-SIV provides authenticated encryption, which detects tampering.
**Fix**: Check that no database triggers, application code, or migrations have modified the encrypted column values.
Search queries return no results [#search-queries-return-no-results]
Missing EQL extension [#missing-eql-extension]
Searchable encryption requires the [EQL PostgreSQL extension](/stack/reference/eql-guide). Without it, queries against encrypted columns won't work.
**Fix**: Install EQL using the [CipherStash CLI](/stack/cipherstash/cli):
```bash
npx stash db install
```
Wrong query type [#wrong-query-type]
If you're searching with the wrong query type for your column's indexes, queries may return no results.
**Check**:
* Using `equality` queries? The column needs `.equality()` in its schema.
* Using `freeTextSearch` queries? The column needs `.freeTextSearch()` in its schema.
* Using `orderAndRange` queries? The column needs `.orderAndRange()` in its schema.
Schema mismatch [#schema-mismatch]
The schema used to encrypt data must match the schema used to encrypt query terms. If you've changed your schema definition (added/removed indexes), existing encrypted data may not be searchable with the new schema.
**Fix**: Re-encrypt existing data with the updated schema if you've changed index configurations.
Native addon fails to load [#native-addon-fails-to-load]
Symptoms [#symptoms]
```text
Error: failed to load native addon
Error: module not found: @cipherstash/protect-ffi
```
Bundler not configured [#bundler-not-configured]
`@cipherstash/stack` uses a native Node.js addon and cannot be processed by bundlers. You must exclude it.
**Fix**: See [Bundling](/stack/deploy/bundling) for configuration for Next.js, webpack, esbuild, and SST.
Linux deployment with npm lockfile v3 [#linux-deployment-with-npm-lockfile-v3]
Some npm users see deployment failures on Linux (e.g., AWS Lambda) when their `package-lock.json` was created on macOS or Windows.
This happens with `package-lock.json` version 3, where npm only records native dependencies for the platform that created the lockfile. Linux builds can miss the native engine that `@cipherstash/stack` needs at runtime.
**Who is affected**:
* You use `npm ci` in CI/CD
* Your `package-lock.json` is version 3 and was generated on macOS or Windows
* You deploy on Linux (Lambda, containers, EC2, etc.)
**Fix**: Use one of these solutions:
**Option 1: Use bun (recommended)**
```bash
bun install --frozen-lockfile
```
**Option 2: Generate lockfile on Linux in CI**
```bash
rm -f package-lock.json
npm install --package-lock-only --ignore-scripts --no-audit --no-fund --platform=linux --arch=x64
npm ci
```
**Option 3: Pin lockfile to version 2**
```bash
npm install --package-lock-only --lockfile-version=2
```
Quick verification [#quick-verification]
Before deploying, verify the native engine loads by running your app inside a Linux container or CI job.
Performance [#performance]
Slow encrypt/decrypt operations [#slow-encryptdecrypt-operations]
CipherStash encryption adds \< 5ms overhead per operation. If you're seeing higher latency:
1. **Check network latency to ZeroKMS.** Key derivation requires a round-trip to ZeroKMS. Deploy your application in the same AWS region as your workspace.
2. **Use bulk operations.** `bulkEncrypt` and `bulkDecrypt` make a single ZeroKMS call regardless of record count, which is significantly faster for multiple values.
3. **Check your connection pool.** If you're creating a new `Encryption` client per request, you're paying initialization cost each time. Initialize once and reuse the client.
Optimizing bulk operations [#optimizing-bulk-operations]
For best performance with bulk operations:
```typescript filename="bulk-optimization.ts"
// Slow: one ZeroKMS call per value
for (const user of users) {
await client.encrypt(user.email, { column: schema.email, table: schema })
}
// Fast: one ZeroKMS call for all values
const plaintexts = users.map(u => ({ id: u.id, plaintext: u.email }))
await client.bulkEncrypt(plaintexts, { column: schema.email, table: schema })
```
Debugging [#debugging]
Enable debug logging to see detailed operation information:
```bash
STASH_STACK_LOG=debug
```
This logs key derivation requests, operation timing, and connection details. The SDK never logs plaintext data or key material at any log level.
# Agent Skills
CipherStash publishes a set of [agent skills](https://skills.sh/) that give AI coding assistants deep knowledge of the CipherStash SDK, CLI, and integrations. When installed, your agent can accurately generate encryption schemas, write integration code, and guide you through database setup without hallucinating API surfaces.
Skills are compatible with any AI coding tool that supports the skills protocol, including Claude Code, Cursor, GitHub Copilot, Windsurf, Cline, Gemini, AMP, Goose, Roo, Trae, and others.
Install skills [#install-skills]
Skills are installed per-project. Run this in your project root:
```bash
npx skills add cipherstash/stack
```
This installs all six CipherStash skills into your project. Your AI coding agent will automatically activate the relevant skill based on what you are working on.
Install via the wizard [#install-via-the-wizard]
`@cipherstash/wizard` prompts you to install integration-appropriate skills after its post-agent steps. It offers to copy skills into `./.claude/skills/` based on your integration:
* **Drizzle**: `stash-encryption`, `stash-drizzle`, `stash-cli`
* **Supabase**: `stash-encryption`, `stash-supabase`, `stash-cli`
* **Prisma / generic**: `stash-encryption`, `stash-cli`
Available skills [#available-skills]
The `cipherstash/stack` skill pack includes six skills. Each one covers a specific area of the CipherStash platform.
stash-encryption [#stash-encryption]
Core field-level encryption with `@cipherstash/stack`. This is the foundational skill that covers the full encryption API.
**Covers:**
* Schema definition with `encryptedTable` and `encryptedColumn`
* Single and bulk encrypt/decrypt operations
* Model operations (`encryptModel`, `decryptModel`, `bulkEncryptModels`, `bulkDecryptModels`)
* Searchable encryption (equality, free-text search, range queries, encrypted JSONB)
* Identity-aware encryption with `LockContext` and JWT-based access control
* Multi-tenant isolation with keysets
* Error handling with the `Result` pattern
* Migration from `@cipherstash/protect`
**When it activates:** Your agent loads this skill when you are defining encrypted schemas, writing encrypt/decrypt logic, or working with the `@cipherstash/stack` package.
**Related docs:** [Encryption](/stack/cipherstash/encryption)
stash-cli [#stash-cli]
The CipherStash CLI (`stash`) for database setup, schema management, and project initialization.
**Covers:**
* `stash.config.ts` configuration
* Setup lifecycle: `init` (scaffold + EQL + context), `plan` (draft plan.md), `impl` (execute plan), `status` (lifecycle map)
* Database commands: `db install`, `db upgrade`, `db push`, `db validate`, `db status`, `db test-connection`
* Schema building with `schema build`
* Authentication with `auth login`
* Programmatic API (`EQLInstaller`, `loadStashConfig`, `defineConfig`, `loadBundledEqlSql`)
* Drizzle migration mode (`--drizzle`)
* Supabase-compatible installs (`--supabase`)
* Automatic Supabase and Drizzle detection
* Automatic OPE fallback on managed databases
**When it activates:** Your agent loads this skill when you are working with `stash.config.ts`, running CLI commands, or setting up EQL in a database.
**Related docs:** [CipherStash CLI](/stack/cipherstash/cli)
stash-drizzle [#stash-drizzle]
Drizzle ORM integration using `@cipherstash/stack/drizzle`.
**Covers:**
* `encryptedType()` column type for Drizzle table schemas
* `extractEncryptionSchema()` to convert Drizzle tables to CipherStash schemas
* `createEncryptionOperators()` for type-safe encrypted queries
* All query operators: `eq`, `ne`, `like`, `ilike`, `gt`, `gte`, `lt`, `lte`, `between`, `inArray`, `asc`, `desc`
* Encrypted JSONB operators: `jsonbPathExists`, `jsonbPathQueryFirst`, `jsonbGet`
* Batched `and()` / `or()` conditions for efficient multi-condition queries
* EQL migration generation
* Non-encrypted column fallback behavior
* Complete Express/Hono/Next.js API examples
**When it activates:** Your agent loads this skill when you are using Drizzle ORM with encrypted columns or importing from `@cipherstash/stack/drizzle`.
**Related docs:** [Drizzle integration](/stack/cipherstash/encryption/drizzle)
stash-supabase [#stash-supabase]
Supabase integration using `@cipherstash/stack/supabase`.
**Covers:**
* `encryptedSupabase()` wrapper for the Supabase JS client
* Transparent encryption on `insert`, `update`, and `upsert`
* Transparent decryption on `select`, `single`, and `maybeSingle`
* Encrypted query filters: `eq`, `neq`, `like`, `ilike`, `gt`, `gte`, `lt`, `lte`, `in`, `match`, `or`, `not`, `filter`
* Identity-aware encryption with `.withLockContext()`
* Audit logging with `.audit()`
* Response types and error handling
* Supabase-specific database setup (JSONB columns, EQL extension)
**When it activates:** Your agent loads this skill when you are using Supabase with encrypted columns or importing from `@cipherstash/stack/supabase`.
**Related docs:** [Supabase integration](/stack/cipherstash/encryption/supabase)
stash-dynamodb [#stash-dynamodb]
Amazon DynamoDB integration using `@cipherstash/stack/dynamodb`.
**Covers:**
* `encryptedDynamoDB()` helper for encrypting items before writes and decrypting after reads
* DynamoDB attribute naming conventions (`__source` and `__hmac` suffixes)
* Single and bulk encrypt/decrypt model operations
* Querying encrypted partition keys, sort keys, and GSI keys via HMAC attributes
* Nested object encryption with `encryptedField`
* Audit logging
* DynamoDB table design patterns for encrypted attributes
* Complete examples with `PutCommand`, `GetCommand`, `QueryCommand`, and `BatchWriteCommand`
**When it activates:** Your agent loads this skill when you are using DynamoDB with encrypted attributes or importing from `@cipherstash/stack/dynamodb`.
**Related docs:** [DynamoDB integration](/stack/cipherstash/encryption/dynamodb)
stash-secrets [#stash-secrets]
Encrypted secrets management with `@cipherstash/stack`.
**Covers:**
* `Secrets` class API: `set`, `get`, `getMany`, `list`, `delete`
* Environment-based isolation with per-environment encryption keysets
* Bulk secret retrieval with `getMany` (2 to 100 secrets per call)
* Error types: `ApiError`, `NetworkError`, `ClientError`, `EncryptionError`, `DecryptionError`
* Configuration via `CS_*` environment variables or explicit config
* Patterns for loading secrets at application startup
**When it activates:** Your agent loads this skill when you are storing or retrieving secrets, or working with the `Secrets` class from `@cipherstash/stack/secrets`.
**Related docs:** [Secrets (coming soon)](https://cipherstash.com/stack/secrets)
How skills work [#how-skills-work]
When you ask your AI coding agent to help with a CipherStash task, it checks which skills are installed and activates the relevant one based on your request. The skill provides the agent with:
* Complete API surface documentation (method signatures, types, return values)
* Correct code examples that match the current SDK version
* Integration-specific patterns and best practices
* Known limitations and workarounds
This means your agent can write accurate CipherStash code on the first try, rather than guessing at API shapes or generating outdated patterns.
Typical workflow [#typical-workflow]
After installing skills, your AI coding agent can assist with the full CipherStash setup:
1. **Initialize your project:** Ask your agent to set up CipherStash and it will run `npx stash init`. Init authenticates you, installs EQL, scaffolds the encryption client, and writes `.cipherstash/context.json`.
2. **Draft a plan:** Run `npx stash plan`. The agent produces `.cipherstash/plan.md` listing the tables and columns to encrypt. Review the plan before proceeding.
3. **Execute the plan:** Run `npx stash impl`. The agent reads the plan and wires up `encryptModel`/`decryptModel` in your codebase. Skills give it accurate knowledge of the current API surface.
4. **Handle edge cases:** The agent knows about searchable encryption constraints, operator family limitations, identity-aware encryption, and multi-tenant keysets.
Requirements [#requirements]
* An AI coding tool that supports the [skills protocol](https://skills.sh/)
* Node.js 18 or later
* A CipherStash account ([sign up](https://dashboard.cipherstash.com/sign-up))
# Billing
CipherStash uses a **per-workspace billing model**. Each workspace has its own plan and usage limits. Organizations and team members are always free and unlimited.
Plans [#plans]
| | Free | Pro ($99/mo) | Business ($870/mo) | Enterprise |
| ----------------- | -------- | ------------ | ------------------ | ---------- |
| Protect ops/month | 10,000 | 50,000 | 500,000 | Unlimited |
| Secrets | 100 | 500 | 2,000 | Unlimited |
| Keysets | 2 | 10 | 25 | Unlimited |
| Clients | 2 | 10 | 50 | Unlimited |
| OIDC providers | 0 | 0 | 5 | Unlimited |
| Keyset isolation | No | Yes | Yes | Yes |
| Lock contexts | No | No | Yes | Yes |
| Workspace members | 1 | 5 | 25 | Unlimited |
| Workspace type | Dev only | Dev + Prod | Dev + Prod | Dev + Prod |
Free plan [#free-plan]
The Free plan is designed for non-commercial personal projects and experimentation. Free workspaces are limited to development environments only.
Pro plan [#pro-plan]
The Pro plan is for teams and production applications. It includes development and production workspace support with higher limits.
Business plan [#business-plan]
The Business plan offers predictable pricing for demanding workloads with multi-tenant encryption and [Lock Contexts](/stack/cipherstash/encryption/identity) support.
Enterprise plan [#enterprise-plan]
Enterprise is an organization-level plan with custom pricing, unlimited limits across all workspaces, and dedicated support. Enterprise organizations bypass per-workspace billing entirely. Contact [sales@cipherstash.com](mailto:sales@cipherstash.com) for details.
Managing workspace billing [#managing-workspace-billing]
Each workspace has a dedicated billing page accessible from the workspace sidebar. From there you can:
* View your current plan and usage metrics
* Upgrade or downgrade your plan
* Cancel your subscription
* Access the Stripe billing portal for payment methods and invoices
How billing works [#how-billing-works]
* **One Stripe customer per organization**: All workspaces in your organization share the same Stripe customer for billing.
* **One subscription per workspace**: Each workspace has its own Stripe subscription tied to its plan.
* **Proration on upgrades**: When upgrading mid-cycle, you're charged a prorated amount for the remainder of the billing period.
* **Downgrades at period end**: When downgrading, the change takes effect at the end of your current billing period.
* **Cancellation**: Canceling a subscription keeps the plan active until the end of the billing period, then reverts to Free.
Enterprise and AWS Marketplace [#enterprise-and-aws-marketplace]
Organizations on Enterprise plans or subscribed through AWS Marketplace have billing managed at the organization level. All workspaces in these organizations have unlimited access to all features. Visit the Organization Profile to manage enterprise billing.
Need help? [#need-help]
Contact [support@cipherstash.com](mailto:support@cipherstash.com) for billing questions.
# The CipherCell
The **CipherCell** is CipherStash's standard format for storing encrypted data in a database. It is a JSON-based structure that combines **encrypted values**, **searchable encrypted metadata**, and **non-sensitive metadata** into a single, self-contained record.
CipherCells are designed to make encrypted data practical to work with in real applications. They can be stored in existing databases (such as PostgreSQL jsonb columns), indexed, queried, and audited without exposing plaintext.
What a CipherCell contains [#what-a-ciphercell-contains]
A CipherCell typically includes:
* **Encrypted data**
* The ciphertext for one or more sensitive values.
* Each value is encrypted independently using strong authenticated encryption and unique per-value keys.
* **Searchable Encrypted Metadata (SEM)**
* Additional cryptographic material derived from the plaintext that enables secure querying using searchable encryption.
* This metadata allows operations such as equality checks, range queries, or text search to be performed **without decrypting the data**.
* The database can evaluate queries over this metadata, but cannot recover the original values.
* **Non-sensitive metadata**
* Plaintext fields that are safe to expose, such as schema identifiers, versioning information, timestamps, or application-level IDs.
* Keeping this metadata unencrypted allows efficient filtering, indexing, and integration with existing tooling.
How CipherCells are used [#how-ciphercells-are-used]
CipherCells are stored directly in the database, usually in a JSON-compatible column.
[Encrypt Query Language (EQL)](/stack/reference/eql-guide) understands this structure and provides database functions and operators that work over CipherCells, enabling encrypted search and filtering while preserving strong security guarantees.
From an application's perspective, a CipherCell behaves like a regular database value:
* Applications write encrypted data as JSON
* Databases store and index it
* Queries operate on Searchable Encrypted Metadata (SEM)
* Decryption happens only in trusted application code with the right keys and claims
Why CipherCells exist [#why-ciphercells-exist]
The CipherCell format solves a common problem with encryption at rest: traditional encryption makes data opaque and hard to query. CipherCells retain the benefits of encryption while enabling:
* Fine-grained, **per-value** protection
* Searchable encryption over structured data
* Compatibility with existing databases and ORMs
* Clear separation between sensitive and non-sensitive information
A CipherCell is the **unit of encrypted storage** in CipherStash: a portable, self-describing JSON record that makes encrypted data usable, searchable, and auditable by default.
Structure [#structure]
**Required fields**: Only `i` (identifier) and `v` (version) are required.
**Payload requirement**: Either `c` (ciphertext) or `sv` (structured encryption vector) must be present, but never both.
**Optional fields**: All searchable encrypted metadata (SEM) fields are optional and only included when the corresponding index types are configured.
A CipherCell is stored as a JSON object with the following top-level structure:
```json
{
"i": {
"t": "table_name",
"c": "column_name"
},
"v": 2,
"c": "encrypted_data_in_messagepack_base85",
"hm": "2e182f0c444d1d51f5f70f32d778b2eaa854f5921a4a2acaa4446c44055cb777",
"ob": ["ore_block_1", "ore_block_2"],
"bf": [1234, 5678, 9012]
}
```
Top-level fields [#top-level-fields]
i - Identifier [#i---identifier]
The table and column identifier for this encrypted data.
**Type**: Object with `t` (table) and `c` (column) properties
**Required**: Yes
```json
{
"i": {
"t": "users",
"c": "email"
}
}
```
This field identifies which table and column the encrypted data belongs to, enabling proper decryption and index usage.
v - Version [#v---version]
The encryption version used for this CipherCell.
**Type**: Integer
**Required**: Yes
```json
{
"v": 2
}
```
The version field allows for cryptographic algorithm upgrades over time while maintaining backward compatibility.
c - Ciphertext (required unless sv is present) [#c---ciphertext-required-unless-sv-is-present]
The encrypted record containing the actual plaintext data.
**Type**: String (MessagePack encoded and Base85 encoded)
**Required**: Yes (unless `sv` field is present)
```json
{
"c": "Xk}0>Z*pVbW@%*8a%F0@"
}
```
Either `c` or `sv` must be present in every CipherCell, but never both. Use `c` for standard encrypted values and `sv` for structured encryption vectors (arrays or JSON structures).
a - Array item flag [#a---array-item-flag]
Indicates whether this CipherCell represents an item within an array.
**Type**: Boolean
**Required**: No
```json
{
"a": true
}
```
Searchable Encrypted Metadata (SEM) [#searchable-encrypted-metadata-sem]
The CipherCell can contain various types of searchable encrypted metadata, each enabling different query capabilities. All SEM fields are optional and only included when the corresponding index type is configured.
hm - HMAC-SHA256 [#hm---hmac-sha256]
Enables exact match queries using HMAC-SHA256.
**Type**: Hex-encoded string (64 characters)
**Index Type**: [Exact](/stack/cipherstash/encryption/searchable-encryption#exact-match)
```json
{
"hm": "2e182f0c444d1d51f5f70f32d778b2eaa854f5921a4a2acaa4446c44055cb777"
}
```
ob - ORE Block [#ob---ore-block]
Enables range queries and ordering using Order Revealing Encryption.
**Type**: Array of strings
**Index Type**: [Order / Range](/stack/cipherstash/encryption/searchable-encryption#range--order)
```json
{
"ob": [
"01a2b3c4d5e6f7g8h9i0",
"j1k2l3m4n5o6p7q8r9s0"
]
}
```
bf - Bloom Filter [#bf---bloom-filter]
Enables substring and pattern matching queries using encrypted Bloom filters with trigrams.
**Type**: Array of integers
**Index Type**: [Match](/stack/cipherstash/encryption/searchable-encryption#match-pattern)
```json
{
"bf": [1234, 5678, 9012, 3456, 7890]
}
```
b3 - Blake3 [#b3---blake3]
Blake3 hash for exact matches in structured encryption vectors.
**Type**: Hex-encoded string
**Used in**: SteVec (Structured Encryption Vector) subfield
s - Selector [#s---selector]
Selector value for field selection in structured encryption vectors.
**Type**: String
**Used in**: SteVec (Structured Encryption Vector) subfield
ocf - ORE CLWW Fixed-Width [#ocf---ore-clww-fixed-width]
ORE CLWW (Chenette-Lewi-Weis-Wu) fixed-width scheme for 64-bit integer values in structured encryption vectors.
**Type**: String
**Used in**: SteVec (Structured Encryption Vector) subfield
ocv - ORE CLWW Variable-Width [#ocv---ore-clww-variable-width]
ORE CLWW variable-width scheme for string comparison in structured encryption vectors.
**Type**: String
**Used in**: SteVec (Structured Encryption Vector) subfield
sv - Structured Encryption Vector (SteVec) (required unless c is present) [#sv---structured-encryption-vector-stevec-required-unless-c-is-present]
Nested array of CipherCells for supporting containment queries and JSON-style operations.
**Type**: Array of CipherCell objects
**Required**: Yes (unless `c` attribute is present)
```json
{
"sv": [
{
"c": "Xk}0>Z*pVbW@%*8a%F0@",
"hm": "hash1...",
"s": "selector1"
},
{
"c": "Yl~1?A+qWcX#&+9b&G1#",
"hm": "hash2...",
"s": "selector2"
}
]
}
```
SteVec enables queries on array elements and JSON document structures while maintaining encryption. Each element in the `sv` array is itself a CipherCell that can contain SEM fields like `b3`, `s`, `ocf`, and `ocv`.
Complete example [#complete-example]
Here's a complete CipherCell with multiple index types enabled:
```json
{
"i": {
"t": "products",
"c": "price"
},
"v": 2,
"c": "Xk}0>Z*pVbW@%*8a%F0@Yl~1?A+qWcX#&+9b&G1#",
"hm": "2e182f0c444d1d51f5f70f32d778b2eaa854f5921a4a2acaa4446c44055cb777",
"ob": [
"01a2b3c4d5e6f7g8h9i0",
"j1k2l3m4n5o6p7q8r9s0",
"t1u2v3w4x5y6z7a8b9c0"
],
"bf": [1234, 5678, 9012, 3456, 7890, 2345, 6789]
}
```
This CipherCell:
* Belongs to the `price` column of the `products` table
* Uses encryption version 2
* Contains the encrypted plaintext value
* Supports exact match queries via `hm`
* Supports range queries and ordering via `ob`
* Supports pattern matching via `bf`
Design principles [#design-principles]
Minimal storage [#minimal-storage]
Only the index types configured for a column are included in the CipherCell. This minimizes storage overhead and ensures optimal performance.
Composable indexes [#composable-indexes]
Multiple index types can be combined on a single column, enabling both exact matches and range queries, or exact matches and pattern matching, depending on application needs.
Forward compatibility [#forward-compatibility]
The version field (`v`) enables cryptographic algorithm upgrades without requiring full database re-encryption. Older versions can coexist with newer versions during migration.
Standardized format [#standardized-format]
The CipherCell format is consistent across all CipherStash SDKs and tools, ensuring interoperability and portability of encrypted data.
Database storage [#database-storage]
CipherCells can be stored as JSON in any database that supports JSON data types.
However, for search to be supported using the [Encryption SDK](/stack/cipherstash/encryption) or [CipherStash Proxy](/stack/cipherstash/proxy), the `eql_v2.encrypted` database type must be used which is available when the Encrypt Query Language (EQL) helpers have been installed.
# Compliance
CipherStash helps organizations meet compliance requirements through field-level encryption, identity-bound access controls, and audit logging. This page summarizes how CipherStash maps to common compliance frameworks and what capabilities are available.
Compliance frameworks [#compliance-frameworks]
CipherStash's encryption and access control capabilities support the following compliance frameworks:
| Framework | How CipherStash helps |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **SOC 2 Type II** | Field-level encryption, access key scoping, audit logging of every decrypt operation, key rotation via keysets |
| **HIPAA** | Encryption of PHI at the field level, identity-aware access controls (lock contexts), audit trails for data access |
| **GDPR** | Encryption of personal data, data residency controls via [regional deployment](/stack/cipherstash/kms/regions), crypto-shredding by deleting keysets |
| **PCI-DSS** | Encryption of cardholder data fields, key management via ZeroKMS, separation of key material from encrypted data |
| **ISO 27001** | Cryptographic controls (A.10), access control (A.9), logging and monitoring (A.12) |
| **CCPA** | Encryption of consumer personal information, access controls, audit trails |
Contact [support@cipherstash.com](mailto:support@cipherstash.com) to request SOC 2 reports, sign a HIPAA Business Associate Agreement (BAA), or obtain a GDPR Data Processing Agreement (DPA).
Data residency [#data-residency]
CipherStash workspaces are deployed to specific AWS regions. Key material and encryption operations stay within the configured region.
Available regions are listed on the [Regions](/stack/cipherstash/kms/regions) page. When you create a workspace, you select a region and all ZeroKMS key material is stored and processed in that region.
Audit logging [#audit-logging]
CipherStash logs every key derivation event. Every encrypt and decrypt operation is recorded with context about who performed it, when, and for which keyset.
Audit logs are available through the CipherStash dashboard. For programmatic access or SIEM integration, contact [support@cipherstash.com](mailto:support@cipherstash.com).
What is logged [#what-is-logged]
| Field | Description |
| ---------------- | ------------------------------------------------------------------------------------- |
| Timestamp | When the operation occurred |
| Operation type | Encrypt or decrypt |
| Client ID | Which application performed the operation |
| Keyset | Which keyset was used |
| Identity context | The identity claim (if using [lock contexts](/stack/cipherstash/encryption/identity)) |
Audit with the SDK [#audit-with-the-sdk]
The SDK supports attaching audit metadata to any operation:
```typescript filename="audit-example.ts"
const result = await client
.encrypt(plaintext, { column: users.email, table: users })
.audit({ metadata: { action: "create", resource: "user" } })
```
This metadata is included in the audit log alongside the standard fields.
Crypto-shredding [#crypto-shredding]
CipherStash supports crypto-shredding: permanently destroying data by deleting the encryption keys rather than the ciphertext.
Because each [keyset](/stack/cipherstash/kms/keysets) provides cryptographic isolation, you can:
1. Delete a keyset to make all data encrypted under it permanently irrecoverable.
2. Use per-tenant keysets to support GDPR right-to-erasure (Article 17) by deleting a tenant's keyset.
The encrypted data remains in your database but can never be decrypted.
Key rotation [#key-rotation]
CipherStash supports key rotation at the keyset level. Key rotation generates new key material without interrupting running applications.
For rotation procedures, see [Keysets](/stack/cipherstash/kms/keysets).
Security architecture [#security-architecture]
For a complete description of the cryptographic design, key hierarchy, and trust model, see [Security architecture](/stack/reference/security-architecture).
# Supabase dashboard integration
The CipherStash dashboard can connect to your Supabase account with OAuth, guide Stack onboarding for a specific project, and register Supabase as an OIDC provider for identity-aware encryption.
Use this when you want a guided setup path instead of configuring everything from the CLI alone.
Where to find it [#where-to-find-it]
In the [CipherStash Dashboard](https://dashboard.cipherstash.com):
1. Open a workspace
2. Go to **Settings → Integrations**
3. Click **Connect Supabase** (or **Open setup** if already connected)
Connected workspaces also have a dedicated setup page at:
```
/workspaces/{workspaceId}/settings/integrations/supabase
```
Connect Supabase (OAuth) [#connect-supabase-oauth]
The dashboard uses Supabase OAuth with PKCE. Tokens are encrypted at rest in the CipherStash database and scoped to the workspace you connect from.
Start OAuth [#start-oauth]
On **Settings → Integrations**, click **Connect Supabase**. You are redirected to Supabase to authorize the CipherStash OAuth app.
Complete authorization [#complete-authorization]
After you approve access, the dashboard stores encrypted access and refresh tokens for that workspace and redirects you to the Supabase setup hub.
Disconnect (optional) [#disconnect-optional]
From the setup hub, click **Disconnect** to remove the integration from the workspace. The dashboard marks the integration disconnected immediately and revokes the refresh token in the background.
OAuth scopes [#oauth-scopes]
The integration requests these Supabase OAuth scopes:
| Scope | Purpose |
| --------------- | -------------------------------------------------------------- |
| `projects:read` | List projects and read project metadata via the Management API |
| `database:read` | Run read-only SQL checks (EQL version, encrypted columns) |
Configure these scopes on your Supabase OAuth app. The dashboard does **not** request `secrets:read`.
Setup hub [#setup-hub]
After connecting, the setup hub helps you onboard a specific Supabase project with Stack.
Project selection [#project-selection]
The hub lists Supabase projects available to the connected account. Select the project you want to encrypt. Readiness checks and onboarding commands update when you change projects.
If you arrived from the Supabase Marketplace with a pre-selected project, that project is chosen automatically via a `?project=` query parameter.
Readiness checks [#readiness-checks]
For the selected project, the dashboard verifies:
| Check | What it means |
| ------------------------------------- | ------------------------------------------------------------ |
| **Project services healthy** | Supabase project health from the Management API |
| **EQL installed** | `SELECT eql_v2.version() AS version;` succeeds |
| **Encrypted columns detected** | Columns using `eql_v2_encrypted` in `information_schema` |
| **Supabase OIDC provider configured** | A Supabase issuer is registered on the CipherStash workspace |
When project health, EQL, and OIDC are all satisfied, the hub shows a **Stack-ready** badge.
EQL detection uses `eql_v2.version()`, not a `pg_extension` lookup. If EQL is missing, run `npx stash db install --supabase --migration` in your application repo.
Configure OIDC from the dashboard [#configure-oidc-from-the-dashboard]
If Supabase OIDC is not registered yet, click **Configure OIDC for this project** on the readiness panel.
The dashboard registers a Supabase OIDC provider on your CipherStash workspace with issuer:
```
https://{projectRef}.supabase.co/auth/v1
```
This enables [identity-aware encryption](/stack/cipherstash/encryption/identity) with Supabase Auth JWTs via `LockContext`.
See [OIDC Providers](/stack/cipherstash/kms/oidc) for manual registration and usage from code.
Stack onboarding commands [#stack-onboarding-commands]
The hub copies the official Supabase + Stack workflow:
```bash
npm install @cipherstash/stack
npm install -D stash
npx stash auth login
npx stash init --supabase
npx stash db install --supabase --migration
```
It also generates a `.env.local` snippet for the selected project, including:
* `DATABASE_URL` (Postgres host `db.{projectRef}.supabase.co`)
* `SUPABASE_URL` and `SUPABASE_ANON_KEY` (when available from the Management API)
* Comments for local dev (`~/.cipherstash/`) vs production (`CS_*` access keys)
For the full CLI and SDK path, see [Supabase](/stack/cipherstash/supabase).
Encrypted schema [#encrypted-schema]
When the selected project already has `eql_v2_encrypted` columns, the hub lists them by schema, table, and column name.
Install from the Supabase Marketplace [#install-from-the-supabase-marketplace]
CipherStash supports the [Supabase partner integration guide](https://supabase.com/docs/guides/integrations/partner-integration-guide) for the **Install Integration** button in the Supabase Marketplace.
There are two redirect methods. Both create a one-time install record and send the user to a handler page where they pick a CipherStash workspace and complete OAuth.
Method 1 — Simple redirect [#method-1--simple-redirect]
Supabase redirects the user to:
```
GET {APP_URL}/api/integrations/supabase/install?project_id={ref}&organization_slug={slug}
```
The API creates an install record and redirects to:
```
{APP_URL}/integrations/supabase/install/{integrationId}
```
Method 2 — Signed redirect [#method-2--signed-redirect]
Supabase POSTs a signed JWT to the same install endpoint:
```http
POST {APP_URL}/api/integrations/supabase/install
Content-Type: application/json
{ "token": "" }
```
On success, the API returns:
```json
{
"integrationId": "...",
"redirectUrl": "{APP_URL}/integrations/supabase/install/{integrationId}",
"expiresAt": "..."
}
```
Supabase redirects the user to `redirectUrl`.
Install handler flow [#install-handler-flow]
1. User lands on `/integrations/supabase/install/{integrationId}`
2. If not signed in to CipherStash, they authenticate first
3. User selects the workspace where Supabase should be connected
4. OAuth completes and binds the install record to that workspace (bind-once)
5. User lands on the setup hub, optionally with the Supabase project pre-selected
Install links expire. If a link is invalid or expired, return to Supabase and click **Install Integration** again.
The normal in-dashboard **Connect Supabase** button does not use partner install records. Partner logic runs only when an `installId` is present in the OAuth flow.
Production configuration (CipherStash operators) [#production-configuration-cipherstash-operators]
These environment variables configure the dashboard deployment (for example on Vercel):
```bash
NEXT_PUBLIC_APP_URL="https://dashboard.cipherstash.com"
# Supabase OAuth (Connect button)
SUPABASE_OAUTH_CLIENT_ID="..."
SUPABASE_OAUTH_CLIENT_SECRET="..."
# Optional — defaults to {NEXT_PUBLIC_APP_URL}/api/integrations/supabase/callback
# SUPABASE_OAUTH_REDIRECT_URI="..."
# Supabase Marketplace signed redirect (Method 2)
SUPABASE_PARTNER_JWT_AUDIENCE="https://dashboard.cipherstash.com"
SUPABASE_PARTNER_JWT_PUBLIC_KEYS='{"pik_xxx":"-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"}'
```
Register the OAuth callback URL on your Supabase OAuth app:
```
{NEXT_PUBLIC_APP_URL}/api/integrations/supabase/callback
```
Values to provide Supabase (Marketplace) [#values-to-provide-supabase-marketplace]
| Item | Example (production) |
| ------------------------ | --------------------------------------------------------------------------------- |
| Redirect record endpoint | `https://dashboard.cipherstash.com/api/integrations/supabase/install` |
| Redirect handler pattern | `https://dashboard.cipherstash.com/integrations/supabase/install/{integrationId}` |
| JWT `aud` claim | `https://dashboard.cipherstash.com` |
Verify signed redirect is enabled:
```bash
curl -I https://dashboard.cipherstash.com/api/integrations/supabase/install
```
When JWT verification is configured, the response includes:
```
X-Supabase-Partner-Signed-Redirect: enabled
```
Database migrations [#database-migrations]
The dashboard requires these migrations:
* `supabase_integrations` — encrypted OAuth tokens per workspace
* `supabase_partner_installs` — one-time Marketplace install records
* `supabase_oauth_sessions` — PKCE OAuth state keyed by `state`
Apply them before enabling the integration in production.
Security notes [#security-notes]
* OAuth tokens are encrypted at rest; integration queries run under organization RLS
* Partner install IDs bind to a single workspace on first use (prevents hijacking)
* OAuth PKCE state is stored in the database keyed by OAuth `state` (multi-tab safe)
* Sign-in and sign-up redirects are validated as internal paths only
* `NEXT_PUBLIC_APP_URL` is required in production (Host header is not trusted)
* Public install endpoints are rate limited
* Partner JWTs are verified with `ES256`, a 5-minute max age, and required claims
Next steps [#next-steps]
* [Supabase](/stack/cipherstash/supabase) — CLI setup, EQL install, and SDK integration paths
* [Supabase JS SDK integration](/stack/cipherstash/encryption/supabase) — `encryptedSupabase` API reference
* [OIDC Providers](/stack/cipherstash/kms/oidc) — Register and use identity providers
* [Identity-aware encryption](/stack/cipherstash/encryption/identity) — Bind encryption to user JWTs
* [Going to production](/stack/deploy/going-to-production) — `CS_*` credentials for deployed apps
# Discovery session
Discovery session [#discovery-session]
A discovery session is a structured 60-minute conversation between your engineering or security team and CipherStash. The goal is to map your data security requirements to the right integration path and identify anything that needs attention before you start building.
This page differs from the [planning guide](/stack/reference/planning-guide). The planning guide is self-serve technical reading you do before or after a session. This page is preparation for the conversation itself.
Who should attend [#who-should-attend]
Bring the people who can answer questions about your data architecture and compliance requirements. Typically:
* An engineer who owns the data layer or ORM setup
* A security, compliance, or privacy lead (if separate from engineering)
You do not need to have any CipherStash code written yet.
What to prepare [#what-to-prepare]
Work through the following before the session. You do not need written answers. Thinking through these areas in advance makes the conversation more productive.
Current data security posture [#current-data-security-posture]
* Which sensitive fields does your application store (PII, payment data, health records)?
* Are those fields encrypted today? If so, at what layer (disk, TLS, application)?
* Do you have column-level or field-level encryption anywhere?
Regulated data inventory [#regulated-data-inventory]
* Which regulations apply to your data (GDPR, HIPAA, PCI-DSS, SOC 2, BDSG)?
* Which specific fields are in scope for each regulation?
* Do you have data residency requirements (EU-only, US-only)?
Target outcomes [#target-outcomes]
* What is the threat model you are trying to address (breach, insider access, accidental exposure)?
* Do you need searchable encrypted fields, or encrypt-only?
* Do you need per-user encryption (identity-aware, lock contexts)?
* What does success look like at 30 days, 90 days?
Architecture constraints [#architecture-constraints]
* Which database are you using (PostgreSQL self-hosted, Supabase, RDS, DynamoDB)?
* Which ORM or query layer sits above it (Drizzle, Prisma, raw SQL, Supabase JS SDK)?
* Do you use a connection proxy or PgBouncer?
* What is your deployment environment (Vercel, AWS Lambda, containers, bare metal)?
* Do you have restrictions on native Node.js modules or binary dependencies?
What to expect during the session [#what-to-expect-during-the-session]
1. **Context gathering (15 min).** The CipherStash team walks through the areas above with you. No slides, no sales deck.
2. **Integration path recommendation (20 min).** Based on your database and ORM, the team recommends one of: Proxy (zero code changes), Encryption SDK (application-layer control), Drizzle adapter, or Supabase wrapper. See [the PostgreSQL options overview](/stack/cipherstash/postgres) for a preview of this decision.
3. **Key questions and blockers (15 min).** Open discussion about anything that could block adoption: compliance requirements, deployment constraints, managed database limitations.
4. **Next steps (10 min).** Concrete actions for both sides, with timelines.
You will leave with a clear recommended path, answers to your blockers, and a point of contact for technical questions during your trial.
Book a session [#book-a-session]
[Contact the CipherStash team](https://cipherstash.com/contact) to schedule a discovery session.
# Drizzle adapter reference
`@cipherstash/stack/drizzle` integrates CipherStash field-level encryption with Drizzle ORM. It provides a custom column type for encrypted fields and drop-in query operators that encrypt search values before they reach PostgreSQL. This page covers the operators, batching patterns, and migration generation. The step-by-step integration guide is at [Drizzle integration guide](/stack/cipherstash/encryption/drizzle). Full type signatures live in the [auto-generated API reference](/stack/reference/stack/latest/packages/stack/src/drizzle).
Public entry points [#public-entry-points]
| Export | Purpose |
| --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| [`encryptedType`](/stack/reference/stack/latest/packages/stack/src/drizzle/functions/encryptedType) | Custom Drizzle column type for an encrypted field. Accepts a `dataType` and index config. |
| [`extractEncryptionSchema`](/stack/reference/stack/latest/packages/stack/src/drizzle/functions/extractEncryptionSchema) | Converts a Drizzle `pgTable` definition into a CipherStash `EncryptedTable` schema for the SDK. |
| [`createEncryptionOperators`](/stack/reference/stack/latest/packages/stack/src/drizzle/functions/createEncryptionOperators) | Returns an object with all Drizzle query operators wrapped for encrypted columns. |
| [`EncryptedColumnConfig`](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig) | Type alias for the column configuration object (`dataType`, `equality`, `freeTextSearch`, `orderAndRange`, `searchableJson`). |
| [`EncryptionConfigError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionConfigError) | Thrown when a column lacks the index required by an operator. |
| [`EncryptionOperatorError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError) | Thrown for operator-level failures (invalid arguments, unsupported operations). |
Encrypted query operators [#encrypted-query-operators]
`createEncryptionOperators` returns a set of operators that mirror the standard Drizzle operator names. Each operator encrypts the search value before constructing the SQL fragment.
**Key pattern:** Most operators are async. `await` the operator call in the `.where()` clause, or pass un-awaited operators to `encryptionOps.and()` / `encryptionOps.or()` for batching.
| Operator | EQL function / mechanism | Required column index | Notes |
| -------------------------------- | ------------------------------------- | ---------------------- | ----------------------------------------------- |
| `eq(col, value)` | PostgreSQL `=` on `eql_v2_encrypted` | `equality: true` | Also accepts `orderAndRange: true` |
| `ne(col, value)` | PostgreSQL `!=` on `eql_v2_encrypted` | `equality: true` | Also accepts `orderAndRange: true` |
| `like(col, pattern)` | Bloom filter via `eql_v2_encrypted` | `freeTextSearch: true` | Case sensitivity depends on token filter config |
| `ilike(col, pattern)` | Bloom filter via `eql_v2_encrypted` | `freeTextSearch: true` | Case sensitivity depends on token filter config |
| `notIlike(col, pattern)` | Bloom filter via `eql_v2_encrypted` | `freeTextSearch: true` | |
| `gt(col, value)` | `eql_v2.gt()` ORE function | `orderAndRange: true` | |
| `gte(col, value)` | `eql_v2.gte()` ORE function | `orderAndRange: true` | |
| `lt(col, value)` | `eql_v2.lt()` ORE function | `orderAndRange: true` | |
| `lte(col, value)` | `eql_v2.lte()` ORE function | `orderAndRange: true` | |
| `between(col, min, max)` | `eql_v2.gte()` + `eql_v2.lte()` | `orderAndRange: true` | Inclusive |
| `notBetween(col, min, max)` | ORE negation | `orderAndRange: true` | |
| `inArray(col, values)` | Multiple equality encryptions | `equality: true` | |
| `notInArray(col, values)` | Multiple equality encryptions | `equality: true` | |
| `asc(col)` | ORE sort expression | `orderAndRange: true` | Sync, no `await` needed |
| `desc(col)` | ORE sort expression | `orderAndRange: true` | Sync, no `await` needed |
| `jsonbPathExists(col, path)` | `eql_v2.jsonb_path_exists()` | `searchableJson: true` | Returns boolean for use in `WHERE` |
| `jsonbPathQueryFirst(col, path)` | `eql_v2.jsonb_path_query_first()` | `searchableJson: true` | Returns encrypted value for use in `SELECT` |
| `jsonbGet(col, path)` | `->` operator on `eql_v2_encrypted` | `searchableJson: true` | Returns encrypted value for use in `SELECT` |
Non-encrypted columns fall back to the standard Drizzle operator automatically.
Sorting encrypted columns with `asc()` or `desc()` requires operator family support in the database. On managed databases (Supabase, RDS) or when EQL is installed with `--exclude-operator-family`, sort application-side after decrypting instead.
```typescript filename="queries.ts"
import { drizzle } from "drizzle-orm/postgres-js"
import { Encryption } from "@cipherstash/stack"
import { extractEncryptionSchema, createEncryptionOperators } from "@cipherstash/stack/drizzle"
import { usersTable } from "./schema"
import postgres from "postgres"
const usersSchema = extractEncryptionSchema(usersTable)
const client = await Encryption({ schemas: [usersSchema] })
const ops = createEncryptionOperators(client)
const db = drizzle({ client: postgres(process.env.DATABASE_URL!) })
// Equality lookup
const exact = await db
.select()
.from(usersTable)
.where(await ops.eq(usersTable.email, "alice@example.com"))
// Range query on an encrypted number column
const adults = await db
.select()
.from(usersTable)
.where(await ops.gte(usersTable.age, 18))
```
Batching conditions with and and or [#batching-conditions-with-and-and-or]
Passing multiple operators to `encryptionOps.and()` or `encryptionOps.or()` batches all encryption into a single ZeroKMS call. This is more efficient than `await`ing each operator separately.
Pass each operator without `await` as an argument to `and()` or `or()`, then `await` the outer call.
```typescript filename="batched-query.ts"
// All three encryptions happen in one ZeroKMS call
const results = await db
.select()
.from(usersTable)
.where(
await ops.and(
ops.gte(usersTable.age, 18),
ops.lte(usersTable.age, 65),
ops.ilike(usersTable.email, "%@example.com"),
),
)
```
Both `and()` and `or()` filter out `undefined` conditions, which makes conditional query building safe:
```typescript filename="conditional-query.ts"
const results = await db
.select()
.from(usersTable)
.where(
await ops.and(
searchEmail ? ops.ilike(usersTable.email, `%${searchEmail}%`) : undefined,
ops.gte(usersTable.age, minAge),
),
)
```
EQL migration generation [#eql-migration-generation]
`extractEncryptionSchema` produces a CipherStash schema object from your Drizzle table. The `@cipherstash/cli` uses this schema to generate the EQL database migration that installs the required PostgreSQL indexes.
Run the migration generator after defining your table:
```bash
npx @cipherstash/cli db install
```
The CLI reads your Drizzle config and calls `extractEncryptionSchema` internally to determine which columns need EQL indexes. It then produces a timestamped SQL migration file in your Drizzle migrations directory.
See the [CipherStash CLI reference](/stack/cipherstash/cli) for all `db install` options.
Cross-links [#cross-links]
* Integration guide: [Drizzle integration guide](/stack/cipherstash/encryption/drizzle)
* Index types: [Encrypted indexes](/stack/cipherstash/encryption/indexes)
* Query patterns: [Encrypted queries](/stack/cipherstash/encryption/queries)
* PostgreSQL setup: [Postgres setup](/stack/cipherstash/postgres)
Full API surface [#full-api-surface]
Everything else is in the auto-generated TypeDoc reference:
* [Drizzle module](/stack/reference/stack/latest/packages/stack/src/drizzle) — all exports
* [`encryptedType`](/stack/reference/stack/latest/packages/stack/src/drizzle/functions/encryptedType) — column builder
* [`extractEncryptionSchema`](/stack/reference/stack/latest/packages/stack/src/drizzle/functions/extractEncryptionSchema) — schema conversion
* [`createEncryptionOperators`](/stack/reference/stack/latest/packages/stack/src/drizzle/functions/createEncryptionOperators) — operator factory
* [`EncryptedColumnConfig`](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig) — column config type
# Encryption SDK reference
`@cipherstash/stack` is CipherStash's field-level encryption SDK for TypeScript. It encrypts individual column values client-side using per-value keys derived from ZeroKMS (backed by AWS KMS), before data leaves the application. This page summarises the public surface, data type rules, and configuration options. Full type signatures live in the [auto-generated API reference](/stack/reference/stack/latest/packages/stack/src/encryption).
Public entry points [#public-entry-points]
| Export | Import path | Purpose |
| ------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Encryption(config)` | `@cipherstash/stack` | Factory function. Returns a `Promise`. [Reference](/stack/reference/stack/latest/packages/stack/src/encryption/functions/Encryption) |
| `EncryptionClient` | `@cipherstash/stack/encryption` | Class with all encrypt/decrypt methods. Obtain via `Encryption()`, not `new`. [Reference](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient) |
| `encryptedTable` / `encryptedColumn` / `encryptedField` | `@cipherstash/stack/schema` | Schema builders. Define which tables and columns to encrypt, and which search indexes to create. [Reference](/stack/reference/stack/latest/packages/stack/src/schema) |
| `LockContext` | `@cipherstash/stack/identity` | Identity-aware encryption. Ties an encrypted value to a specific JWT identity. [Reference](/stack/reference/stack/latest/packages/stack/src/identity) |
| `Secrets` | `@cipherstash/stack/secrets` | End-to-end encrypted secret storage. Separate from field-level encryption. [Reference](/stack/reference/stack/latest/packages/stack/src/types-public) |
| Error types | `@cipherstash/stack/errors` | `StackError`, `EncryptionErrorTypes`, `getErrorMessage`. [Reference](/stack/reference/stack/latest/packages/stack/src/types-public) |
Adapter packages [#adapter-packages]
| Adapter | Import path | Guide |
| ----------- | ----------------------------- | -------------------------------------------------------- |
| Drizzle ORM | `@cipherstash/stack/drizzle` | [Drizzle guide](/stack/cipherstash/encryption/drizzle) |
| DynamoDB | `@cipherstash/stack/dynamodb` | [DynamoDB guide](/stack/cipherstash/encryption/dynamodb) |
| Supabase | `@cipherstash/stack/supabase` | [Supabase guide](/stack/cipherstash/encryption/supabase) |
Supported data types [#supported-data-types]
Each encrypted column has a declared `dataType`. This tells the SDK how to serialise the value before encryption and how to deserialise it after decryption.
| Data type | `dataType()` value | Cast required? |
| ------------ | -------------------- | -------------- |
| String | `"string"` (default) | No |
| Text (alias) | `"text"` | No |
| Number | `"number"` | Yes |
| Bigint | `"bigint"` | Yes |
| Boolean | `"boolean"` | Yes |
| Date | `"date"` | Yes |
| JSON | `"json"` | Yes |
**Why casting is required for non-string types.** The schema describes the shape of a column but not the runtime value. When you encrypt a `number`, the SDK serialises it as a number so that ORE (Order-Revealing Encryption) indexes preserve ordering. Without an explicit `dataType`, the SDK defaults to string serialisation. Mixing declared types and actual values produces decryption errors, so always set `dataType` when the column holds a non-string value.
```typescript filename="schema.ts"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch(),
age: encryptedColumn("age").dataType("number").orderAndRange(),
metadata: encryptedColumn("metadata").dataType("json").searchableJson(),
})
```
Special-value handling [#special-value-handling]
Some numeric inputs are invalid for encryption. The following table covers the behaviours documented and validated by the SDK.
| Input | Result |
| ------------------------ | ---------------------------------- |
| `NaN` | Error (rejected before encryption) |
| `Infinity` / `-Infinity` | Error (rejected before encryption) |
Passing `NaN` or `Infinity` to an encrypted numeric column throws at the operation level, not at the type level. TypeScript will not catch these at compile time. Validate your inputs before calling `encrypt` or `encryptModel`.
ProtectClientConfig highlights [#protectclientconfig-highlights]
`Encryption()` accepts an `EncryptionClientConfig` object. The `config` field is a `ClientConfig`. All credentials fall back to environment variables when omitted.
| Option | Env variable | Description |
| -------------- | ---------------------- | ---------------------------------------------------------------------------- |
| `workspaceCrn` | `CS_WORKSPACE_CRN` | Workspace Cloud Resource Name. Format: `crn:.aws:` |
| `accessKey` | `CS_CLIENT_ACCESS_KEY` | API access key for authenticating with CipherStash |
| `clientId` | `CS_CLIENT_ID` | Client identifier generated during workspace onboarding |
| `clientKey` | `CS_CLIENT_KEY` | Client key material used for ZeroKMS encryption operations |
| `keyset` | (none) | Multi-tenant isolation. Specify `{ name: "tenant-a" }` or `{ id: "" }` |
See the full type at [EncryptionClientConfig](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptionClientConfig) and [ClientConfig](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/ClientConfig).
```typescript filename="init.ts"
import { Encryption } from "@cipherstash/stack"
import { users } from "./schema"
// Reads CS_* env vars automatically when config is omitted
const client = await Encryption({ schemas: [users] })
// Or pass credentials explicitly
const client = await Encryption({
schemas: [users],
config: {
workspaceCrn: "crn:ap-southeast-2.aws:your-workspace-id",
clientId: "your-client-id",
clientKey: "your-client-key",
accessKey: "your-access-key",
},
})
```
Logging [#logging]
Set `STASH_STACK_LOG` to control log verbosity. The SDK never logs plaintext data.
| Value | Output |
| ----------------- | ----------------------- |
| `error` (default) | Errors only |
| `info` | Info and errors |
| `debug` | Debug, info, and errors |
Full API surface [#full-api-surface]
Everything else is in the auto-generated TypeDoc reference:
* [Encryption module](/stack/reference/stack/latest/packages/stack/src/encryption) — `Encryption()`, `EncryptionClient` class, all methods
* [Schema module](/stack/reference/stack/latest/packages/stack/src/schema) — `encryptedTable`, `encryptedColumn`, `encryptedField`, type inference helpers
* [Identity module](/stack/reference/stack/latest/packages/stack/src/identity) — `LockContext`
* [Types](/stack/reference/stack/latest/packages/stack/src/types-public) — `EncryptionClientConfig`, `ClientConfig`, `EncryptOptions`, `BulkEncryptPayload`, and more
# Encrypt Query Language (EQL)
**Encrypt Query Language (EQL)** is a set of PostgreSQL types, operators, and functions that enable queries on encrypted data without decryption. EQL works seamlessly with the [CipherCell](/stack/reference/cipher-cell) format to provide searchable encryption capabilities directly in PostgreSQL.
What is EQL? [#what-is-eql]
EQL provides the database-side components needed to query encrypted data. Unlike traditional PostgreSQL extensions, EQL is implemented as a collection of types, operators, and functions, making it compatible with managed database providers like AWS RDS that restrict extension installation.
When combined with the [Encryption SDK](/stack/cipherstash/encryption) or [CipherStash Proxy](/stack/cipherstash/proxy), EQL enables:
* **Exact match queries** using encrypted equality operators
* **Range queries** with order-preserving encryption
* **Pattern matching** using encrypted Bloom filters
* **Unique constraints** on encrypted columns
* **JSON/JSONB operations** on encrypted structured data
Core components [#core-components]
The eql_v2_encrypted type [#the-eql_v2_encrypted-type]
The foundation of EQL is the `eql_v2_encrypted` data type, which stores [CipherCells](/stack/reference/cipher-cell) containing encrypted data and searchable encrypted metadata.
```sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email eql_v2_encrypted,
name eql_v2_encrypted
);
```
The `eql_v2_encrypted` type is required for searchable encryption in PostgreSQL. Regular `JSON` or `JSONB` types can store CipherCells but do not support encrypted queries.
Operators [#operators]
EQL provides PostgreSQL operators that work directly with encrypted data:
```sql
-- Exact match
SELECT * FROM users WHERE email = 'encrypted_search_value'::eql_v2_encrypted;
-- Range queries
SELECT * FROM products WHERE price > 'encrypted_value'::eql_v2_encrypted;
-- Pattern matching
SELECT * FROM documents WHERE content LIKE '%encrypted_pattern%';
```
Functions [#functions]
EQL includes functions for configuration, querying, and working with encrypted data:
Configuration functions [#configuration-functions]
Functions to set up and manage encrypted columns:
* `eql_v2.add_column()`: Initialize a column for encryption
* `eql_v2.add_search_config()`: Add searchable indexes to encrypted columns
* `eql_v2.remove_column()`: Remove column configuration
* `eql_v2.config()`: View current configuration
Query functions [#query-functions]
Functions for querying encrypted data (operator equivalents also available):
* Equality checks: `eql_v2.encrypted_eq()`
* Range comparisons: `eql_v2.encrypted_lt()`, `eql_v2.encrypted_gt()`, etc.
* Pattern matching: `eql_v2.encrypted_like()`, `eql_v2.encrypted_ilike()`
Index term extraction [#index-term-extraction]
Functions to extract searchable terms from CipherCells:
* `eql_v2.encrypted_get_hmac_256()`: Extract HMAC term for exact matches
* `eql_v2.encrypted_get_bloom_filter()`: Extract Bloom filter for pattern matching
* `eql_v2.encrypted_get_ore()`: Extract ORE term for range queries
Index types [#index-types]
EQL supports multiple searchable encryption index types. Each index type enables different query patterns:
unique: Exact match [#unique-exact-match]
Enables exact equality queries and unique constraints using HMAC-SHA256.
[Learn more about exact indexes](/stack/cipherstash/encryption/searchable-encryption#exact-match)
ore: Range queries [#ore-range-queries]
Enables range comparisons (`<`, `>`, `BETWEEN`) and ordering (`ORDER BY`) using Order Revealing Encryption.
[Learn more about range indexes](/stack/cipherstash/encryption/searchable-encryption#range--order)
match: Pattern matching [#match-pattern-matching]
Enables substring and full-text search (`LIKE`, `ILIKE`) using encrypted Bloom filters with trigrams.
[Learn more about match indexes](/stack/cipherstash/encryption/searchable-encryption#match-pattern)
ste_vec: Structured data [#ste_vec-structured-data]
Enables containment queries and JSON-style operations on encrypted arrays and JSONB data.
How it works [#how-it-works]
EQL leverages PostgreSQL's native indexing capabilities to enable efficient queries on encrypted data. The searchable encrypted metadata within [CipherCells](/stack/reference/cipher-cell) is indexed using standard PostgreSQL index types (B-tree for exact/range, GIN for pattern matching).
When a query is executed:
1. **Client-side**: The application encrypts the search value using the same encryption scheme, producing a CipherCell with the appropriate searchable encrypted metadata
2. **Database-side**: EQL operators extract and compare the searchable encrypted metadata from both the stored CipherCells and the search CipherCell
3. **Result**: Matching rows are returned without ever decrypting the data in the database
Compatibility [#compatibility]
EQL is designed to work with:
* **PostgreSQL 14+**: Full support for all EQL features
* **Managed databases**: Works with AWS RDS, Azure Database, Google Cloud SQL, and other managed PostgreSQL providers
* **CipherStash SDKs**: Integrates with all CipherStash SDKs as well as CipherStash Proxy
Unlike PostgreSQL extensions that require `CREATE EXTENSION`, EQL types and functions are installed directly into your database schema, making it compatible with managed database environments that restrict extension installation.
Related documentation [#related-documentation]
* [CipherCell format](/stack/reference/cipher-cell): The data structure used by EQL
* [Supported queries](/stack/cipherstash/encryption/searchable-encryption): Available searchable encryption schemes
# Error handling
All async methods in the SDK return a `Result` object — a discriminated union with either a `data` key (success) or a `failure` key (error). You never get both.
The one exception is `Encryption()` initialization, which throws on failure rather than returning a `Result`. See [Handling initialization errors](#handling-initialization-errors) below.
The Result pattern [#the-result-pattern]
```typescript title="result-check.ts"
const result = await client.encrypt("hello@example.com", {
column: users.email,
table: users,
})
if (result.failure) {
// result.failure.type: string (e.g. "EncryptionError")
// result.failure.message: string (human-readable description)
console.error(result.failure.type, result.failure.message)
} else {
// result.data: Encrypted payload
console.log(result.data)
}
```
This pattern applies to every async method: `encrypt`, `decrypt`, `encryptModel`, `decryptModel`, `bulkEncrypt`, `bulkDecrypt`, `bulkEncryptModels`, `bulkDecryptModels`, `encryptQuery`, and `LockContext.identify()`. The one exception is `Encryption()` initialization, which throws on failure.
Error types [#error-types]
| Type | When it occurs | Common causes |
| ------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `ClientInitError` | `Encryption()` initialization | Missing or invalid credentials, unreachable ZeroKMS endpoint, no schemas provided |
| `EncryptionError` | `encrypt`, `encryptModel`, `bulkEncrypt`, `encryptQuery` | Invalid plaintext for the column's data type, NaN/Infinity for numeric columns, non-string value for `freeTextSearch` column |
| `DecryptionError` | `decrypt`, `decryptModel`, `bulkDecrypt` | Corrupted ciphertext, wrong keyset, lock context mismatch (data was encrypted with a different identity) |
| `LockContextError` | `LockContext` operations | Invalid JWT, missing required claims, CTS endpoint unreachable |
| `CtsTokenError` | CTS token exchange | JWT rejected by CipherStash Token Service, expired token, CTS endpoint misconfigured |
Handling initialization errors [#handling-initialization-errors]
Client initialization is the most common place to encounter errors. Unlike other SDK operations, `Encryption()` throws on failure rather than returning a `Result`.
```typescript title="init-error.ts"
import { Encryption } from "@cipherstash/stack"
import { users } from "./schema"
try {
const client = await Encryption({ schemas: [users] })
} catch (error) {
// Check your CS_* environment variables
console.error("Failed to initialize:", error.message)
process.exit(1)
}
```
Common initialization issues [#common-initialization-issues]
| Symptom | Cause | Fix |
| ------------------------- | ------------------------------ | ------------------------------------------------ |
| `"Missing workspace CRN"` | `CS_WORKSPACE_CRN` not set | Set the env var or pass `workspaceCrn` in config |
| `"Missing client ID"` | `CS_CLIENT_ID` not set | Set the env var or pass `clientId` in config |
| `"Missing client key"` | `CS_CLIENT_KEY` not set | Set the env var or pass `clientKey` in config |
| `"Missing access key"` | `CS_CLIENT_ACCESS_KEY` not set | Set the env var or pass `accessKey` in config |
| Connection timeout | ZeroKMS endpoint unreachable | Check network connectivity and firewall rules |
Handling encrypt/decrypt errors [#handling-encryptdecrypt-errors]
```typescript title="encrypt-error.ts"
const encrypted = await client.encrypt(value, {
column: users.email,
table: users,
})
if (encrypted.failure) {
switch (encrypted.failure.type) {
case "EncryptionError":
// Log the error and decide whether to retry or fail
console.error("Encryption failed:", encrypted.failure.message)
break
}
}
```
Common encrypt/decrypt issues [#common-encryptdecrypt-issues]
| Symptom | Cause | Fix |
| ---------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------- |
| `"NaN is not a valid value"` | Passed `NaN` to a numeric column | Validate inputs before encrypting |
| `"Infinity is not a valid value"` | Passed `Infinity` to a numeric column | Validate inputs before encrypting |
| `"Expected string value"` | Non-string value for a column with `.freeTextSearch()` | Convert to string or remove the `freeTextSearch` index |
| Decryption fails with lock context error | Data was encrypted with a different identity | Ensure the same user's JWT is used for both encrypt and decrypt |
| Decryption returns corrupted data error | Wrong keyset or tampered ciphertext | Verify the keyset matches the one used during encryption |
Handling bulk operation errors [#handling-bulk-operation-errors]
Bulk operations (`bulkDecrypt`, `bulkDecryptModels`) support **per-item error handling**. The overall operation succeeds but individual items may fail.
```typescript title="bulk-error.ts"
const result = await client.bulkDecrypt(encryptedPayloads)
if (result.failure) {
// Entire operation failed (e.g., ZeroKMS unreachable)
console.error("Bulk decrypt failed:", result.failure.message)
} else {
// Per-item results
for (const item of result.data) {
if ("data" in item) {
console.log(`${item.id}: decrypted successfully`)
} else {
// Individual item failed
console.error(`${item.id}: ${item.error}`)
}
}
}
```
Handling identity errors [#handling-identity-errors]
When using [identity-aware encryption](/stack/cipherstash/encryption/identity), errors can occur during JWT identification or when using lock contexts.
```typescript title="identity-error.ts"
import { LockContext } from "@cipherstash/stack/identity"
const lc = new LockContext()
const identifyResult = await lc.identify(userJwt)
if (identifyResult.failure) {
switch (identifyResult.failure.type) {
case "LockContextError":
// JWT is invalid or missing required claims
console.error("Identity error:", identifyResult.failure.message)
break
case "CtsTokenError":
// Token exchange with CTS failed
console.error("CTS error:", identifyResult.failure.message)
break
}
}
```
Common identity issues [#common-identity-issues]
| Symptom | Cause | Fix |
| ------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `LockContextError` with missing claim | JWT doesn't contain the configured identity claim | Check your JWT contains the `sub` claim (or your custom claims) |
| `CtsTokenError` with connection error | CTS endpoint unreachable | Verify `CS_CTS_ENDPOINT` is set correctly |
| `CtsTokenError` with rejection | JWT is expired or not trusted by CTS | Ensure JWT is fresh and from a [configured identity provider](/stack/cipherstash/kms/cts) |
Error type constants and utilities [#error-type-constants-and-utilities]
The `@cipherstash/stack/errors` subpath exports runtime constants, the `StackError` union type, and a utility for safely extracting error messages.
StackError union type [#stackerror-union-type]
`StackError` is a discriminated union of all error types, enabling exhaustive `switch` handling:
```typescript title="error-types.ts"
import { EncryptionErrorTypes, type StackError, getErrorMessage } from "@cipherstash/stack/errors"
function handleError(error: StackError) {
switch (error.type) {
case EncryptionErrorTypes.ClientInitError:
console.error("Init failed:", error.message)
break
case EncryptionErrorTypes.EncryptionError:
console.error("Encrypt failed:", error.message, error.code)
break
case EncryptionErrorTypes.DecryptionError:
console.error("Decrypt failed:", error.message)
break
case EncryptionErrorTypes.LockContextError:
console.error("Lock context failed:", error.message)
break
case EncryptionErrorTypes.CtsTokenError:
console.error("CTS token failed:", error.message)
break
default:
// TypeScript ensures exhaustiveness
const _exhaustive: never = error
}
}
```
getErrorMessage [#geterrormessage]
Use `getErrorMessage` to safely extract a message from any thrown value:
```typescript title="get-error-message.ts"
import { getErrorMessage } from "@cipherstash/stack/errors"
try {
await client.encrypt("data", { column: users.email, table: users })
} catch (e) {
console.error(getErrorMessage(e))
}
```
Wrapping errors in application code [#wrapping-errors-in-application-code]
Create a helper that converts `Result` into thrown errors for frameworks that expect exceptions (e.g., Express, Next.js API routes):
```typescript title="helpers.ts"
function unwrap(result: { data?: T; failure?: { type: string; message: string } }): T {
if (result.failure) {
throw new Error(`[${result.failure.type}] ${result.failure.message}`)
}
return result.data as T
}
// Usage
const encrypted = unwrap(await client.encrypt("hello", { column: users.email, table: users }))
```
The `Result` pattern is intentionally designed to avoid thrown exceptions. Use the `unwrap` helper only at your application's boundaries where exceptions are expected.
Logging [#logging]
Control SDK log verbosity with the `STASH_STACK_LOG` environment variable:
```bash
STASH_STACK_LOG=error # debug | info | error (default: error)
```
| Value | What is logged |
| ------- | ----------------------- |
| `error` | Errors only (default) |
| `info` | Info and errors |
| `debug` | Debug, info, and errors |
The SDK never logs plaintext data or key material at any log level.
# Glossary
A [#a]
ABAC (Attribute Based Access Control) [#abac-attribute-based-access-control]
ABAC is a dynamic security model that makes access decisions based on a variety of attributes, including user characteristics, resource details, and environmental factors.
Unlike [RBAC](#rbac-role-based-access-control), which relies solely on a user's assigned role, ABAC offers more granular and context-aware policies that adapt to changing conditions and diverse scenarios.
Access key [#access-key]
A persistent authentication credential used for communication with [ZeroKMS](#zerokms) or [CTS](#cts-cipherstash-token-service).
See [Access keys](/stack/cipherstash/kms/access-keys) for details.
Account management [#account-management]
Activities to administer your CipherStash account, like billing, adding, and removing users.
C [#c]
Ciphertext [#ciphertext]
An encrypted version of plaintext, produced by applying an encryption algorithm (a cipher). It is unreadable without a cipher to decrypt it.
See also: [Plaintext](#plaintext)
CipherStash CLI [#cipherstash-cli]
The command line tool for interacting with CipherStash services.
CipherStash Proxy [#cipherstash-proxy]
A database proxy that sits between an application and a database, enhancing your existing database with encryption in use.
CipherStash Proxy works in-tandem with your existing infrastructure and is fully contained within your environment.
See [CipherStash Proxy](/stack/cipherstash/proxy) for details.
Client key [#client-key]
The cryptographic credential assigned to a programmatic access point for a [keyset](#keyset). A client key can have access to many keysets, and a keyset can also be shared by multiple client keys.
There are two types of client keys:
* **Device-backed client keys** are created automatically by `npx stash init` and tied to a developer's [device](#device). Used for local development.
* **Application client keys** are created in the Dashboard for production and CI/CD. Identified by a [client ID](#client-id) and [client key](#client-key) set via environment variables.
See [Client keys](/stack/cipherstash/kms/clients) for details.
Client ID [#client-id]
A unique identifier for a [client key](#client-key). Each client key and client ID is unique to your app.
Client key value [#client-key-value]
The secret credential set via `CS_CLIENT_KEY`. Together with the [client ID](#client-id), it forms the credential pair for a [client key](#client-key). The client key value is sensitive and must be kept secret.
CTS (CipherStash Token Service) [#cts-cipherstash-token-service]
CTS manages the trust relationships between a workspace and third-party or customer identity providers.
It brokers secure access to CipherStash services like ZeroKMS, ensuring that only authenticated and authorized users gain entry.
See [CTS](/stack/cipherstash/kms/cts) for details.
D [#d]
Dashboard [#dashboard]
The web interface for managing CipherStash Cloud primitives — workspaces, client keys, keysets, access keys, devices, and members.
Available at [dashboard.cipherstash.com](https://dashboard.cipherstash.com/).
The Dashboard manages cloud infrastructure; product-specific configuration (encryption schemas, secrets, proxy rules) is handled by the SDKs and CLI.
Device [#device]
A unique identity tied to a developer's device and user account, created by `npx stash init`.
Each device has an associated [client key](#client-key) that is automatically granted access to the default [keyset](#keyset).
Devices are used for local development authentication — no environment variables required.
Production environments use application [client keys](#client-key) with environment variables instead.
See [Getting started](/stack/quickstart) for setup.
Data access event [#data-access-event]
An event triggered by execution of SQL statements by CipherStash Proxy. Includes metadata of statements executed and records accessed.
E [#e]
EQL (Encrypt Query Language) [#eql-encrypt-query-language]
Our [open-source library](https://github.com/cipherstash/encrypt-query-language) for PostgreSQL users. It simplifies the process of encrypting and querying sensitive data, giving you powerful tools to encrypt data transparently at the field level, query encrypted data directly using familiar SQL commands, and leverage encrypted indexes for secure and efficient searches.
See [EQL](/stack/reference/eql-guide) for details.
H [#h]
HMAC (Hash-based Message Authentication Code) [#hmac-hash-based-message-authentication-code]
A cryptographic technique that combines a hash function with a secret key to verify both the integrity and authenticity of a message.
Unlike raw hash functions (such as SHA-256), HMAC requires a secret key, which means only parties with the key can generate valid HMACs.
This prevents attackers from pre-computing hash tables (rainbow tables) or guessing values.
In searchable encryption, HMACs are used to create encrypted search tokens. The key stays on the application side, so the server can match encrypted search tokens without ever learning the plaintext or being able to generate new tokens.
I [#i]
IdP (Identity Provider) [#idp-identity-provider]
A third party identity provider, like Auth0, Okta, or Clerk.
J [#j]
JWT (JSON Web Token) [#jwt-json-web-token]
JWT is a compact, URL-safe means of representing claims between two parties as a JSON object, typically used for authentication and authorization.
They are digitally signed to ensure the integrity and authenticity of the information, allowing systems to verify user identity without maintaining server-side sessions.
K [#k]
Keyset [#keyset]
A core CipherStash Cloud primitive for cryptographic isolation, managed by [ZeroKMS](#zerokms).
Each keyset maintains its own set of data encryption keys — data encrypted under one keyset cannot be decrypted with another.
Use keysets for tenant isolation, environment separation, regional compliance, or any cryptographic boundary your architecture requires.
A [client key](#client-key) can access many keysets, and a keyset can be shared by multiple client keys.
In the Secrets SDK, [environments](#environment) map directly to keysets.
See [Keysets](/stack/cipherstash/kms/keysets) for details.
O [#o]
OIDC (OpenID Connect) [#oidc-openid-connect]
OIDC is an identity layer built on top of the OAuth 2.0 protocol that enables applications to verify user identity through an [Identity Provider](#idp-identity-provider).
It facilitates secure single sign-on (SSO) and simplifies the authentication process by allowing the Identity Provider to share standardized identity information using RESTful APIs and [JSON Web Tokens](#jwt-json-web-token) (JWTs).
ORE (Order Revealing Encryption) [#ore-order-revealing-encryption]
A searchable encryption technique allowing for search, comparison, and sorting of encrypted data without decryption.
See [Range queries](/stack/cipherstash/encryption/searchable-encryption#range--order) for details.
P [#p]
Plaintext [#plaintext]
Unencrypted information, readable by humans and computers.
R [#r]
RBAC (Role Based Access Control) [#rbac-role-based-access-control]
RBAC is a security model that assigns access permissions based on a user's role within an organization, streamlining the management of access rights by grouping permissions into predefined roles.
Unlike [ABAC](#abac-attribute-based-access-control), which evaluates policies based on a range of attributes, RBAC relies solely on roles to determine access.
S [#s]
Searchable encrypted metadata [#searchable-encrypted-metadata]
An encrypted data structure for finding records in encrypted columns. Essential for querying encrypted data, as it replaces the need for full table scans, improving performance.
This is a core feature of CipherStash, supporting range, exact, and match queries.
See [Supported queries](/stack/cipherstash/encryption/searchable-encryption) for details.
W [#w]
Workspace [#workspace]
CipherStash uses workspaces to keep things organized.
A workspace contains [keysets](#keyset), client keys, and configuration, and can:
* Be used to separate environments (e.g. dev and prod)
* Be shared with other users
* Be associated with a custom identity provider
See [Platform](/stack/reference) for details.
Z [#z]
ZeroKMS [#zerokms]
CipherStash's specialized key management service. ZeroKMS provides high performance batch encryption and decryption, enabling a unique encryption key per field.
See [ZeroKMS](/stack/cipherstash/kms) for details.
# API Reference
Auto-generated TypeDoc reference for CipherStash packages. For hand-written guides and examples, see the [Encryption docs](/stack/cipherstash/encryption).
Packages [#packages]
@cipherstash/stack [#cipherstashstack]
The core CipherStash SDK. Includes Encryption (`Encryption()`, `EncryptionClient`, schema builders, type inference, Lock Context), Secrets API, and integrations (Drizzle, Supabase, DynamoDB, and more).
[Browse @cipherstash/stack reference](/stack/reference/stack/latest)
EQL API [#eql-api]
PostgreSQL types, operators, and functions for the Encrypt Query Language (EQL) extension. Enables searchable encryption and encrypted queries directly in the database.
[Browse EQL API reference](/stack/reference/eql)
# Members
There are two types of memberships in CipherStash:
* Organization memberships
* Workspace memberships
Every user belongs to at least one organization, and can be invited to any number of workspaces inside that organization.
Organization memberships [#organization-memberships]
Organization memberships are used to manage which users in your organization have access to CipherStash in general.
Once a user is added to an organization, they can be granted access to any number of workspaces inside that organization.
Workspace memberships [#workspace-memberships]
Workspace memberships are used to manage which users have access to a specific workspace.
Once a user is added to a workspace, they have full access to managing the workspace, including creating and managing keysets, clients, and access keys.
Adding a member to an organization [#adding-a-member-to-an-organization]
To add a member to an organization, navigate to **Members** in the Dashboard's organization sidebar.
Adding a member to a workspace [#adding-a-member-to-a-workspace]
To add a member to a workspace, go to the [Settings](https://dashboard.cipherstash.com/workspaces/_/settings) page for your workspace in the Dashboard and grant access to a user.
Developer device onboarding [#developer-device-onboarding]
After a member is added to an organization and granted workspace access, they initialize their local development environment:
```bash
npx stash init
```
This creates a unique [device](/stack/reference/glossary#device) and [client key](/stack/reference/glossary#client-key) for that developer, with automatic access to the default [keyset](/stack/cipherstash/kms/keysets).
No environment variables are needed for local development.
See [Team onboarding](/stack/deploy/team-onboarding) for the full workflow.
# Migration guide
Migration from @cipherstash/protect [#migration-from-cipherstashprotect]
Use the following table to map the old API to the new one.
Import changes [#import-changes]
| `@cipherstash/protect` | `@cipherstash/stack` | Import path |
| ---------------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------- |
| `protect(config)` | `Encryption(config)` | `@cipherstash/stack` |
| `csTable(name, cols)` | `encryptedTable(name, cols)` | `@cipherstash/stack/schema` |
| `csColumn(name)` | `encryptedColumn(name)` | `@cipherstash/stack/schema` |
| `import { LockContext } from "@cipherstash/encryption/identify"` | `import { LockContext } from "@cipherstash/stack/identity"` | `@cipherstash/stack/identity` |
New features in @cipherstash/stack [#new-features-in-cipherstashstack]
| Feature | Import path |
| --------------- | ---------------------------- |
| `Secrets` class | `@cipherstash/stack/secrets` |
| `stash` CLI | `npx stash` |
What stays the same [#what-stays-the-same]
All method signatures on the encryption client (`encrypt`, `decrypt`, `encryptModel`, `decryptModel`, `bulkEncrypt`, `bulkDecrypt`, `bulkEncryptModels`, `bulkDecryptModels`) remain the same.
The `Result` pattern (`data` / `failure`) is unchanged.
Step-by-step [#step-by-step]
1. Install the new package:
```bash
npm install @cipherstash/stack
npm uninstall @cipherstash/protect
```
If you're using Bun, version 1.3 or later is required.
2. Update imports:
```typescript
// Before
import { protect } from "@cipherstash/protect"
import { csTable, csColumn } from "@cipherstash/protect"
// After
import { Encryption } from "@cipherstash/stack"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
```
3. Rename function calls:
```typescript
// Before
const client = await protect({ schemas: [users] })
// After
const client = await Encryption({ schemas: [users] })
```
4. Update schema definitions:
```typescript
// Before
const users = csTable("users", {
email: csColumn("email").equality().freeTextSearch(),
})
// After
const users = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch(),
})
```
Your encrypt/decrypt calls need no changes. They work the same way.
# Planning guide
This guide helps you plan a CipherStash integration. Use it to evaluate your security posture, choose the right integration path, and understand the architecture.
Key discovery areas [#key-discovery-areas]
Before starting, assess the following:
* **Current data security posture**: How do you currently encrypt sensitive data? Are you relying solely on encryption-at-rest?
* **Compliance requirements**: What regulations apply (GDPR, HIPAA, PCI-DSS, SOC2)?
* **Architecture constraints**: What databases and application frameworks are you using?
* **Security concerns**: What attack vectors are you most concerned about (breaches, insider threats, accidental exposure)?
Understanding encryption types [#understanding-encryption-types]
Encryption-at-rest [#encryption-at-rest]
Data is encrypted when stored on disk but decrypted for any operation. A database breach that exposes memory or query results reveals plaintext data.
Encryption-in-transit [#encryption-in-transit]
Data is encrypted between services (TLS) but decrypted at each endpoint. Data is exposed in application memory and during processing.
Encryption-in-use [#encryption-in-use]
Data remains encrypted during search and processing. CipherStash provides encryption-in-use through searchable encryption. You can query encrypted data without decrypting it first.
| Capability | At-rest | In-transit | In-use (CipherStash) |
| ----------------------------- | ------- | ---------- | -------------------- |
| Protects stored data | Yes | No | Yes |
| Protects data in transit | No | Yes | Yes |
| Protects data during queries | No | No | Yes |
| Searchable without decryption | No | No | Yes |
| Zero-trust architecture | No | No | Yes |
Architecture considerations [#architecture-considerations]
Zero-knowledge design [#zero-knowledge-design]
CipherStash uses a split-key architecture. [ZeroKMS](/stack/cipherstash/kms) manages an authority key that is necessary but not sufficient to derive data keys. Your application holds an independent client key. Data keys are derived locally and never leave your infrastructure.
This means:
* Plaintext data never leaves your systems
* Data keys are never transmitted over the network
* Even CipherStash cannot access your data
PostgreSQL integration [#postgresql-integration]
CipherStash is designed around PostgreSQL, the most popular open-source database. Integration requires no architecture changes:
* **[EQL](/stack/reference/eql-guide)** provides native PostgreSQL types for encrypted data
* Existing queries and applications work with minimal modification
* Performance overhead is minimal (sub-millisecond for most operations)
Integration paths [#integration-paths]
| Path | Best for | Code changes |
| ----------------------------------------------- | ---------------------------------------------------------- | --------------------------------------------------- |
| [Encryption SDK](/stack/cipherstash/encryption) | Application-layer encryption with fine-grained control | Moderate (encrypt/decrypt calls in your data layer) |
| [CipherStash Proxy](/stack/cipherstash/proxy) | Existing PostgreSQL applications with minimal code changes | Minimal (point your connection at the proxy) |
ORM support [#orm-support]
The Encryption SDK integrates with popular ORMs:
* **[Drizzle ORM](/stack/cipherstash/encryption/drizzle)**: Custom encrypted column types with query operators
* **[Supabase](/stack/cipherstash/encryption/supabase)**: Transparent encryption wrapper around the Supabase client
* **[DynamoDB](/stack/cipherstash/encryption/dynamodb)**: Encrypted attributes with searchable capabilities
ZeroKMS overview [#zerokms-overview]
[ZeroKMS](/stack/cipherstash/kms) is CipherStash's zero-trust key management service:
* **Key hierarchy**: Authority keys, client keys, and derived data keys provide defense in depth
* **Regional deployment**: Available in [multiple regions](/stack/cipherstash/kms/regions) for data sovereignty
* **Audit logging**: Complete visibility into key usage and access patterns
* **Disaster recovery**: Multi-region key replication with [automated failover](/stack/cipherstash/kms/disaster-recovery)
Development to production journey [#development-to-production-journey]
| Stage | Auth model | Setup |
| ----------------- | --------------------- | ------------------------------------------------------ |
| Local development | Device-based | `npx stash init` per developer — no env vars needed |
| Staging / CI | Environment variables | Application client key + `CS_*` env vars |
| Production | Environment variables | Application client key + `CS_*` env vars (member role) |
Local development uses device-based auth for zero-config setup and per-developer identity.
Each developer is uniquely identified via their device, providing audit trails and individual access control.
Staging and production use environment variables because there is no interactive device.
See [Going to production](/stack/deploy/going-to-production) for the full transition guide and [Team onboarding](/stack/deploy/team-onboarding) for setting up your development team.
Enterprise features [#enterprise-features]
Lock contexts [#lock-contexts]
[Lock contexts](/stack/cipherstash/encryption/identity) provide identity-aware encryption. Bind encryption operations to authenticated users using JWTs for:
* Temporal access control: time-limited access to sensitive data
* Provable access boundaries: cryptographic proof of who accessed what
* Audit trails: complete record of all data access events
Multi-tenant encryption [#multi-tenant-encryption]
Cryptographic tenant isolation ensures complete separation between tenants. Each tenant's data is encrypted with independent key material, meeting strict data residency and compliance requirements.
Next steps [#next-steps]
1. **Get started**: Run `npx stash init` to set up [device-based auth](/stack/quickstart)
2. **Choose your integration path**: [Encryption SDK](/stack/quickstart) or [CipherStash Proxy](/stack/cipherstash/proxy/getting-started)
3. **Build locally**: Device auth handles credentials automatically — focus on your integration
4. **Go to production**: Set up [application client keys and env vars](/stack/deploy/going-to-production) for deployment
# Error reference
This page covers every error CipherStash Proxy can return, grouped by category, with steps to diagnose and resolve each one.
Authentication errors [#authentication-errors]
Database authentication failed [#database-authentication-failed]
**Error:** `Database authentication failed. Check username and password`
The proxy could not authenticate with the upstream PostgreSQL database.
1. Check that the configured username and password are correct and can connect to the database.
2. Check that the database uses a supported authentication method. Supported methods: `password`, `md5`, `scram-sha-256`. See [PostgreSQL password authentication](https://www.postgresql.org/docs/17/auth-password.html).
Client authentication failed [#client-authentication-failed]
**Error:** `Client authentication failed. Check username and password.`
The proxy rejected the client connection because the credentials were incorrect.
Check that the username and password your application sends match the values configured in the proxy.
ZeroKMS authentication failed [#zerokms-authentication-failed]
**Error:** `ZeroKMS authentication failed. Check the configured credentials.`
The proxy could not authenticate with CipherStash ZeroKMS.
1. Check that the configured `client` credentials are correct.
2. Check that the active `keyset_name` or `keyset_id` is associated with a keyset in the configured workspace.
***
Mapping errors [#mapping-errors]
Invalid parameter [#invalid-parameter]
**Error:** `Invalid parameter for column 'column_name' of type 'cast' in table 'table_name'. (OID 'oid')`
Each encrypted column definition includes a target `cast` type. When encrypting a parameter or literal, the proxy decodes and casts the data into that type. This error means the passed data cannot be decoded and cast to the expected type.
In some cases, parameter types are automatically converted. For example, PostgreSQL `INT2`, `INT4`, and `INT8` are converted to the corresponding encrypted integer types.
Check that the parameter or literal matches the type configured for the encrypted column.
Invalid SQL statement [#invalid-sql-statement]
**Error:** `sql parser error: Expected: SELECT, VALUES, or a subquery in the query body` (varies)
The proxy could not parse the SQL statement.
Check that the statement is valid PostgreSQL SQL. If you believe the statement is correct and the parser is wrong, contact [CipherStash support](https://cipherstash.com/support).
Unsupported parameter type [#unsupported-parameter-type]
**Error:** `Encryption of PostgreSQL {name} (OID {oid}) types is not currently supported.`
The data type of the parameter is not supported for encryption.
Check the supported types for encrypted columns.
Statement could not be type checked [#statement-could-not-be-type-checked]
**Error:** `Statement could not be type checked: '{type-check-error-message}'`
The proxy checks SQL statements against the database schema to transparently encrypt and decrypt data. The behavior when a type check fails depends on the `mapping_errors_enabled` configuration:
* When `mapping_errors_enabled` is `false` (default): the error is logged and the statement passes through to the database.
* When `mapping_errors_enabled` is `true`: the error is raised and statement execution halts.
The default behavior passes statements through because most production systems have a small number of encrypted columns, and blocking on false negatives causes more harm. When a statement does pass through, the database's EQL column constraints catch invalid data and return an error like:
```sql
ERROR: Encrypted column missing version (v) field: 34234
CONTEXT: PL/pgSQL function _cs_encrypted_check_v(jsonb) line 6 at RAISE
SQL function "cs_check_encrypted_v1" statement 1
```
Check that you are running the latest version of CipherStash Proxy. Contact [CipherStash support](https://cipherstash.com/support) if the error persists.
Internal mapper error [#internal-mapper-error]
**Error:** `Statement encountered an internal error. This may be a bug in the statement mapping module of CipherStash Proxy.`
An unexpected error occurred in the statement mapping module.
Update to the latest version. Contact [CipherStash support](https://cipherstash.com/support) if the error persists.
***
Encrypt errors [#encrypt-errors]
Column could not be encrypted [#column-could-not-be-encrypted]
**Error:** `Column 'column_name' in table 'table_name' could not be encrypted.`
The proxy uses CipherStash ZeroKMS for encryption operations. The most likely cause is a network issue reaching the ZeroKMS service.
1. Check that CipherStash ZeroKMS is available at the [status page](https://status.cipherstash.com/).
2. Check that the proxy has network access to ZeroKMS in the appropriate region.
3. Check that the encrypted configuration `cast` matches the expected type.
KeysetId could not be parsed [#keysetid-could-not-be-parsed]
**Error:** `KeysetId '{id}' could not be parsed using 'SET CIPHERSTASH.KEYSET_ID'. KeysetId should be a valid UUID.`
The value passed to `SET CIPHERSTASH.KEYSET_ID` is not a valid UUID.
Check that the `KeysetId` is a valid UUID. The correct syntax is:
```sql
SET [SESSION] CIPHERSTASH.KEYSET_ID { TO | = } '{KeysetId}'
```
KeysetId could not be set [#keysetid-could-not-be-set]
**Error:** `Keyset Id could not be set using 'SET CIPHERSTASH.KEYSET_ID'`
1. Check the syntax: the `keyset_id` value must be in single quotes.
2. Check that the `keyset_id` is a valid UUID.
3. Check that the value is a literal. PostgreSQL `SET` does not support parameterised queries.
KeysetName could not be set [#keysetname-could-not-be-set]
**Error:** `Keyset Name could not be set using 'SET CIPHERSTASH.KEYSET_NAME'`
1. Check the syntax: the `keyset_name` value must be in single quotes.
2. Check that the value is a literal. PostgreSQL `SET` does not support parameterised queries.
Unknown keyset identifier [#unknown-keyset-identifier]
**Error:** `Unknown keyset name or id '{keyset}'`
The proxy could not find a keyset matching the name or ID.
1. Check that the active `keyset_name` or `keyset_id` is associated with a keyset in the configured workspace.
2. Check that the configured `client` has been granted access to the keyset in the dashboard.
3. Keyset names are case-sensitive. Check for an exact match.
4. Check that the configured `client` credentials are correct.
Could not decrypt data for keyset [#could-not-decrypt-data-for-keyset]
**Error:** `Could not decrypt data for keyset '{keyset_id}'`
The active `keyset_id` does not match the `keyset_id` of the data being decrypted. Each encrypted record belongs to a keyset with a unique identifier. The proxy encrypts data using the currently active keyset. If the record's `keyset_id` does not match the active `keyset_id`, the data cannot be decrypted.
You can set a default keyset in the proxy configuration, or use `SET CIPHERSTASH.KEYSET_ID` to set the active keyset at runtime.
1. Check that the `keyset_id` in the configuration matches the `keyset_id` of the encrypted records.
2. If using `SET CIPHERSTASH.KEYSET_ID`, check that the value matches the encrypted records.
3. Check that the configured `client` has been granted access to the `keyset_id`.
Plaintext could not be encoded [#plaintext-could-not-be-encoded]
**Error:** `Decrypted column could not be encoded as the expected type.`
Each encrypted column definition includes a target `cast` type. When the proxy decrypts data, it casts and encodes the result as the PostgreSQL representation of that type. Changing the encrypted column definition after data has been written can cause this error.
1. Check that the encrypted configuration has the correct type.
2. Check that the column configuration has not changed since the data was written.
Unknown column [#unknown-column]
**Error:** `Column 'column_name' in table 'table_name' has no Encrypt configuration`
The column has an encrypted type (`eql_v2_encrypted`) but no encryption configuration has been defined.
Define the encrypted configuration using EQL:
```sql
SELECT cs_add_column_v1('users', 'email');
```
Unknown table [#unknown-table]
**Error:** `Table 'table_name' has no Encrypt configuration`
The table has one or more encrypted columns but no encryption configuration has been defined.
Define the encrypted configuration using EQL:
```sql
SELECT cs_add_column_v1('users', 'email');
```
Unknown index term [#unknown-index-term]
**Error:** `Unknown Index Term for column '{column_name}' in table '{table_name}'.`
The encrypted column has an unrecognised index configuration. EQL validates indexes when they are added, but direct database changes can misconfigure the setup.
Check the Encrypt configuration for the column and redefine it if necessary.
Column configuration mismatch [#column-configuration-mismatch]
**Error:** `Column configuration for column '{column_name}' in table '{table_name}' does not match the encrypted column.`
The proxy validates that encrypted columns match their configuration before decrypting. This check prevents confused deputy attacks and should not appear during normal operation. Contact [CipherStash support](https://cipherstash.com/support) if it persists.
***
Decrypt errors [#decrypt-errors]
Column could not be deserialised [#column-could-not-be-deserialised]
**Error:** `Column 'column_name' in table 'table_name' could not be deserialised.`
The proxy stores encrypted data and search terms as `jsonb`. This error indicates an internal issue deserialising and extracting the ciphertext for decryption. It may occur if the encrypted data has been altered by another process.
Check that the data in the encrypted column is in the correct EQL format. Contact [CipherStash support](https://cipherstash.com/support) if the error persists.
***
Configuration errors [#configuration-errors]
Missing or invalid TLS configuration [#missing-or-invalid-tls-configuration]
**Errors:**
```
Invalid Transport Layer Security (TLS) certificate.
Invalid Transport Layer Security (TLS) private key.
Missing Transport Layer Security (TLS) certificate at path: {path}.
Missing Transport Layer Security (TLS) private key at path: {path}.
```
For path-based configuration, check that the certificate and key exist at the specified paths and are valid. For PEM-based configuration, check that the certificate and key values are valid.
Network configuration change requires restart [#network-configuration-change-requires-restart]
**Error:** `Network configuration change requires restart`
When the proxy receives a `SIGHUP` signal, it reloads application-level configuration without disrupting active connections. However, the following network settings require a full restart:
* `server.host`
* `server.port`
* `server.require_tls`
* `server.worker_threads`
* `tls` (any TLS configuration)
Application-level settings (`database`, `auth`, `encrypt`, `log`, `prometheus`, `development`) can be reloaded without a restart using `SIGHUP`.
Stop the proxy, update the configuration, then restart the proxy.
# Reference
Configuration loading order [#configuration-loading-order]
Proxy accepts configuration from a TOML file, environment variables, or both.
1. If `cipherstash-proxy.toml` exists in the current working directory, Proxy reads it first.
2. Proxy then reads any environment variables that are set.
3. When both are present, environment variables override values from the TOML file.
Config options [#config-options]
[server] [#server]
| Option | Env variable | Default | Description |
| -------------------------- | ------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------- |
| `host` | `CS_SERVER__HOST` | `0.0.0.0` | Proxy listen address |
| `port` | `CS_SERVER__PORT` | `6432` | Proxy listen port |
| `require_tls` | `CS_SERVER__REQUIRE_TLS` | `false` | Enforce TLS for client-to-proxy connections |
| `shutdown_timeout` | `CS_SERVER__SHUTDOWN_TIMEOUT` | `2000` | Milliseconds to wait for connections to drain on shutdown |
| `worker_threads` | `CS_SERVER__WORKER_THREADS` | `NUMBER_OF_CORES/2` or `4` | Number of server worker threads |
| `thread_stack_size` | `CS_SERVER__THREAD_STACK_SIZE` | `2097152` (2 MiB) | Thread stack size in bytes. Automatically set to 4 MiB when log level is `debug` or `trace` |
| `cipher_cache_size` | `CS_SERVER__CIPHER_CACHE_SIZE` | `64` | Maximum number of encryption/decryption operations to cache |
| `cipher_cache_ttl_seconds` | `CS_SERVER__CIPHER_CACHE_TTL_SECONDS` | `3600` | Seconds before cached cipher operations expire |
[database] [#database]
| Option | Env variable | Default | Description |
| ------------------------ | ------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `host` | `CS_DATABASE__HOST` | `0.0.0.0` | Database host address |
| `port` | `CS_DATABASE__PORT` | `5432` | Database port |
| `name` | `CS_DATABASE__NAME` | (required) | Database name |
| `username` | `CS_DATABASE__USERNAME` | (required) | Database username |
| `password` | `CS_DATABASE__PASSWORD` | (required) | Database password |
| `connection_timeout` | `CS_DATABASE__CONNECTION_TIMEOUT` | none | Milliseconds to hold an idle connection open. In production, set this higher than your application's connection pool idle timeout |
| `with_tls_verification` | `CS_DATABASE__WITH_TLS_VERIFICATION` | `false` | Enable TLS verification between Proxy and the database |
| `config_reload_interval` | `CS_DATABASE__CONFIG_RELOAD_INTERVAL` | `60` | Seconds between encrypted index configuration reloads |
| `schema_reload_interval` | `CS_DATABASE__SCHEMA_RELOAD_INTERVAL` | `60` | Seconds between database schema reloads |
[tls] [#tls]
Configure TLS for client-to-proxy connections. Provide either file paths or inline PEM strings.
| Option | Env variable | Description |
| ------------------ | -------------------------- | ------------------------------------------ |
| `certificate_path` | `CS_TLS__CERTIFICATE_PATH` | Path to the public certificate `.crt` file |
| `private_key_path` | `CS_TLS__PRIVATE_KEY_PATH` | Path to the private key file |
| `certificate_pem` | `CS_TLS__CERTIFICATE_PEM` | Public certificate as a PEM string |
| `private_key_pem` | `CS_TLS__PRIVATE_KEY_PEM` | Private key as a PEM string |
[auth] [#auth]
| Option | Env variable | Description |
| ------------------- | ---------------------- | ----------------------------- |
| `workspace_crn` | `CS_WORKSPACE_CRN` | CipherStash Workspace CRN |
| `client_access_key` | `CS_CLIENT_ACCESS_KEY` | CipherStash Client Access Key |
[encrypt] [#encrypt]
| Option | Env variable | Description |
| ------------------- | ---------------------- | ---------------------- |
| `default_keyset_id` | `CS_DEFAULT_KEYSET_ID` | CipherStash Dataset ID |
| `client_id` | `CS_CLIENT_ID` | CipherStash Client ID |
| `client_key` | `CS_CLIENT_KEY` | CipherStash Client Key |
[log] [#log]
| Option | Env variable | Default | Description |
| ---------------------------------- | ------------------------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------ |
| `level` | `CS_LOG__LEVEL` | `info` | Global log level. Valid values: `error`, `warn`, `info`, `debug`, `trace` |
| `format` | `CS_LOG__FORMAT` | `pretty` (terminal), `structured` (non-terminal) | Log format. Valid values: `pretty`, `text`, `structured` (JSON) |
| `output` | `CS_LOG__OUTPUT` | `stdout` | Log output destination. Valid values: `stdout`, `stderr` |
| `ansi_enabled` | `CS_LOG__ANSI_ENABLED` | `true` (terminal), `false` (non-terminal) | Enable colored output |
| `slow_statements` | `CS_LOG__SLOW_STATEMENTS` | `false` | Enable slow statement logging |
| `slow_statement_min_duration_ms` | `CS_LOG__SLOW_STATEMENT_MIN_DURATION_MS` | `2000` | Minimum duration in milliseconds for a statement to be considered slow |
| `slow_db_response_min_duration_ms` | `CS_LOG__SLOW_DB_RESPONSE_MIN_DURATION_MS` | `100` | Minimum duration in milliseconds for a database response to be considered slow |
[prometheus] [#prometheus]
| Option | Env variable | Default | Description |
| --------- | ------------------------ | ------- | -------------------------------------- |
| `enabled` | `CS_PROMETHEUS__ENABLED` | `false` | Enable the Prometheus metrics exporter |
| `port` | `CS_PROMETHEUS__PORT` | `9930` | Port for the Prometheus exporter |
Full TOML example [#full-toml-example]
```toml filename="cipherstash-proxy.toml"
[server]
host = "0.0.0.0"
port = "6432"
require_tls = "false"
shutdown_timeout = "2000"
worker_threads = "4"
thread_stack_size = "2097152"
cipher_cache_size = "64"
cipher_cache_ttl_seconds = "3600"
[database]
host = "0.0.0.0"
port = "5432"
name = "database"
username = "username"
password = "password"
connection_timeout = "300000"
with_tls_verification = "false"
config_reload_interval = "60"
schema_reload_interval = "60"
[tls]
certificate_path = "./server.crt"
private_key_path = "./server.key"
[auth]
workspace_crn = "cipherstash-workspace-crn"
client_access_key = "cipherstash-client-access-key"
[encrypt]
default_keyset_id = "cipherstash-dataset-id"
client_id = "cipherstash-client-id"
client_key = "cipherstash-client-key"
[log]
level = "info"
format = "pretty"
output = "stdout"
ansi_enabled = "true"
slow_statements = "false"
slow_statement_min_duration_ms = "2000"
slow_db_response_min_duration_ms = "100"
[prometheus]
enabled = "false"
port = "9930"
```
Development settings [#development-settings]
Default settings are tuned for production. When running Proxy locally, override these for a better development experience. None of these settings are appropriate for production.
Enable colored, human-readable logs:
```bash
CS_LOG__FORMAT="pretty"
CS_LOG__ANSI_ENABLED="true"
```
Reduce reload intervals when iterating on your schema:
```bash
CS_DATABASE__CONFIG_RELOAD_INTERVAL="10"
CS_DATABASE__SCHEMA_RELOAD_INTERVAL="10"
```
Slow query logging [#slow-query-logging]
Slow query logging helps you identify statements that take longer than expected. Enable it with:
```bash
CS_LOG__SLOW_STATEMENTS="true"
```
By default, any statement taking longer than 2000 ms is flagged. Tune the thresholds to match your latency expectations:
```bash
CS_LOG__SLOW_STATEMENT_MIN_DURATION_MS="500" # statement total time
CS_LOG__SLOW_DB_RESPONSE_MIN_DURATION_MS="200" # database round-trip only
```
When a slow statement is detected, Proxy emits a structured log line at the `SLOW_STATEMENTS` target. With `format = "pretty"`, it looks like:
```
WARN slow_statement duration_ms=620 query="SELECT * FROM users WHERE ..."
```
Use these log lines to identify queries that need indexes or to spot unexpectedly slow EQL operations. To isolate slow-statement output in production, set `CS_LOG__SLOW_STATEMENTS_LEVEL=warn` while keeping the global level at `error`.
Docker-specific options [#docker-specific-options]
These environment variables are only available in the Docker container image and are not present in the binary.
| Env variable | Description |
| ------------------------------------------ | ------------------------------------------------------- |
| `CS_DATABASE__INSTALL_EQL` | Install EQL on container startup |
| `CS_DATABASE__INSTALL_EXAMPLE_SCHEMA` | Load the example schema on container startup |
| `CS_DATABASE__INSTALL_AWS_RDS_CERT_BUNDLE` | Add the AWS RDS certificate bundle for TLS verification |
`CS_DATABASE__INSTALL_EQL` and `CS_DATABASE__INSTALL_EXAMPLE_SCHEMA` are for development use only. Install EQL using the [installation script](https://github.com/cipherstash/encrypt-query-language/releases) as a database migration in production.
Per-target log levels [#per-target-log-levels]
Override the log level for specific internal components using the pattern `CS_LOG__{TARGET}_LEVEL`. These environment variables accept the same values as `CS_LOG__LEVEL`.
| Env variable | Target | Description |
| ------------------------------- | ----------------- | ------------------------------------------------------ |
| `CS_LOG__DEVELOPMENT_LEVEL` | `DEVELOPMENT` | General development logging |
| `CS_LOG__AUTHENTICATION_LEVEL` | `AUTHENTICATION` | Client authentication and SASL |
| `CS_LOG__CONFIG_LEVEL` | `CONFIG` | Configuration loading |
| `CS_LOG__CONTEXT_LEVEL` | `CONTEXT` | Connection context |
| `CS_LOG__ENCODING_LEVEL` | `ENCODING` | Data encoding and decoding |
| `CS_LOG__ENCRYPT_LEVEL` | `ENCRYPT` | Encryption operations |
| `CS_LOG__DECRYPT_LEVEL` | `DECRYPT` | Decryption operations |
| `CS_LOG__ENCRYPT_CONFIG_LEVEL` | `ENCRYPT_CONFIG` | Encryption configuration loading |
| `CS_LOG__ZEROKMS_LEVEL` | `ZEROKMS` | ZeroKMS cipher initialization, cache, and connectivity |
| `CS_LOG__MIGRATE_LEVEL` | `MIGRATE` | Schema migration |
| `CS_LOG__PROTOCOL_LEVEL` | `PROTOCOL` | PostgreSQL wire protocol |
| `CS_LOG__MAPPER_LEVEL` | `MAPPER` | SQL statement mapping and transformation |
| `CS_LOG__SCHEMA_LEVEL` | `SCHEMA` | Database schema analysis |
| `CS_LOG__SLOW_STATEMENTS_LEVEL` | `SLOW_STATEMENTS` | Slow statement detection |
CLI reference [#cli-reference]
```bash
cipherstash-proxy [OPTIONS] [DBNAME] [COMMAND]
```
Commands [#commands]
| Command | Description |
| --------- | ----------------------------------------------- |
| `encrypt` | Encrypt existing plaintext data in the database |
| `help` | Print help information |
Arguments and options [#arguments-and-options]
| Flag | Default | Description |
| ------------------------------------------- | ------------------------------------------------ | ------------------------------------ |
| `DBNAME` | | Database name (positional, optional) |
| `-H, --db-host ` | `127.0.0.1` | Database host |
| `-u, --db-user ` | `postgres` | Database user |
| `-p, --config-file-path ` | `cipherstash-proxy.toml` | Path to the TOML config file |
| `-l, --log-level ` | `info` | Log level |
| `-f, --log-format ` | `pretty` (terminal), `structured` (non-terminal) | Log format |
Prometheus metrics [#prometheus-metrics]
Enable the Prometheus exporter by setting `CS_PROMETHEUS__ENABLED=true`. Metrics are exposed on port `9930` by default.
| Metric | Type | Description |
| --------------------------------------------------------- | --------- | ----------------------------------------------------------------- |
| `cipherstash_proxy_keyset_cipher_cache_hits_total` | Counter | Keyset-scoped cipher cache hits |
| `cipherstash_proxy_keyset_cipher_cache_miss_total` | Counter | Cipher cache misses requiring initialization |
| `cipherstash_proxy_keyset_cipher_init_total` | Counter | New keyset-scoped cipher initializations |
| `cipherstash_proxy_keyset_cipher_init_duration_seconds` | Histogram | Duration of cipher initialization, including ZeroKMS network call |
| `cipherstash_proxy_clients_active_connections` | Gauge | Current active client connections |
| `cipherstash_proxy_clients_bytes_received_total` | Counter | Bytes received from clients |
| `cipherstash_proxy_clients_bytes_sent_total` | Counter | Bytes sent to clients |
| `cipherstash_proxy_decrypted_values_total` | Counter | Individual values decrypted |
| `cipherstash_proxy_decryption_duration_seconds` | Histogram | Time spent performing decryption |
| `cipherstash_proxy_decryption_duration_seconds_count` | Counter | Number of decryption timing observations |
| `cipherstash_proxy_decryption_duration_seconds_sum` | Counter | Total cumulative decryption time |
| `cipherstash_proxy_decryption_error_total` | Counter | Unsuccessful decryption operations |
| `cipherstash_proxy_decryption_requests_total` | Counter | ZeroKMS decrypt requests |
| `cipherstash_proxy_encrypted_values_total` | Counter | Individual values encrypted |
| `cipherstash_proxy_encryption_duration_seconds` | Histogram | Time spent performing encryption |
| `cipherstash_proxy_encryption_duration_seconds_count` | Counter | Number of encryption timing observations |
| `cipherstash_proxy_encryption_duration_seconds_sum` | Counter | Total cumulative encryption time |
| `cipherstash_proxy_encryption_error_total` | Counter | Unsuccessful encryption operations |
| `cipherstash_proxy_encryption_requests_total` | Counter | ZeroKMS encrypt requests |
| `cipherstash_proxy_rows_encrypted_total` | Counter | Encrypted rows returned to clients |
| `cipherstash_proxy_rows_passthrough_total` | Counter | Non-encrypted rows returned to clients |
| `cipherstash_proxy_rows_total` | Counter | Total rows returned to clients |
| `cipherstash_proxy_server_bytes_received_total` | Counter | Bytes received from the PostgreSQL server |
| `cipherstash_proxy_server_bytes_sent_total` | Counter | Bytes sent to the PostgreSQL server |
| `cipherstash_proxy_statements_execution_duration_seconds` | Histogram | Database SQL execution time |
| `cipherstash_proxy_statements_session_duration_seconds` | Histogram | Total statement processing time (encrypt + execute + decrypt) |
| `cipherstash_proxy_statements_encrypted_total` | Counter | Statements requiring encryption |
| `cipherstash_proxy_statements_passthrough_total` | Counter | Statements not requiring encryption |
| `cipherstash_proxy_statements_total` | Counter | Total statements processed |
| `cipherstash_proxy_statements_unmappable_total` | Counter | Statements that could not be mapped |
Supported architectures [#supported-architectures]
CipherStash Proxy is available as a Docker container image for `linux/arm64` architectures.
# Security architecture
This page is the single reference for CipherStash's cryptographic design. It covers the key hierarchy, encryption primitives, trust model, and data flow so security teams can evaluate CipherStash without piecing together information from multiple sources.
Cryptographic primitives [#cryptographic-primitives]
| Purpose | Algorithm | Details |
| ------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Data encryption | AES-GCM-SIV | 256-bit keys, authenticated encryption with nonce-misuse resistance |
| Equality search tokens | HMAC-SHA-256 | Deterministic tokens for exact-match lookups |
| Range and sorting indexes | Block ORE | Order-Revealing Encryption ([Lewi-Wu 2016](https://eprint.iacr.org/2016/612)) with security enhancements from [Bogatov et al 2018](https://arxiv.org/abs/1810.05135) |
| Free-text search indexes | Encrypted Bloom filters | Trigram-based tokenization ([Nojima/Kadobayashi 2009](https://link.springer.com/chapter/10.1007/978-3-642-04474-8_17), [Chum/Zhang 2017](https://link.springer.com/chapter/10.1007/978-3-319-66399-9_15)) |
| Ciphertext integrity | Blake3 | Fast cryptographic hash for CipherCell structure validation |
| Ciphertext encoding | MessagePack + Base85 | Compact binary serialization stored as JSONB in PostgreSQL |
All encryption happens client-side in the application process. CipherStash infrastructure never sees plaintext data.
Key hierarchy [#key-hierarchy]
CipherStash uses a **distributed trust model** with a dual-party key split. No single party (including CipherStash) can derive data encryption keys alone.
Key types [#key-types]
| Key | Where it lives | Purpose |
| ----------------- | ----------------------------------- | ----------------------------------------------------------------- |
| **Authority key** | ZeroKMS (server-side, HSM-friendly) | Server half of the key derivation input |
| **Client key** | Your application / workload | Client half of the key derivation input (`CS_CLIENT_KEY` env var) |
| **Data key** | Ephemeral (in-memory only) | Per-value encryption key, derived from both halves, never stored |
Key derivation [#key-derivation]
ZeroKMS uses an **HMAC-based key derivation scheme**:
1. The SDK sends a key derivation request to ZeroKMS with the client key component.
2. ZeroKMS combines the authority key and client key to produce a **key seed**.
3. The SDK uses the key seed to derive a unique data encryption key locally.
4. The data key encrypts the value using AES-GCM-SIV, then is immediately discarded.
Data keys are **ephemeral**: they are never stored, cached, or transmitted. This eliminates wrapped-key storage overhead and removes the risk of key cache compromise.
Why this matters [#why-this-matters]
| Traditional KMS | CipherStash ZeroKMS |
| --------------------------------------- | -------------------------------------------------------------------------- |
| Wrapped data keys stored alongside data | Data keys are ephemeral (never stored) |
| Key caching required for performance | No caching needed. HMAC derivation at 10,000+ ops/sec per node |
| Single point of trust (KMS provider) | Distributed trust: both authority key and client key required |
| Compromising KMS exposes all data keys | Compromising ZeroKMS alone is insufficient. Attacker also needs client key |
Trust model [#trust-model]
CipherStash implements a **zero-knowledge architecture**:
* **CipherStash never sees plaintext data.** All encryption and decryption happens in your application using the native Rust FFI module.
* **CipherStash never sees unwrapped data keys.** Data keys are derived ephemerally and exist only in the client's memory.
* **Both parties are required.** An attacker must compromise both the ZeroKMS authority key and the client key to derive data keys.
Shared responsibility [#shared-responsibility]
| Your responsibility | CipherStash responsibility |
| ---------------------------------------------- | ----------------------------------------------------- |
| Protect the client key (`CS_CLIENT_KEY`) | Protect authority keys in ZeroKMS (HSM-backed) |
| Secure your application and database | Operate ZeroKMS infrastructure with high availability |
| Manage access keys and keysets | Enforce access control policies on keysets |
| Configure identity providers for lock contexts | Operate the CipherStash Token Service (CTS) |
| Store encrypted data in your database | Never store, access, or log plaintext data |
Blast radius [#blast-radius]
CipherStash uses [keysets](/stack/cipherstash/kms/keysets) to scope encryption keys:
* Each keyset provides **full cryptographic isolation**. Tenant A's keyset cannot decrypt Tenant B's data.
* Compromising a single client key affects only the keysets that application has access to.
* Revoking an application's access key immediately prevents further key derivation requests.
Data flow [#data-flow]
Encryption (write path) [#encryption-write-path]
1. Application calls `client.encrypt(plaintext, { column, table })`.
2. SDK sends a key derivation request to ZeroKMS (over TLS).
3. ZeroKMS combines authority key + client key → returns key seed.
4. SDK derives a unique data key from the seed locally.
5. SDK encrypts the plaintext using AES-GCM-SIV with the data key.
6. SDK generates search index tokens (HMAC, ORE, Bloom filter) if the column has indexes configured.
7. SDK packages ciphertext + index tokens into a [CipherCell](/stack/reference/cipher-cell) (JSON payload).
8. Data key is discarded from memory.
9. Application stores the CipherCell in PostgreSQL (as `eql_v2_encrypted` or `jsonb`).
Decryption (read path) [#decryption-read-path]
1. Application reads the CipherCell from PostgreSQL.
2. Application calls `client.decrypt(encryptedData)`.
3. SDK sends a key derivation request to ZeroKMS.
4. ZeroKMS combines authority key + client key → returns key seed.
5. SDK derives the data key and decrypts the ciphertext.
6. Data key is discarded from memory.
7. Plaintext is returned to the application.
Search (query path) [#search-query-path]
1. Application calls `client.encryptQuery(searchTerm, { column, table, queryType })`.
2. SDK generates search tokens (HMAC for equality, ORE for range, Bloom filter for free-text) using a key derived from ZeroKMS.
3. Application sends the encrypted tokens to PostgreSQL.
4. PostgreSQL's [EQL extension](/stack/reference/eql-guide) compares encrypted tokens using the appropriate index. The database never sees plaintext.
Searchable encryption security properties [#searchable-encryption-security-properties]
Searchable encryption enables queries on encrypted data but involves inherent security tradeoffs. Each index type reveals specific, bounded information to the database server.
| Index type | What is revealed to the database | Mitigation |
| ---------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **HMAC (equality)** | Whether two encrypted values are identical (frequency information) | Per-column unique keys prevent cross-column correlation |
| **ORE (range/sort)** | Relative ordering of encrypted values | Block ORE limits leakage to block-level ordering ([Lewi-Wu 2016](https://eprint.iacr.org/2016/612)) |
| **Bloom filter (free-text)** | Probabilistic token membership with configurable false positive rate | Parameters `k` and `m` control the tradeoff between search accuracy and information leakage |
When to use searchable encryption [#when-to-use-searchable-encryption]
* **Use it** when you need to query encrypted data and the leakage profile is acceptable for your threat model.
* **Don't use it** when the mere ordering or frequency of values is sensitive (e.g., exact salary rankings). In those cases, encrypt without indexes and decrypt application-side.
For a deeper analysis of each index type, see [Searchable encryption](/stack/cipherstash/encryption/searchable-encryption) and [Supported queries](/stack/cipherstash/encryption/searchable-encryption).
Network security [#network-security]
All communication between the SDK and CipherStash services uses **TLS 1.2+** over HTTPS:
* SDK → ZeroKMS: Key derivation requests over HTTPS
* SDK → CTS: Token exchange requests over HTTPS (for identity-aware encryption)
* SDK → your database: Your existing database connection (CipherStash does not proxy this)
CipherStash services are deployed across [multiple AWS regions](/stack/cipherstash/kms/regions). Key material does not leave the region your workspace is configured in.
Open-source components [#open-source-components]
| Component | Repository | License |
| ---------------------------- | ------------------------------------------------------------------------------------------------------ | ----------- |
| EQL (Encrypt Query Language) | [github.com/cipherstash/encrypt-query-language](https://github.com/cipherstash/encrypt-query-language) | Open source |
| ORE implementation | [github.com/cipherstash/ore.rs](https://github.com/cipherstash/ore.rs) | Open source |
| CipherStash Proxy | [github.com/cipherstash/proxy](https://github.com/cipherstash/proxy) | Open source |
The core cryptographic implementations (ORE, Bloom filter construction) are open source and independently auditable. ZeroKMS is a managed service operated by CipherStash.
Further reading [#further-reading]
* [What is CipherStash?](/stack/reference/what-is-cipherstash): Platform overview and threat model
* [ZeroKMS](/stack/cipherstash/kms): Key management and keyset isolation
* [CipherCell format](/stack/reference/cipher-cell): Encrypted payload structure
* [Disaster recovery](/stack/cipherstash/kms/disaster-recovery): Key recovery and outage behavior
# Supported solutions
CipherStash offers multiple integration paths designed for developer productivity and production readiness.
Integration options [#integration-options]
Encryption SDK [#encryption-sdk]
**Best for**: Teams who want fine-grained control over data encryption directly in their application.
The `@cipherstash/stack` SDK provides application-layer encryption with full TypeScript support, schema-based configuration, and searchable encryption capabilities.
* **Languages**: TypeScript / JavaScript (Node.js)
* **ORMs**: [Drizzle](/stack/cipherstash/encryption/drizzle), [Supabase](/stack/cipherstash/encryption/supabase)
* **Dashboard**: [Connect Supabase from the dashboard](/stack/reference/dashboard-supabase-integration) — OAuth, EQL readiness, OIDC, and Marketplace install
* **Get started**: [Encryption SDK guide](/stack/quickstart)
CipherStash Proxy [#cipherstash-proxy]
**Best for**: DevOps teams who want to add encryption to existing PostgreSQL applications with little to no code changes.
A drop-in SQL proxy for PostgreSQL that automatically handles encryption and decryption operations.
* **Database**: PostgreSQL only
* **Get started**: [Proxy guide](/stack/cipherstash/proxy/getting-started)
Supported databases [#supported-databases]
Database compatibility [#database-compatibility]
| Database | Standard encryption | Searchable encryption |
| ----------------------------------- | ------------------- | ------------------------------------------------ |
| PostgreSQL 15+ | Yes | Yes |
| AWS RDS PostgreSQL | Yes | Yes |
| AWS Aurora PostgreSQL | Yes | Yes |
| GCP Cloud SQL for PostgreSQL | Yes | Yes |
| Azure Database for PostgreSQL | Yes | Yes |
| OCI Database Service for PostgreSQL | Yes | Yes |
| DynamoDB | Yes | Yes |
| Supabase | Yes | Yes ([EQL + Stack](/stack/cipherstash/supabase)) |
| Neon Postgres | Yes | — |
| MySQL | Yes | — |
| CockroachDB | Yes | — |
**Standard encryption** works with any database that supports JSON or JSONB column types. Encrypted values are stored as JSON objects ([CipherCells](/stack/reference/cipher-cell)).
**Searchable encryption** requires [EQL](/stack/reference/eql-guide) (for PostgreSQL) or native integration (for DynamoDB). Searchable encryption enables equality lookups, range queries, ordering, and free-text search on encrypted data without decryption.
Performance characteristics [#performance-characteristics]
SDK integration [#sdk-integration]
* **Latency**: Less than 5ms overhead for most operations
* **Throughput**: Scales with your application
* **Setup time**: Running in local dev in under 1 hour, production in under 3 days
Proxy integration [#proxy-integration]
* **Latency**: Less than 5ms overhead for most operations
* **Throughput**: Horizontally scalable based on database throughput
* **Setup time**: Operational in hours with existing PostgreSQL
Getting started [#getting-started]
1. **Choose your integration path** based on your application type and requirements
2. **Review the getting started guide** for your selected solution: [Encryption SDK](/stack/quickstart) or [Proxy](/stack/cipherstash/proxy/getting-started)
3. **Set up your development environment** with the appropriate SDK or proxy
4. **Provision credentials** in the [CipherStash Dashboard](https://dashboard.cipherstash.com/)
5. **Deploy to production** with confidence in your security posture
# What is CipherStash?
CipherStash is Data Level Access Control for Postgres. Encrypt fields at the application layer, query ciphertext without decryption, bind keys to identities, and audit every access event cryptographically.
A breach yields ciphertext, nothing useful.
The problem [#the-problem]
For thirty years, the database has been readable to anyone who reached it. Encrypting a column meant losing the query. Traditional security assumes a human is watching. AI agents ship code at machine speed. The credentials they run on are application credentials. Prompt injection is a one-step exfiltration attack.
The stack [#the-stack]
CipherStash solves this with four primitives:
/ENCRYPTION [#encryption]
Searchable field-level encryption. Range queries, exact match, and free-text fuzzy search over ciphertext with sub-millisecond overhead. Works with any managed Postgres provider. [Read more](/stack/cipherstash/encryption).
/KEY-MANAGEMENT [#key-management]
ZeroKMS. 100x faster than AWS KMS. Unique key per value, derived on demand, never stored. Identity and policy baked into every key. [Read more](/stack/cipherstash/kms).
/SECRETS (coming soon) [#secrets-coming-soon]
Secrets without the .env. Every secret encrypted at the field level. Cryptographically isolated environments. Identity-bound access. [Join the waitlist](https://cipherstash.com/stack/secrets).
/PROXY [#proxy]
Transparent searchable encryption for existing PostgreSQL databases. Zero application code changes. [Read more](/stack/cipherstash/proxy).
How it works [#how-it-works]
Zero-knowledge architecture. The platform never sees data keys.
1. Every sensitive value is encrypted with a unique key.
2. Keys are derived on demand via ZeroKMS and never stored.
3. Identity and policy are baked into the key itself.
4. Decryption is enforced at the moment of access, wherever the data ends up.
5. Every decryption event is recorded: who, what, when, from where.
Attackers, over-permissioned agents, and curious insiders all see the same thing: ciphertext with no key.
Threat model [#threat-model]
**Database breach.** Encrypted data remains ciphertext. Zero-knowledge architecture means compromised credentials yield nothing useful.
**Insider threats.** Identity-bound keys enforce per-value access control. Full audit trail for every decryption.
**Supply chain attacks.** Instant access revocation. No waiting on vendors.
**AI agent exfiltration.** Prompt injection reaches the database but decrypts nothing. The agent's credentials are not the user's keys.
Performance [#performance]
* **\< 1ms** query overhead
* **100x** faster than AWS KMS
* **100,000x** faster than fully homomorphic encryption
* **Quantum safe**
Compliance [#compliance]
HIPAA, SOC 2, GDPR. Continuous assurance, not point-in-time snapshots. Cryptographic audit trails provide proof, not just logs.
Integration paths [#integration-paths]
* **[Encryption SDK](/stack/cipherstash/encryption)** for TypeScript applications with fine-grained control.
* **[CipherStash Proxy](/stack/cipherstash/proxy)** for existing PostgreSQL apps with zero code changes.
# Programmatic API
Import `stash` directly for custom tooling or CI scripts.
EQLInstaller [#eqlinstaller]
```typescript filename="scripts/setup-db.ts"
import { EQLInstaller, loadStashConfig } from 'stash'
const config = await loadStashConfig()
const installer = new EQLInstaller({
databaseUrl: config.databaseUrl,
})
const permissions = await installer.checkPermissions()
if (!permissions.ok) {
console.error('Missing permissions:', permissions.missing)
process.exit(1)
}
if (await installer.isInstalled()) {
console.log('EQL is already installed')
} else {
await installer.install()
}
```
Methods [#methods]
| Method | Returns | Description |
| ----------------------- | -------------------------------- | --------------------------------------------------- |
| `checkPermissions()` | `Promise` | Check if the database role has required permissions |
| `isInstalled()` | `Promise` | Check if the `eql_v2` schema exists |
| `getInstalledVersion()` | `Promise` | Get the installed EQL version (or `null`) |
| `install(options?)` | `Promise` | Execute the EQL install SQL in a transaction |
Install options [#install-options]
```typescript filename="scripts/setup-db.ts"
await installer.install({
excludeOperatorFamily: true, // Skip CREATE OPERATOR FAMILY
supabase: true, // Supabase mode (implies excludeOperatorFamily + grants roles)
latest: true, // Fetch latest from GitHub instead of bundled
})
```
PermissionCheckResult [#permissioncheckresult]
```typescript filename="types.ts"
interface PermissionCheckResult {
ok: boolean
missing: string[] // Human-readable descriptions of missing permissions
}
```
loadBundledEqlSql [#loadbundledeqlsql]
Load the bundled EQL install SQL as a string for custom install workflows:
```typescript filename="scripts/custom-install.ts"
import { loadBundledEqlSql } from 'stash'
const sql = loadBundledEqlSql() // standard
const sql = loadBundledEqlSql({ supabase: true }) // supabase variant
const sql = loadBundledEqlSql({ excludeOperatorFamily: true }) // no operator family
```
downloadEqlSql [#downloadeqlsql]
Download the latest EQL install SQL from GitHub:
```typescript filename="scripts/custom-install.ts"
import { downloadEqlSql } from 'stash'
const sql = await downloadEqlSql() // standard
const sql = await downloadEqlSql(true) // no operator family variant
```
defineConfig [#defineconfig]
Type-safe identity function for `stash.config.ts`. Provides autocompletion and type checking without runtime overhead:
```typescript filename="stash.config.ts"
import { defineConfig } from 'stash'
export default defineConfig({
databaseUrl: process.env.DATABASE_URL!,
})
```
loadStashConfig [#loadstashconfig]
Finds and loads the nearest `stash.config.ts`, validates it with Zod, applies defaults, and returns the typed config:
```typescript filename="scripts/setup-db.ts"
import { loadStashConfig } from 'stash'
const config = await loadStashConfig()
// config.databaseUrl — guaranteed to be a non-empty string
// config.client — path to encryption client (default: './src/encryption/index.ts')
```
Exits with a helpful error if the config file is not found or fails validation.
# Execute an encryption plan
Execute an encryption plan [#execute-an-encryption-plan]
`stash impl` is the third step in the setup lifecycle. It reads your plan, confirms the scope, and hands off to a coding agent to make the changes. Run `stash plan` first to produce `.cipherstash/plan.md`.
```bash
npx stash impl
```
Prerequisites [#prerequisites]
`stash impl` reads `.cipherstash/context.json`, which is written by `stash init`. If that file is missing, the command errors with a message pointing you to run `stash init` first.
Behaviour overview [#behaviour-overview]
`stash impl` branches based on three conditions: whether a plan file exists, whether you are in a TTY, and whether `--continue-without-plan` is set.
| Condition | Behaviour |
| ---------------------------------- | ---------------------------------------------------------------------------------------- |
| Plan exists, TTY | Render plan summary. Ask "Proceed with implementation against this plan?" (default-yes). |
| Plan exists, non-TTY | Log the plan path and proceed without confirmation. |
| No plan, `--continue-without-plan` | Ask "Implementation can take some time. Continue?" (default-no). Proceed if confirmed. |
| No plan, TTY | Show a picker: "Draft a plan first (recommended)" or "Continue without a plan". |
| No plan, non-TTY | Error with a clear message. Requires `stash plan` or `--continue-without-plan`. |
Plan-summary confirmation [#plan-summary-confirmation]
When `.cipherstash/plan.md` exists and a machine-readable summary block is present, `stash impl` renders a panel like:
```
Plan summary
3 columns across 2 tables
◇ users.email add new encrypted column
◇ users.phone migrate existing column
◇ orders.notes migrate existing column
Includes migrate-existing columns — implementation is staged across
4 deploys (schema-add → backfill → cutover → drop).
```
It then asks:
```
Proceed with implementation against this plan? (Y/n)
```
Answering no cancels cleanly. Answering yes dispatches to the agent.
If the plan file has no summary block (older plans or plans produced without the structured header), `stash impl` shows a soft prompt to open the file in your editor before proceeding, then continues to the agent picker.
No-plan picker [#no-plan-picker]
If no plan exists and you are in a TTY (without `--continue-without-plan`), `stash impl` shows:
```
No plan found. What would you like to do?
> Draft a plan first (recommended) [runs `stash plan` — usually 1–3 min]
Continue without a plan [skip the planning checkpoint]
```
Selecting "Draft a plan first" delegates to `stash plan`. When `stash plan` completes, you return to `stash impl` automatically.
Selecting "Continue without a plan" shows the security confirm (default-no) before proceeding.
Security confirm (no-plan path) [#security-confirm-no-plan-path]
When bypassing the plan checkpoint, the command asks:
```
Implementation can take some time. Continue? (y/N)
```
This prompt defaults to no. It requires an explicit `y` or `yes`. Pressing enter cancels.
Flags [#flags]
| Flag | Description |
| ------------------------- | ---------------------------------------------------------------------------- |
| `--continue-without-plan` | Skip the no-plan picker. Goes straight to the security confirm (default-no). |
There is no `--yes` or `--force` that bypasses the security confirm. The confirm on the no-plan path always requires an explicit affirmative. This is intentional: implementation can take tens of minutes and makes real changes to your codebase.
Agent options [#agent-options]
`stash impl` offers four handoff targets:
| Agent | Detected by | Notes |
| ----------------- | ------------------- | ----------------------------------------------- |
| Claude Code | `claude` on `$PATH` | Launched interactively if detected. |
| Codex | `codex` on `$PATH` | Launched interactively if detected. |
| CipherStash Agent | Always available | Runs `stash wizard`. |
| Write AGENTS.md | Always available | Works with Cursor, Windsurf, Cline, and others. |
The command defaults to Claude Code if detected, then Codex, then AGENTS.md (the broadest "works without anything else installed" option). The CipherStash Agent is never selected by default.
Non-TTY behaviour [#non-tty-behaviour]
In CI and piped contexts, `stash impl` requires either a plan on disk or `--continue-without-plan`:
```bash
# CI with a plan already committed
npx stash impl
# CI without a plan
npx stash impl --continue-without-plan
```
Without `--continue-without-plan` and no plan on disk, the command errors:
```
No plan at `.cipherstash/plan.md`. Run `stash plan` first, or pass --continue-without-plan to skip planning.
```
After implementation [#after-implementation]
When the agent handoff completes, `stash impl` prints:
```
Implementation handoff complete. Run `stash db status` to verify state.
```
Run `stash db status` to check EQL installation and `stash encrypt status` for per-column migration state.
Next steps [#next-steps]
# CipherStash CLI
`stash` is a dev-time CLI and library for managing CipherStash EQL (Encrypted Query Language) in PostgreSQL.
`@cipherstash/stack` is the runtime encryption SDK. It stays lean with no heavy dependencies like `pg`. `stash` is a devDependency that handles database tooling: installing EQL extensions, checking permissions, validating schemas, and managing the schema lifecycle.
Think of it like Prisma CLI or Drizzle Kit. It sets up the database while the main SDK handles runtime operations.
| Package | Role | Install as |
| -------------------- | ------------------------------------ | ------------- |
| `@cipherstash/stack` | Runtime encryption and decryption | dependency |
| `stash` | Database setup and schema management | devDependency |
Quick start [#quick-start]
Interactive (recommended) [#interactive-recommended]
The setup lifecycle has three explicit save-points. Each command can be run standalone; the chain prompts after `init` and `plan` are convenience for first-time users.
Run init [#run-init]
`stash init` authenticates you, resolves your database, scaffolds an encryption client, installs dependencies, installs EQL, and writes `.cipherstash/context.json`.
```bash
npx stash init
```
When init finishes, it asks (default-yes) whether to continue to `stash plan`.
Draft an encryption plan [#draft-an-encryption-plan]
`stash plan` hands off to a coding agent (Claude Code or Codex), which reads your project and writes `.cipherstash/plan.md`. The plan lists the tables and columns to encrypt. Review it before proceeding.
```bash
npx stash plan
```
When plan finishes, it asks (default-yes) whether to continue to `stash impl`.
Execute the plan [#execute-the-plan]
`stash impl` reads the plan, shows a summary panel, asks you to confirm, and dispatches to the agent to make the changes.
```bash
npx stash impl
```
Manual setup [#manual-setup]
If you prefer to configure things yourself rather than running `stash init`, you can scaffold the config file manually and run `stash db install` directly.
Install the CLI [#install-the-cli]
```bash
npm install -D stash
```
Create stash.config.ts [#create-stashconfigts]
Create `stash.config.ts` in your project root:
```typescript filename="stash.config.ts"
import { defineConfig } from 'stash'
export default defineConfig({
databaseUrl: process.env.DATABASE_URL!,
})
```
Add your database URL [#add-your-database-url]
```bash filename=".env"
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
```
Install EQL [#install-eql]
```bash
npx stash db install
```
> **Good to know**: Using Drizzle? The CLI auto-detects a `drizzle.config.*` file or `drizzle-orm`/`drizzle-kit` in `package.json` and generates a migration automatically. You can also pass `--drizzle` explicitly. Run `npx drizzle-kit migrate` after to apply it.
Configuration [#configuration]
The `stash.config.ts` file is the single source of truth for the CLI. Use `defineConfig` for type safety.
```typescript filename="stash.config.ts"
import { defineConfig } from 'stash'
export default defineConfig({
databaseUrl: process.env.DATABASE_URL!,
client: './src/encryption/index.ts',
})
```
| Option | Required | Default | Description |
| ------------- | -------- | --------------------------- | ------------------------------------------------------------------- |
| `databaseUrl` | Yes | | PostgreSQL connection string |
| `client` | No | `./src/encryption/index.ts` | Path to your encryption client file. Used by `push` and `validate`. |
The CLI loads `.env.local`, `.env.development.local`, `.env.development`, then `.env` (first-win, Next.js precedence) before evaluating the config, so `process.env` references work without any extra setup. The config file is resolved by walking up from the current working directory, similar to how `tsconfig.json` resolution works.
Wizard [#wizard]
`stash wizard` (also available as `@cipherstash/wizard`) is the CipherStash-hosted AI setup tool. It reads your codebase, asks which columns to encrypt, and wires up `@cipherstash/stack` for you.
The primary setup path is `stash init` → `stash plan` → `stash impl`, which can hand off to Claude Code, Codex, AGENTS.md (Cursor, Windsurf, Cline), or the wizard. The wizard is one handoff target among these options.
Run it directly with:
```bash
npx stash wizard
```
Or via the package runner:
```bash
npx @cipherstash/wizard # npm / Node
pnpm dlx @cipherstash/wizard # pnpm
bunx @cipherstash/wizard # bun
yarn dlx @cipherstash/wizard # yarn
```
Prerequisites [#prerequisites]
Before running the wizard, your project should have:
* An authenticated CipherStash session (`npx stash auth login`)
* A `stash.config.ts` (run `npx stash init` or `npx stash db install` to scaffold one)
* A reachable database via `DATABASE_URL`
What the wizard does [#what-the-wizard-does]
1. Detects your framework (Drizzle, Supabase, Prisma, generic) and TypeScript usage.
2. Runs health checks against the CipherStash gateway and your database.
3. Prompts you to pick the tables and columns to encrypt.
4. Sends a prompt to the Claude Agent SDK, which edits your schema and call sites to use `@cipherstash/stack`'s encryption APIs. The agent runs against a CipherStash-hosted LLM gateway. No Anthropic API key is required.
5. Runs post-agent steps: package install, `db install`, `db push`, and framework-specific migrations.
6. Scans for remaining call sites that need `encryptModel`/`decryptModel` wiring and prints a summary. These locations are not edited automatically.
7. Offers to install integration-appropriate [agent skills](/stack/reference/agent-skills) into `./.claude/skills/`.
Wizard log [#wizard-log]
Each run writes a timestamped log to `.cipherstash/wizard-log.md` in your project root. The log records phases, decisions, and touched files.
Backfilling existing data [#backfilling-existing-data]
When the wizard adds `encryptedType` to columns that already have data, you must backfill the existing rows with `encryptModel` before dropping the old column. The wizard prints a reminder with the recommended pattern. You are responsible for writing and running this backfill.
Experimental commands [#experimental-commands]
env [#env]
The `env` command prints a `.env.production.local` block with your CipherStash credentials. Pass `--write` to write it to disk.
```bash
STASH_EXPERIMENTAL_ENV_CMD=1 npx stash env
STASH_EXPERIMENTAL_ENV_CMD=1 npx stash env --write
```
This command is experimental and gated behind the `STASH_EXPERIMENTAL_ENV_CMD=1` environment variable. It is not ready for production use.
Next steps [#next-steps]
# Interactive setup
`npx stash init` is the first step in the CipherStash setup lifecycle. It authenticates you, resolves your database, scaffolds an encryption client, installs dependencies, installs the EQL extension, and writes a context file. When it finishes, it prompts you to continue to `stash plan`.
```bash
npx stash init
```
What init does [#what-init-does]
Init runs six steps with minimal prompts:
1. **Authenticates with CipherStash**: If you are already authenticated, it logs `Using workspace (region)` and moves on. No prompt. If you are not authenticated, it opens your browser for device-based authentication. Your token is saved to `~/.cipherstash/auth.json`.
2. **Resolves the database**: Reads your `DATABASE_URL` and verifies a connection can be made. Detects your Postgres provider (Supabase from the URL host, or generic Postgres).
3. **Generates the encryption client file**: Auto-detects your integration (Drizzle from `drizzle.config.*` or `drizzle-orm`/`drizzle-kit` in `package.json`, Supabase from the `DATABASE_URL` host, or generic Postgres). Writes a placeholder client to `./src/encryption/index.ts` silently. If that file already exists, it prompts you to keep it or overwrite it.
4. **Installs dependencies**: Checks whether `@cipherstash/stack` and `stash` are already in `node_modules`. If both are present, skips silently. If either is missing, shows a single combined install prompt. Detects your package manager (npm, pnpm, yarn, bun) automatically.
5. **Installs EQL**: Runs the same logic as `stash db install`. Scaffolds `stash.config.ts` if missing, detects your provider, and installs the EQL extension into your database. You do not need to run `stash db install` separately after init.
6. **Gathers context**: Writes `.cipherstash/context.json` with the detected integration, package manager, schemas, environment keys, and available agents. This file is required by `stash plan` and `stash impl`.
Best case: 0 prompts (already authenticated, both packages installed, no existing client file, database reachable). Worst case: 2 prompts (region selection for first-time login, install confirmation for missing packages).
| Flag | Description |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `--supabase` | Changes the intro message and next-steps output to the Supabase path. Detection handles file scaffolding regardless of this flag. |
| `--drizzle` | Changes the intro message and next-steps output to the Drizzle path. Detection handles file scaffolding regardless of this flag. |
Generated files [#generated-files]
Init produces two files:
**`./src/encryption/index.ts`**: A placeholder encryption client. The template matches your detected integration. For a generic Postgres project it generates:
```typescript filename="src/encryption/index.ts"
import { Encryption } from "@cipherstash/stack"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch(),
name: encryptedColumn("name").equality().freeTextSearch(),
})
export default await Encryption({ schemas: [users] })
```
**`.cipherstash/context.json`**: Detected facts about your project. `stash plan` and `stash impl` read this file. It includes your integration type, package manager, detected schemas, environment keys, and available coding agents.
After init [#after-init]
When init completes, it shows a summary panel:
```
Setup complete
✓ Authenticated to CipherStash
✓ Database connection verified
✓ Encryption client scaffolded
✓ `@cipherstash/stack` installed
✓ `stash` CLI installed
✓ EQL extension installed
```
Then it asks (default-yes):
```
Continue to `stash plan` now to draft your encryption plan? (Y/n)
```
Answering yes launches `stash plan` immediately. Answering no (or running non-interactively) prints:
```
Next: run `stash plan` to draft your encryption plan.
```
db install flags [#db-install-flags]
If you need to run EQL installation with specific flags (for example, writing a Supabase migration file), use `stash db install` directly after init:
```bash
npx stash db install --supabase --migration
npx stash db install --drizzle
npx stash db install --drizzle --supabase
```
> **Good to know**: You can pass `--drizzle` and `--supabase` together. The CLI combines both behaviors: Supabase-compatible SQL output as a Drizzle migration.
See [Install and upgrade EQL](/stack/cipherstash/cli/install) for the full flag reference.
Next steps [#next-steps]
# Install and upgrade EQL
install [#install]
Install the CipherStash EQL extensions into your database. Uses bundled SQL by default for offline, deterministic installs.
```bash
npx stash db install [options]
```
`db install` scaffolds `stash.config.ts` if it is missing. The command:
1. **Scaffolds `stash.config.ts`** with the database URL and client path (if the file does not already exist).
2. **Scaffolds the encryption client file** at the path referenced in `stash.config.ts` if the file does not exist. Uses the same auto-detected integration template as `init` (Drizzle, Supabase, or generic Postgres). Silent, no prompt.
3. **Checks permissions** and auto-selects the right SQL variant based on your database role.
4. **Installs EQL extensions** in your database.
After a successful install, the CLI prints a summary of what was installed. If you reached this step via `stash init`, EQL was already installed as part of init. Manual invocation is useful when you need specific flags (for example, `--supabase --migration` to write a Supabase migration file) or when you skipped the database connection during init.
| Option | Description |
| --------------------------- | ---------------------------------------------------------------------------------------------------- |
| `--dry-run` | Show what would happen without making changes |
| `--force` | Reinstall even if EQL is already installed |
| `--supabase` | Use Supabase-compatible install (excludes operator families and grants Supabase roles) |
| `--migration` | Write EQL into a Supabase migration file instead of pushing directly. Requires `--supabase`. |
| `--direct` | Push EQL directly to the database, skipping the migration-file prompt. Requires `--supabase`. |
| `--migrations-dir ` | Override the Supabase migrations directory (default: `supabase/migrations/`). Requires `--supabase`. |
| `--exclude-operator-family` | Skip operator family creation (for non-superuser database roles) |
| `--drizzle` | Generate a Drizzle migration instead of direct install |
| `--latest` | Fetch the latest EQL from GitHub instead of using the bundled version |
| `--name ` | Migration name when using `--drizzle` (default: `install-eql`) |
| `--out ` | Drizzle output directory when using `--drizzle` (default: `drizzle`) |
Standard install:
```bash
npx stash db install
```
Dry run to preview changes:
```bash
npx stash db install --dry-run
```
Fetch the latest EQL from GitHub:
```bash
npx stash db install --latest
```
Auto-detection [#auto-detection]
`db install` auto-detects two things before prompting you:
* **Supabase**: If `DATABASE_URL` contains a `*.supabase.co`, `*.supabase.com`, or `*.pooler.supabase.com` host, the CLI uses the Supabase-compatible install automatically. You can still pass `--supabase` explicitly to override.
* **Drizzle**: If a `drizzle.config.*` file exists or `drizzle-orm`/`drizzle-kit` appears in `package.json`, the CLI generates a Drizzle migration automatically. You can still pass `--drizzle` explicitly.
Automatic OPE fallback [#automatic-ope-fallback]
On managed databases (Supabase, Neon, RDS), the connected role is often not a superuser. `db install` detects this and automatically uses the no-operator-family (OPE) install variant, logging a line to inform you. You do not need to pass `--exclude-operator-family` manually on these hosts.
Supabase install [#supabase-install]
```bash
npx stash db install --supabase
```
The `--supabase` flag uses the Supabase-specific SQL variant. It omits `CREATE OPERATOR FAMILY` and grants `USAGE`, table, routine, and sequence permissions on the `eql_v2` schema to `anon`, `authenticated`, and `service_role`.
When `--supabase` is passed and a `supabase/migrations/` directory is detected, the CLI prompts:
```
How should EQL be installed?
> Create a Supabase migration file (recommended)
Push directly to the database
```
The migration-file path writes EQL SQL into `supabase/migrations/00000000000000_cipherstash_eql.sql`. The all-zero timestamp prefix ensures it runs before any user migrations that reference `eql_v2_encrypted`. Use `--migration` to skip the prompt and always create the file, or `--direct` to skip the prompt and always push directly.
```bash
# Write EQL as a migration file
npx stash db install --supabase --migration
# Push EQL directly to the database
npx stash db install --supabase --direct
# Override the migrations directory
npx stash db install --supabase --migration --migrations-dir ./db/migrations
```
`--migration`, `--direct`, and `--migrations-dir` all require `--supabase` to be passed explicitly. Passing them without `--supabase` will error. `--latest` is not compatible with `--migration`.
EQL installed via direct push does not survive `supabase db reset`. The reset command drops the database and reruns only files in `supabase/migrations/`. If you use `supabase db reset`, install EQL as a migration file instead. See [Supabase db reset removes EQL](/stack/cipherstash/cli/troubleshooting#supabase-db-reset).
Without operator families, `ORDER BY` on encrypted columns is not supported, regardless of the client or ORM used. Sort application-side after decrypting results as a workaround. This limitation also applies when using `--exclude-operator-family` on any database.
Drizzle migrations [#drizzle-migrations]
If you use Drizzle ORM and want EQL installation as part of your migration history, use `--drizzle`:
```bash
npx stash db install --drizzle
npx drizzle-kit migrate
```
This process:
1. Runs `drizzle-kit generate --custom --name=` to create an empty migration.
2. Loads the bundled EQL install SQL (or downloads from GitHub with `--latest`).
3. Writes the EQL SQL into the generated migration file.
To customize the migration name and output directory:
```bash
npx stash db install --drizzle --name setup-eql --out ./migrations
```
`drizzle-kit` must be installed. The `--out` value must match your Drizzle config.
You can combine `--drizzle` and `--supabase`:
```bash
npx stash db install --drizzle --supabase
```
This generates a Supabase-compatible EQL migration file.
Encrypting existing columns [#encrypting-existing-columns]
When you add `encryptedType` to a Drizzle column that already has data, `drizzle-kit generate` emits an `ALTER TABLE ... ALTER COLUMN ... SET DATA TYPE eql_v2_encrypted` statement. Postgres cannot cast existing data to `eql_v2_encrypted` implicitly, so this migration would fail.
`db install --drizzle` automatically rewrites those statements into a safe `ADD COLUMN / DROP COLUMN / RENAME COLUMN` sequence. The rewrite adds a comment in the migration to remind you to backfill the new column with `encryptModel` before dropping the old one. You are responsible for writing and running that backfill in your application code before applying the DROP step.
Permission pre-checks [#permission-pre-checks]
Before installing, the CLI verifies you have the following permissions:
* `CREATE` on the database (for `CREATE SCHEMA` and `CREATE EXTENSION`)
* `CREATE` on the `public` schema (for `CREATE TYPE "public"."eql_v2_encrypted"`)
* `SUPERUSER` or extension owner (for `CREATE EXTENSION pgcrypto`, if not already installed)
If the role is not a superuser, the CLI falls back automatically to the OPE (no-operator-family) variant instead of exiting with an error.
***
upgrade [#upgrade]
Upgrade an existing EQL installation to the version bundled with the package, or to the latest version from GitHub.
```bash
npx stash db upgrade [options]
```
| Option | Description |
| --------------------------- | --------------------------------------------------------------------- |
| `--dry-run` | Show what would happen without making changes |
| `--supabase` | Use Supabase-compatible upgrade |
| `--exclude-operator-family` | Skip operator family creation |
| `--latest` | Fetch the latest EQL from GitHub instead of using the bundled version |
The EQL install SQL is idempotent and safe to re-run. The CLI checks the current version, re-runs the install SQL, and reports the new version. If EQL is not installed, the CLI suggests running `npx stash db install` instead.
Bundled EQL SQL [#bundled-eql-sql]
The EQL install SQL is bundled with the package for offline, deterministic installs. The bundled version is pinned to the package version. Use `--latest` to fetch the newest version from GitHub.
Three SQL variants are included:
| File | Used when |
| -------------------------------------------- | ---------------------------------------------------------- |
| `cipherstash-encrypt.sql` | Default install |
| `cipherstash-encrypt-supabase.sql` | `--supabase` flag |
| `cipherstash-encrypt-no-operator-family.sql` | `--exclude-operator-family` flag or automatic OPE fallback |
# Draft an encryption plan
Draft an encryption plan [#draft-an-encryption-plan]
`stash plan` is the second step in the setup lifecycle. It hands off to a coding agent, which reads your project and produces a reviewable encryption plan at `.cipherstash/plan.md`. No code is changed. The plan is a checkpoint you review before running `stash impl`.
```bash
npx stash plan
```
Prerequisites [#prerequisites]
`stash plan` reads `.cipherstash/context.json`, which is written by `stash init`. If that file is missing, the command errors with a message pointing you to run `stash init` first.
What plan does [#what-plan-does]
1. Reads `.cipherstash/context.json` to understand your project (integration, schemas, environment, installed agents).
2. Checks for an existing `.cipherstash/plan.md`. If one is found, warns you that the agent will be asked to revise it. Delete the file first to start fresh.
3. Asks which agent should write the plan. All four handoff targets are available: Claude Code, Codex, AGENTS.md (for Cursor/Windsurf/Cline), and the CipherStash Agent (`@cipherstash/wizard`). Each target produces a plan-mode artifact: Claude Code / Codex / AGENTS.md read the mode-aware `setup-prompt.md`; the wizard receives `--mode plan` and the gateway returns its planning prompt.
4. Hands off to the selected agent with a planning prompt. The agent reads your codebase and produces `.cipherstash/plan.md`.
5. After the agent finishes, asks (default-yes) whether to continue to `stash impl` now.
The plan.md artifact [#the-planmd-artifact]
The agent writes `.cipherstash/plan.md` as a human-readable Markdown file. At the top, it embeds a machine-readable summary block:
```
```
`stash impl` parses this block to render a confirmation panel before launching implementation.
Column paths [#column-paths]
Each entry in `columns` has a `path` field with one of two values:
| Path | Meaning | Implementation |
| --------- | -------------------------------------- | ---------------------------------------------------------------------------------- |
| `new` | Column does not yet exist as encrypted | Additive. Single deploy: add the encrypted column and update write paths. |
| `migrate` | Column has existing plaintext data | Staged across four deploys: add encrypted twin, backfill, cutover, drop plaintext. |
If `stash impl` finds a plan without the summary block (for example, a plan written before this feature was added), it falls back to a soft prompt asking you to open the file in your editor before proceeding.
Agent options [#agent-options]
`stash plan` offers all four handoff targets:
| Agent | Detected by | Notes |
| ----------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Claude Code | `claude` on `$PATH` | Launched interactively if detected. |
| Codex | `codex` on `$PATH` | Launched interactively if detected. |
| AGENTS.md | — | Writes `AGENTS.md` plus `.cipherstash/setup-prompt.md` for Cursor, Windsurf, Cline, and other editor-based agents. |
| CipherStash Agent | — | Runs the in-house wizard package (`stash wizard`) with `--mode plan`. The wizard skips its column-selection TUI and post-agent install/push/migrate steps; the gateway returns a planning prompt. |
The command defaults to Claude Code if detected, then Codex, otherwise AGENTS.md. The CipherStash Agent is never selected by default. Pick it explicitly if you want the in-house wizard. Each target produces a `.cipherstash/plan.md` with the same machine-readable summary block at the top, so the choice of target doesn't change what `stash impl` parses later.
Chain prompt [#chain-prompt]
After the agent completes the plan, `stash plan` shows:
```
Plan drafted at `.cipherstash/plan.md`. Continue to `stash impl` now? (Y/n)
```
Answering yes launches `stash impl` immediately. Answering no (or running non-interactively) prints the next step and exits:
```
Plan drafted at `.cipherstash/plan.md`. Review it, then run `stash impl` to implement.
```
Non-TTY environments (CI, pipes) skip the prompt and always print the hint.
Reviewing the plan [#reviewing-the-plan]
Before running `stash impl`, open `.cipherstash/plan.md` and verify:
* The tables and columns listed match your intent.
* The `path` values are correct. A `migrate` path triggers the full four-deploy lifecycle. Confirm this is what you want for any column with existing data.
* Any columns with `path: migrate` have a backfill strategy accounted for.
Next steps [#next-steps]
# Push and status
push [#push]
Push your encryption schema to the database.
```bash
npx stash db push [options]
```
| Option | Description |
| ----------- | ------------------------------------------------------------------------- |
| `--dry-run` | Load and validate the schema, then print it as JSON. No database changes. |
`push` is only required when using [CipherStash Proxy](/stack/cipherstash/proxy). If you're using the SDK directly (Drizzle, Supabase, or plain PostgreSQL), this step is not needed. The schema lives in your application code.
How push works [#how-push-works]
When pushing, the CLI:
1. Loads the encryption client from the path in `stash.config.ts`
2. Runs [schema validation](/stack/cipherstash/cli/validate) (warns but doesn't block)
3. Transforms SDK data types to EQL-compatible `cast_as` values
4. Connects to Postgres and marks existing `eql_v2_configuration` rows as `inactive`
5. Inserts the new config as an `active` row
SDK to EQL type mapping [#sdk-to-eql-type-mapping]
The SDK uses developer-friendly type names, but EQL expects PostgreSQL-aligned types. The `push` command maps these automatically:
| SDK type (`dataType()`) | EQL `cast_as` |
| ----------------------- | ------------- |
| `string` | `text` |
| `text` | `text` |
| `number` | `double` |
| `bigint` | `big_int` |
| `boolean` | `boolean` |
| `date` | `date` |
| `json` | `jsonb` |
status [#status]
Show the current state of EQL in your database.
```bash
npx stash db status
```
Reports:
* Whether EQL is installed and which version
* Database permission status
* Whether an active encrypt config exists in `eql_v2_configuration` (only relevant for CipherStash Proxy)
test-connection [#test-connection]
Verify that the database URL in your config is valid and the database is reachable.
```bash
npx stash db test-connection
```
Reports the database name, connected user/role, and PostgreSQL server version. Useful for debugging connection issues before running `install` or `push`.
# Project status
Project status [#project-status]
`stash status` shows where your project stands in the CipherStash setup lifecycle. It reads disk state only: no network, no database connection, no authentication. It runs in milliseconds.
```bash
npx stash status
```
What it checks [#what-it-checks]
`stash status` inspects three files:
| File | What it signals |
| ------------------------------ | ----------------------------------------------- |
| `.cipherstash/context.json` | `stash init` has run successfully |
| `.cipherstash/plan.md` | `stash plan` has produced an encryption plan |
| `.cipherstash/setup-prompt.md` | `stash impl` has engaged an agent at least once |
Output [#output]
`stash status` renders a lifecycle panel followed by a deeper-inspection block:
```
CipherStash project status
Lifecycle
✓ Initialized supabase · npm · 3 tables
◯ Plan written run `stash plan` to draft
◯ Implementation waiting on plan
Deeper inspection
Database state: `stash db status`
Per-column state: `stash encrypt status`
Next: run `stash plan` to draft your encryption plan.
```
Stage markers [#stage-markers]
| Marker | Meaning |
| ------ | ----------------- |
| `✓` | Stage is complete |
| `◯` | Stage is pending |
Stages [#stages]
**Initialized**: `stash init` has run and `.cipherstash/context.json` exists. The detail line shows the detected integration, package manager, and number of tables in the schema.
**Plan written**: `.cipherstash/plan.md` exists. The detail line shows the plan file path. If the project is initialized but no plan exists, it shows the next command to run.
**Implementation**: An agent has been engaged at least once (`.cipherstash/setup-prompt.md` exists). This stage is always shown as pending because disk state cannot tell you whether the agent finished. Check `stash encrypt status` for per-column state. If no agent has been engaged, the detail line shows the next command to run.
Deeper inspection [#deeper-inspection]
`stash status` points to two commands for state that requires a database connection:
| Command | What it shows |
| ---------------------- | ------------------------------------------------------------------- |
| `stash db status` | EQL installation state, database permissions, active encrypt config |
| `stash encrypt status` | Per-column migration state: phase, progress, and drift |
When to use stash status [#when-to-use-stash-status]
Run `stash status` any time you want a quick answer to "where am I?" without triggering auth or a database round-trip. It is safe to run repeatedly, in any environment, including CI.
Common use cases:
* After onboarding to a new device, to check what has already been set up
* Before running `stash plan` or `stash impl`, to confirm prerequisites are met
* In a CI pre-flight check, to verify context.json is committed
Next steps [#next-steps]
# Troubleshooting
Common errors [#common-errors]
| Error | Cause | Fix |
| ------------------------------------------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `Could not find stash.config.ts` | No config file in cwd or parent dirs | Run `npx stash init` (which runs `db install` automatically), or run `npx stash db install` directly, or create `stash.config.ts` manually |
| `databaseUrl is required` | Config missing `databaseUrl` | Add `databaseUrl` to config and check `.env` is loaded |
| `must be superuser to create an operator family` | Standard SQL requires superuser | The CLI falls back to OPE mode automatically on managed databases. Pass `--exclude-operator-family` if you see this on self-hosted Postgres. |
| `Insufficient database permissions` | Role lacks `CREATE` privileges | Connect as superuser or grant permissions |
| `EQL is already installed` | `eql_v2` schema exists | Use `--force` to reinstall |
| `Encrypt client file not found` | `push`/`validate` can't find the file at `config.client` | Set `client` in `stash.config.ts` to the correct path |
| `drizzle-kit generate failed` | drizzle-kit not installed or wrong output dir | Install `drizzle-kit` and set `--out` to match your Drizzle config |
| EQL missing after `supabase db reset` | EQL was installed via direct push, not as a migration | Re-run `db install --supabase --migration` to add EQL to `supabase/migrations/`. See [below](#supabase-db-reset). |
Permission issues [#permission-issues]
The `install` command checks database permissions before running. On managed databases (Supabase, Neon, RDS), the CLI detects a non-superuser role and automatically uses the no-operator-family (OPE) install variant.
If you still see permission errors:
1. Run `npx stash db test-connection` to verify your database URL is correct.
2. Run `npx stash db status` to check the current EQL state.
3. Ensure the connected role has `CREATE` privileges on the database and `public` schema.
4. For the `pgcrypto` extension, the role needs `SUPERUSER` or extension owner privileges.
Supabase-specific issues [#supabase-specific-issues]
When using `--supabase` or the automatic Supabase detection:
* `ORDER BY` on encrypted columns is not supported. Sort application-side after decrypting.
* The `anon`, `authenticated`, and `service_role` roles are Supabase-specific. Don't use `--supabase` on standard PostgreSQL.
Drizzle migration issues [#drizzle-migration-issues]
If `--drizzle` fails:
1. Ensure `drizzle-kit` is installed: `npm install -D drizzle-kit`
2. Ensure the `--out` directory matches your `drizzle.config.ts` output directory.
3. Check that your Drizzle config is valid by running `npx drizzle-kit generate` manually.
Encrypting existing columns [#encrypting-existing-columns]
When adding `encryptedType` to a column that already has data, the CLI rewrites `ALTER COLUMN ... SET DATA TYPE eql_v2_encrypted` statements into a safe `ADD COLUMN / DROP COLUMN / RENAME COLUMN` sequence. You must backfill the new column with `encryptModel` in your application code before applying the DROP step on non-empty tables.
Supabase db reset [#supabase-db-reset]
Why it happens [#why-it-happens]
`supabase db reset` drops the entire database and reruns only the SQL files in `supabase/migrations/`. If you installed EQL via direct push (the default before the `--migration` flag was added), EQL is not in your migrations directory and gets wiped by the reset.
Fix for new installs [#fix-for-new-installs]
Re-run `db install` and choose the migration-file path:
```bash
npx stash db install --supabase --migration
```
The CLI writes EQL SQL to `supabase/migrations/00000000000000_cipherstash_eql.sql`. The all-zero timestamp prefix ensures it runs before any user migrations that reference `eql_v2_encrypted`. After the file is created, `supabase db reset` will reinstall EQL automatically on every reset.
Upgrade path for existing direct-push installs [#upgrade-path-for-existing-direct-push-installs]
If you already ran a direct-push install and your live database is working, your existing install is not broken. To get a migration file going forward without disrupting the live database, run:
```bash
npx stash db install --supabase --migration --force
```
The EQL SQL is idempotent. The `--force` flag regenerates the install even though EQL is already present. Your live install is unaffected. After this, `supabase db reset` reinstalls EQL from the migration file.
# Schema validation
The `validate` command checks your encryption schema for common misconfigurations.
```bash
npx stash db validate [options]
```
| Option | Description |
| --------------------------- | ------------------------------------------------------------------------------ |
| `--supabase` | Check for Supabase-specific issues (e.g. `ORDER BY` without operator families) |
| `--exclude-operator-family` | Check for issues when operator families are excluded |
Validation rules [#validation-rules]
| Rule | Severity | Description |
| ----------------------------------------- | -------- | ----------------------------------------------- |
| `freeTextSearch` on non-string column | Warning | Free-text search only works with string data |
| `orderAndRange` without operator families | Warning | `ORDER BY` won't work without operator families |
| No indexes on encrypted column | Info | Column is encrypted but not searchable |
| `searchableJson` without `json` data type | Error | `searchableJson` requires `dataType("json")` |
Examples [#examples]
Basic validation:
```bash
npx stash db validate
```
Validate with Supabase context (checks for operator family issues):
```bash
npx stash db validate --supabase
```
How validation works [#how-validation-works]
The command loads your encryption schema from the file specified by `client` in `stash.config.ts` and runs it through the validation rules.
* **Errors** cause the command to exit with code 1.
* **Warnings** and **info** messages are printed but don't cause a non-zero exit.
* Validation also runs automatically before [`push`](/stack/cipherstash/cli/push). Issues are logged as warnings but don't block the push.
# Bulk operations
Bulk operations [#bulk-operations]
`bulkEncrypt` and `bulkDecrypt` encrypt or decrypt an array of raw values in a single call to ZeroKMS. Every value still gets its own unique key. The batch just pays the network round-trip once, regardless of how many items you pass.
This page covers the raw-value variants. If you want to encrypt whole objects (records with multiple fields), see [Model operations](/stack/cipherstash/encryption/models) instead.
For full method signatures, see the [`EncryptionClient` API reference](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient).
Why bulk matters [#why-bulk-matters]
Calling `encrypt` in a loop makes one ZeroKMS request per value. For 100 emails that is 100 round-trips. `bulkEncrypt` collapses those into one.
The throughput gain is significant for any batch larger than a handful of records. Use bulk operations whenever you are processing more than one value at a time.
bulkEncrypt [#bulkencrypt]
Pass an array of `{ id, plaintext }` objects. The `id` is your correlation key: it flows through to the output so you can match encrypted results back to your source records.
```typescript filename="bulk-encrypt.ts"
import { Encryption } from "@cipherstash/stack"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch(),
})
const client = await Encryption({ schemas: [users] })
const plaintexts = [
{ id: "u1", plaintext: "alice@example.com" },
{ id: "u2", plaintext: "bob@example.com" },
{ id: "u3", plaintext: "charlie@example.com" },
]
const result = await client.bulkEncrypt(plaintexts, {
column: users.email,
table: users,
})
if (result.failure) {
throw new Error(`Bulk encryption failed: ${result.failure.message}`)
}
// result.data is an array of { id: string, data: Encrypted }
// The id matches the id you passed in
const encrypted = result.data
```
Input shape [#input-shape]
Each element in the input array takes this shape:
| Field | Type | Required | Description |
| ----------- | ------------------------------------- | -------- | -------------------------------------- |
| `id` | `string` | No | Correlation key returned in the output |
| `plaintext` | `string \| number \| boolean \| null` | Yes | The value to encrypt |
You can omit `id` when you do not need to correlate results (for example, when processing an ordered list where position is the correlation).
Mapping results back to records [#mapping-results-back-to-records]
When `id` is present, use it to build a lookup map:
```typescript filename="bulk-encrypt-map.ts"
const encryptedByUserId = Object.fromEntries(
result.data.map((item) => [item.id, item.data]),
)
// encryptedByUserId["u1"] → Encrypted payload for alice
```
bulkDecrypt [#bulkdecrypt]
Pass the array produced by `bulkEncrypt`. Results come back in the same order, with per-item success or failure.
```typescript filename="bulk-decrypt.ts"
const decrypted = await client.bulkDecrypt(encrypted)
if (decrypted.failure) {
throw new Error(`Bulk decryption failed: ${decrypted.failure.message}`)
}
for (const item of decrypted.data) {
if ("data" in item) {
console.log(`${item.id}: ${item.data}`)
} else {
console.error(`${item.id} failed: ${item.error}`)
}
}
```
Per-item failure handling [#per-item-failure-handling]
`bulkDecrypt` returns a top-level `Result` wrapping an array where each element is either a success or a per-item error. The top-level `failure` fires for infrastructure errors (network, auth). Individual decryption failures surface as `{ id, error }` items in the array.
```typescript filename="bulk-decrypt-errors.ts"
const successful: string[] = []
const failed: string[] = []
for (const item of decrypted.data) {
if ("data" in item) {
successful.push(item.data as string)
} else {
failed.push(item.id)
}
}
```
Ordering guarantee [#ordering-guarantee]
`bulkDecrypt` returns items in the same order as the input array. If you do not use `id`, you can rely on index position for correlation.
Complete example: bulk insert with UNNEST [#complete-example-bulk-insert-with-unnest]
This pattern encrypts an array of values and inserts them into PostgreSQL with a single multi-row statement.
```typescript filename="bulk-insert.ts"
import { Pool } from "pg"
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
async function insertUsers(emails: string[]) {
const plaintexts = emails.map((email, i) => ({
id: String(i),
plaintext: email,
}))
const encryptResult = await client.bulkEncrypt(plaintexts, {
column: users.email,
table: users,
})
if (encryptResult.failure) {
throw new Error(`Encryption failed: ${encryptResult.failure.message}`)
}
const encryptedValues = encryptResult.data.map((item) => item.data)
const result = await pool.query(
`INSERT INTO users (email)
SELECT * FROM UNNEST($1::jsonb[])
RETURNING id`,
[encryptedValues],
)
return result.rows.map((row) => row.id)
}
```
Always use the `::jsonb` cast when passing encrypted values to PostgreSQL. This ensures PostgreSQL handles the CipherCell JSON payload correctly.
For the table setup and single-record insert pattern, see [Storing encrypted data](/stack/cipherstash/encryption/storing-data).
Identity-aware bulk encryption [#identity-aware-bulk-encryption]
Lock an entire batch to a user's identity by chaining `.withLockContext()`:
```typescript filename="bulk-encrypt-identity.ts"
import { LockContext } from "@cipherstash/stack/identity"
const lc = new LockContext()
const lockContext = (await lc.identify(userJwt)).data!
const encrypted = await client
.bulkEncrypt(plaintexts, { column: users.email, table: users })
.withLockContext(lockContext)
const decrypted = await client
.bulkDecrypt(encrypted.data)
.withLockContext(lockContext)
```
See [Identity-aware encryption](/stack/cipherstash/encryption/identity) for the full lock context flow.
When to use bulk vs model operations [#when-to-use-bulk-vs-model-operations]
| Scenario | Recommended method |
| ------------------------------------------------------- | ----------------------------------------- |
| Encrypting one field from a list of records | `bulkEncrypt` / `bulkDecrypt` |
| Encrypting whole records with multiple encrypted fields | `bulkEncryptModels` / `bulkDecryptModels` |
| Migrating a single column in an existing table | `bulkEncrypt` |
| Inserting new records from a form or API payload | `bulkEncryptModels` |
The rule of thumb: use raw bulk methods when you are working with a single field across many records. Use model methods when you have whole objects to round-trip.
See [Model operations](/stack/cipherstash/encryption/models) for `bulkEncryptModels` and `bulkDecryptModels`.
ORM integrations [#orm-integrations]
Drizzle and DynamoDB have adapter-level bulk support that wraps these methods:
* [Drizzle bulk insert](/stack/cipherstash/encryption/drizzle): `bulkEncryptModels` with Drizzle `.values()`
* [DynamoDB bulk operations](/stack/cipherstash/encryption/dynamodb): `BatchWriteItem` and `BatchGetItem` wrappers
Next steps [#next-steps]
* [Model operations](/stack/cipherstash/encryption/models): encrypt whole records in one call
* [Storing encrypted data](/stack/cipherstash/encryption/storing-data): raw SQL insert and retrieve patterns
* [Identity-aware encryption](/stack/cipherstash/encryption/identity): scope encryption to a user's JWT
# Configuration
Local development (recommended) [#local-development-recommended]
If you have run `npx stash init`, no configuration is needed for local development.
The SDK automatically uses device-based authentication to access your workspace and default keyset.
See [Getting started](/stack/quickstart) for setup instructions.
Production and CI/CD configuration [#production-and-cicd-configuration]
In production environments where device-based auth is not available, the SDK supports two configuration methods: environment variables and programmatic config. Environment variables take precedence over programmatic config.
Environment variables [#environment-variables]
Set these in your hosting platform or CI/CD system:
| Variable | Description | Required |
| ---------------------- | --------------------------------------------------- | --------------------------------------------- |
| `CS_WORKSPACE_CRN` | The workspace identifier (CRN format) | Yes |
| `CS_CLIENT_ID` | The client key identifier | Yes |
| `CS_CLIENT_KEY` | Client key material used with ZeroKMS | Yes |
| `CS_CLIENT_ACCESS_KEY` | API key for authenticating with the CipherStash API | Yes |
| `CS_CONFIG_PATH` | Temporary path for client configuration | No (default: `/home/{username}/.cipherstash`) |
| `STASH_STACK_LOG` | Log level (`debug`, `info`, `error`) | No (default: `error`) |
See [Going to production](/stack/deploy/going-to-production) for a step-by-step guide to generating production credentials.
Avoid setting `CS_*` environment variables for local development.
Device-based auth provides per-developer identity, no shared secrets, and automatic session management.
See [Getting started](/stack/quickstart) to set up device auth.
Programmatic config [#programmatic-config]
Pass config directly when initializing the client. Use this approach in production when loading credentials from a secret manager:
```typescript filename="encryption/index.ts"
import { Encryption, type EncryptionClientConfig } from "@cipherstash/stack"
import { users } from "./schema"
const config: EncryptionClientConfig = {
schemas: [users],
config: {
workspaceCrn: "your-workspace-crn",
accessKey: "your-access-key",
clientId: "your-client-id",
clientKey: "your-client-key",
},
}
const client = await Encryption(config)
```
Subpath exports [#subpath-exports]
The SDK provides multiple subpath exports for importing only what you need:
| Import path | Provides |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `@cipherstash/stack` | `Encryption` function, `Secrets` class, `encryptedTable`, `encryptedColumn`, `encryptedField` (convenience re-exports) |
| `@cipherstash/stack/schema` | `encryptedTable`, `encryptedColumn`, `encryptedField`, schema types |
| `@cipherstash/stack/identity` | `LockContext` class and identity types |
| `@cipherstash/stack/secrets` | `Secrets` class and secrets types |
| `@cipherstash/stack/drizzle` | `encryptedType`, `extractEncryptionSchema`, `createEncryptionOperators` for Drizzle ORM |
| `@cipherstash/stack/supabase` | `encryptedSupabase` wrapper for Supabase |
| `@cipherstash/stack/dynamodb` | `encryptedDynamoDB` helper for DynamoDB |
| `@cipherstash/stack/encryption` | `EncryptionClient` class, `Encryption` function |
| `@cipherstash/stack/errors` | `EncryptionErrorTypes`, `StackError`, error subtypes, `getErrorMessage` |
| `@cipherstash/stack/client` | Client-safe exports: schema builders, schema types, `EncryptionClient` type (no native FFI) |
| `@cipherstash/stack/types` | All TypeScript types |
Keysets [#keysets]
[Key Sets](/stack/cipherstash/kms/keysets) are a ZeroKMS primitive for cryptographic isolation.
Each Key Set maintains its own set of data encryption keys. Data encrypted under one Key Set cannot be decrypted with another.
This is the same primitive that will power environment isolation in Secrets (coming soon).
You can specify a Key Set by ID or by name:
By ID [#by-id]
```typescript filename="encryption/index.ts"
const client = await Encryption({
schemas: [users],
config: {
workspaceCrn: "your-workspace-crn",
accessKey: "your-access-key",
clientId: "your-client-id",
clientKey: "your-client-key",
keyset: { id: "123e4567-e89b-12d3-a456-426614174000" },
},
})
```
By name [#by-name]
```typescript filename="encryption/index.ts"
const client = await Encryption({
schemas: [users],
config: {
workspaceCrn: "your-workspace-crn",
accessKey: "your-access-key",
clientId: "your-client-id",
clientKey: "your-client-key",
keyset: { name: "Company A" },
},
})
```
For full details on creating and managing Key Sets, see the [Key Sets reference](/stack/cipherstash/kms/keysets).
Logging [#logging]
Control log verbosity with the `STASH_STACK_LOG` environment variable:
```bash
STASH_STACK_LOG=error # debug | info | error (default: error)
```
| Value | What is logged |
| ------- | ----------------------- |
| `error` | Errors only (default) |
| `info` | Info and errors |
| `debug` | Debug, info, and errors |
The SDK never logs plaintext data.
Deploying to production [#deploying-to-production]
For a complete guide to transitioning from local device-based auth to production environment variables, see [Going to production](/stack/deploy/going-to-production).
File system write permissions [#file-system-write-permissions]
In most hosting environments, set `CS_CONFIG_PATH` to a writable directory:
```bash
CS_CONFIG_PATH=/tmp/.cipherstash
```
This has been tested on [Vercel](https://vercel.com/) and [AWS Lambda](https://aws.amazon.com/lambda/).
Bundler configuration [#bundler-configuration]
`@cipherstash/stack` includes a native FFI module that must be excluded from bundling:
* **Next.js**: See [Bundling: Next.js](/stack/deploy/bundling#nextjs)
* **SST / serverless**: See [SST setup](/stack/deploy/sst)
* **Webpack / esbuild**: Add `@cipherstash/stack` to your externals configuration
Platform requirements [#platform-requirements]
* **Node.js** >= 18
* See [Troubleshooting](/stack/deploy/troubleshooting) for npm lockfile issues on Linux
# Drizzle ORM
CipherStash provides first-class Drizzle ORM integration through `@cipherstash/stack/drizzle`. Define encrypted columns directly in your Drizzle table schema, and use auto-encrypting operators that make encrypted queries look like standard Drizzle code.
Installation [#installation]
```bash
npm install @cipherstash/stack drizzle-orm
```
If you're using Bun, version 1.3 or later is required.
The Drizzle integration is included in `@cipherstash/stack` and imports from `@cipherstash/stack/drizzle`.
Database setup [#database-setup]
Install EQL via Drizzle migrations [#install-eql-via-drizzle-migrations]
Use the [CipherStash CLI](/stack/cipherstash/cli) to generate a Drizzle migration that installs the [EQL](/stack/reference/eql-guide) extension:
```bash
npx stash db install --drizzle
npx drizzle-kit migrate
```
| Flag | Default | Description |
| ---------------- | ------------- | ------------------------------------------------- |
| `--name ` | `install-eql` | Migration name |
| `--out ` | `drizzle` | Output directory (must match your Drizzle config) |
See [CipherStash CLI — Drizzle migrations](/stack/cipherstash/cli/install#drizzle-migrations) for details.
Column storage [#column-storage]
Encrypted columns use the `eql_v2_encrypted` PostgreSQL type (installed by EQL). If not using EQL directly, use JSONB:
```sql filename="create-table.sql"
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email eql_v2_encrypted, -- with EQL extension
name jsonb NOT NULL, -- or use jsonb
age INTEGER -- non-encrypted columns are normal types
);
```
Schema definition [#schema-definition]
Use `encryptedType()` to define encrypted columns directly in your Drizzle table schema. TypeScript property names in camelCase map to their corresponding snake\_case database column names (e.g., `createdAt` in TypeScript maps to `created_at` in the database).
```typescript filename="schema.ts"
import { pgTable, integer, timestamp, varchar } from "drizzle-orm/pg-core"
import { encryptedType } from "@cipherstash/stack/drizzle"
const usersTable = pgTable("users", {
id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
// Encrypted string with search capabilities
email: encryptedType("email", {
equality: true, // enables: eq, ne, inArray
freeTextSearch: true, // enables: like, ilike
orderAndRange: true, // enables: gt, gte, lt, lte, between, asc, desc
}),
// Encrypted number
age: encryptedType("age", {
dataType: "number",
equality: true,
orderAndRange: true,
}),
// Encrypted JSON object with searchable JSONB queries
profile: encryptedType<{ name: string; bio: string }>("profile", {
dataType: "json",
searchableJson: true,
}),
// Non-encrypted columns
role: varchar("role", { length: 50 }),
createdAt: timestamp("created_at").defaultNow(),
})
```
encryptedType(name, config?) [#encryptedtypetdataname-config]
| Config Option | Type | Description |
| ---------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------- |
| `dataType` | `"string"` \| `"number"` \| `"json"` \| `"boolean"` \| `"bigint"` \| `"date"` | Plaintext data type (default: `"string"`) |
| `equality` | `boolean` \| `TokenFilter[]` | Enable equality index |
| `freeTextSearch` | `boolean` \| `MatchIndexOpts` | Enable free-text search index |
| `orderAndRange` | `boolean` | Enable ORE index for sorting and range queries |
| `searchableJson` | `boolean` | Enable JSONB path queries (requires `dataType: "json"`) |
The generic type parameter `` sets the TypeScript type for the decrypted value.
Initialization [#initialization]
1. Extract schema from Drizzle table [#1-extract-schema-from-drizzle-table]
```typescript filename="init.ts"
import {
extractEncryptionSchema,
createEncryptionOperators,
} from "@cipherstash/stack/drizzle"
import { Encryption } from "@cipherstash/stack"
// Convert Drizzle table definition to CipherStash schema
const usersSchema = extractEncryptionSchema(usersTable)
```
2. Initialize encryption client [#2-initialize-encryption-client]
```typescript filename="init.ts"
const encryptionClient = await Encryption({
schemas: [usersSchema],
})
```
3. Create query operators [#3-create-query-operators]
```typescript filename="init.ts"
const encryptionOps = createEncryptionOperators(encryptionClient)
```
4. Create Drizzle instance [#4-create-drizzle-instance]
```typescript filename="init.ts"
import { drizzle } from "drizzle-orm/postgres-js"
import postgres from "postgres"
const db = drizzle({ client: postgres(process.env.DATABASE_URL!) })
```
For fine-grained control over encryption timing (e.g., caching encrypted values, debugging, or building custom abstractions), you can use the manual `encryptionClient.encrypt()` pattern instead of the auto-encrypting operators. See [Encrypt and decrypt](/stack/cipherstash/encryption/encrypt-decrypt) for the low-level API.
Insert encrypted data [#insert-encrypted-data]
Encrypt models before inserting:
```typescript filename="insert.ts"
// Single insert
const encrypted = await encryptionClient.encryptModel(
{ email: "alice@example.com", age: 30, role: "admin" },
usersSchema,
)
if (!encrypted.failure) {
await db.insert(usersTable).values(encrypted.data)
}
// Bulk insert
const encrypted = await encryptionClient.bulkEncryptModels(
[
{ email: "alice@example.com", age: 30, role: "admin" },
{ email: "bob@example.com", age: 25, role: "user" },
],
usersSchema,
)
if (!encrypted.failure) {
await db.insert(usersTable).values(encrypted.data)
}
```
Query encrypted data [#query-encrypted-data]
Equality [#equality]
```typescript filename="query-equality.ts"
// Exact match — await the operator
const results = await db
.select()
.from(usersTable)
.where(await encryptionOps.eq(usersTable.email, "alice@example.com"))
```
Text search [#text-search]
```typescript filename="query-text-search.ts"
// Case-insensitive search
const results = await db
.select()
.from(usersTable)
.where(await encryptionOps.ilike(usersTable.email, "%alice%"))
// Case-sensitive search
const results = await db
.select()
.from(usersTable)
.where(await encryptionOps.like(usersTable.email, "%@example.com"))
```
Range queries [#range-queries]
```typescript filename="query-range.ts"
// Greater than or equal
const results = await db
.select()
.from(usersTable)
.where(await encryptionOps.gte(usersTable.age, 18))
// Between (inclusive)
const results = await db
.select()
.from(usersTable)
.where(await encryptionOps.between(usersTable.age, 18, 65))
```
Array operators [#array-operators]
```typescript filename="query-array.ts"
const results = await db
.select()
.from(usersTable)
.where(await encryptionOps.inArray(usersTable.email, [
"alice@example.com",
"bob@example.com",
]))
```
Sorting [#sorting]
Sort operators are synchronous — no `await` needed:
```typescript filename="query-sort.ts"
// Sort by encrypted column ascending
const results = await db
.select()
.from(usersTable)
.orderBy(encryptionOps.asc(usersTable.age))
// Sort descending
const results = await db
.select()
.from(usersTable)
.orderBy(encryptionOps.desc(usersTable.age))
```
Select specific columns [#select-specific-columns]
```typescript filename="query-select.ts"
const results = await db.select({
id: usersTable.id,
email: usersTable.email,
createdAt: usersTable.createdAt,
}).from(usersTable)
```
Pagination [#pagination]
```typescript filename="query-pagination.ts"
// First page
const results = await db.select().from(usersTable).limit(10)
// Second page
const results = await db.select().from(usersTable).limit(10).offset(10)
```
Aggregation [#aggregation]
```typescript filename="query-aggregation.ts"
import { sql } from "drizzle-orm"
// Total count
const result = await db.select({ count: sql`count(*)` }).from(usersTable)
// Count with condition
const result = await db
.select({ count: sql`count(*)` })
.from(usersTable)
.where(await encryptionOps.gte(usersTable.age, 18))
```
Date and timestamp queries [#date-and-timestamp-queries]
```typescript filename="date-query.ts"
const now = new Date()
const twoWeeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000)
const results = await db
.select()
.from(usersTable)
.where(
await encryptionOps.and(
encryptionOps.gte(usersTable.createdAt, twoWeeksAgo),
encryptionOps.lte(usersTable.createdAt, now),
),
)
```
JSONB queries [#jsonb-queries]
Query encrypted JSON columns using JSONB operators. These require `searchableJson: true` and `dataType: "json"` in the column's `encryptedType` config.
Check path existence [#check-path-existence]
```typescript filename="query-jsonb.ts"
// Check if a JSONB path exists in an encrypted column
const results = await db
.select()
.from(usersTable)
.where(await encryptionOps.jsonbPathExists(usersTable.profile, "$.bio"))
```
Extract value at path [#extract-value-at-path]
```typescript filename="query-jsonb-extract.ts"
// Extract the first matching value at a JSONB path
const result = await encryptionOps.jsonbPathQueryFirst(usersTable.profile, "$.name")
```
Get value with -> operator [#get-value-with---operator]
```typescript filename="query-jsonb-get.ts"
// Get a value using the JSONB -> operator
const result = await encryptionOps.jsonbGet(usersTable.profile, "$.name")
```
`jsonbPathExists` returns a boolean and can be used in `WHERE` clauses. `jsonbPathQueryFirst` and `jsonbGet` return encrypted values — use them in `SELECT` expressions.
Batched conditions (and / or) [#batched-conditions-and--or]
Use `encryptionOps.and()` and `encryptionOps.or()` to batch multiple encrypted conditions into a single ZeroKMS call. This is more efficient than awaiting each operator individually.
```typescript filename="query-batched.ts"
// Batched AND — all encryptions happen in one call
const results = await db
.select()
.from(usersTable)
.where(
await encryptionOps.and(
encryptionOps.gte(usersTable.age, 18), // no await — lazy operator
encryptionOps.lte(usersTable.age, 65), // no await — lazy operator
encryptionOps.ilike(usersTable.email, "%example.com"),
eq(usersTable.role, "admin"), // mix with regular Drizzle ops
),
)
// Batched OR
const results = await db
.select()
.from(usersTable)
.where(
await encryptionOps.or(
encryptionOps.eq(usersTable.email, "alice@example.com"),
encryptionOps.eq(usersTable.email, "bob@example.com"),
),
)
```
Pass lazy operators (no `await`) to `and()`/`or()`, then `await` the outer call. This batches all encryption into a single operation.
Decrypt results [#decrypt-results]
```typescript filename="decrypt.ts"
// Single model
const decrypted = await encryptionClient.decryptModel(results[0])
if (!decrypted.failure) {
console.log(decrypted.data.email) // "alice@example.com"
}
// Bulk decrypt
const decrypted = await encryptionClient.bulkDecryptModels(results)
if (!decrypted.failure) {
for (const user of decrypted.data) {
console.log(user.email)
}
}
```
Non-encrypted column fallback [#non-encrypted-column-fallback]
All operators automatically detect whether a column is encrypted. If the column is a regular Drizzle column, the operator falls back to the standard Drizzle operator:
```typescript filename="fallback.ts"
// This works for both encrypted and non-encrypted columns
await encryptionOps.eq(usersTable.email, "alice@example.com") // encrypted
await encryptionOps.eq(usersTable.role, "admin") // falls back to drizzle eq()
```
Operator reference [#operator-reference]
Encrypted operators (async) [#encrypted-operators-async]
| Operator | Usage | Required Index |
| ------------------------------------ | ----------------------------------- | ----------------------------- |
| `eq(col, value)` | Equality | `equality` or `orderAndRange` |
| `ne(col, value)` | Not equal | `equality` or `orderAndRange` |
| `gt(col, value)` | Greater than | `orderAndRange` |
| `gte(col, value)` | Greater than or equal | `orderAndRange` |
| `lt(col, value)` | Less than | `orderAndRange` |
| `lte(col, value)` | Less than or equal | `orderAndRange` |
| `between(col, min, max)` | Between (inclusive) | `orderAndRange` |
| `notBetween(col, min, max)` | Not between | `orderAndRange` |
| `like(col, pattern)` | LIKE pattern match | `freeTextSearch` |
| `ilike(col, pattern)` | ILIKE case-insensitive | `freeTextSearch` |
| `notIlike(col, pattern)` | NOT ILIKE | `freeTextSearch` |
| `inArray(col, values)` | IN array | `equality` |
| `notInArray(col, values)` | NOT IN array | `equality` |
| `jsonbPathQueryFirst(col, selector)` | Extract first value at JSONB path | `searchableJson` |
| `jsonbGet(col, selector)` | Get value using JSONB `->` operator | `searchableJson` |
| `jsonbPathExists(col, selector)` | Check if JSONB path exists | `searchableJson` |
Sort operators (sync) [#sort-operators-sync]
| Operator | Usage | Required Index |
| ----------- | --------------- | --------------- |
| `asc(col)` | Ascending sort | `orderAndRange` |
| `desc(col)` | Descending sort | `orderAndRange` |
Logical operators (async, batched) [#logical-operators-async-batched]
| Operator | Usage | Description |
| -------------------- | ---------------- | ------------------ |
| `and(...conditions)` | Combine with AND | Batches encryption |
| `or(...conditions)` | Combine with OR | Batches encryption |
Both `and()` and `or()` accept `undefined` conditions, which are filtered out. This is useful for conditional query building:
```typescript filename="conditional-query.ts"
const results = await db
.select()
.from(usersTable)
.where(
await encryptionOps.and(
maybeCond ? encryptionOps.eq(usersTable.email, value) : undefined,
encryptionOps.gte(usersTable.age, 18),
),
)
```
Passthrough operators (sync, no encryption) [#passthrough-operators-sync-no-encryption]
`exists`, `notExists`, `isNull`, `isNotNull`, `not`, `arrayContains`, `arrayContained`, `arrayOverlaps`
These are re-exported from Drizzle and work identically.
Complete example: Express API [#complete-example-express-api]
```typescript filename="server.ts"
import "dotenv/config"
import express from "express"
import { eq } from "drizzle-orm"
import { drizzle } from "drizzle-orm/postgres-js"
import postgres from "postgres"
import { pgTable, integer, timestamp, varchar } from "drizzle-orm/pg-core"
import {
encryptedType,
extractEncryptionSchema,
createEncryptionOperators,
EncryptionOperatorError,
EncryptionConfigError,
} from "@cipherstash/stack/drizzle"
import { Encryption } from "@cipherstash/stack"
// Schema
const usersTable = pgTable("users", {
id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
email: encryptedType("email", {
equality: true,
freeTextSearch: true,
}),
age: encryptedType("age", {
dataType: "number",
orderAndRange: true,
}),
role: varchar("role", { length: 50 }),
createdAt: timestamp("created_at").defaultNow(),
})
// Init
const usersSchema = extractEncryptionSchema(usersTable)
const encryptionClient = await Encryption({ schemas: [usersSchema] })
const encryptionOps = createEncryptionOperators(encryptionClient)
const db = drizzle({ client: postgres(process.env.DATABASE_URL!) })
const app = express()
app.use(express.json())
// Create user
app.post("/users", async (req, res) => {
const encrypted = await encryptionClient.encryptModel(req.body, usersSchema)
if (encrypted.failure) {
return res.status(500).json({ error: encrypted.failure.message })
}
const [user] = await db.insert(usersTable).values(encrypted.data).returning()
res.json(user)
})
// Search users
app.get("/users", async (req, res) => {
const conditions = []
if (req.query.email) {
conditions.push(
encryptionOps.ilike(usersTable.email, `%${req.query.email}%`),
)
}
if (req.query.minAge) {
conditions.push(
encryptionOps.gte(usersTable.age, Number(req.query.minAge)),
)
}
if (req.query.role) {
conditions.push(eq(usersTable.role, req.query.role as string))
}
let query = db.select().from(usersTable)
if (conditions.length > 0) {
query = query.where(
await encryptionOps.and(...conditions),
) as typeof query
}
const results = await query
const decrypted = await encryptionClient.bulkDecryptModels(results)
if (decrypted.failure) {
return res.status(500).json({ error: decrypted.failure.message })
}
res.json(decrypted.data)
})
app.listen(3000)
```
Error handling [#error-handling]
Individual operators (e.g., `eq()`, `gte()`, `like()`) throw when called with invalid configuration or missing indexes:
* **`EncryptionOperatorError`**: thrown for operator-level issues (e.g., invalid arguments, unsupported operations).
* **`EncryptionConfigError`**: thrown for configuration issues (e.g., using `like` on a column without `freeTextSearch: true`).
```typescript filename="error-handling.ts"
import {
createEncryptionOperators,
EncryptionOperatorError,
EncryptionConfigError,
} from "@cipherstash/stack/drizzle"
try {
const result = await encryptionOps.like(usersTable.age, "%foo%")
} catch (err) {
if (err instanceof EncryptionConfigError) {
// Column does not have freeTextSearch enabled
console.error("Config error:", err.message, err.context)
} else if (err instanceof EncryptionOperatorError) {
console.error("Operator error:", err.message, err.context)
}
}
```
Encryption client operations return `Result` objects with `data` or `failure`. See [Error handling](/stack/reference/error-handling) for details.
# DynamoDB
CipherStash provides a DynamoDB integration through `@cipherstash/stack/dynamodb`. The `encryptedDynamoDB` helper encrypts items before writing to DynamoDB and decrypts them after reading — it does not wrap the AWS SDK, so you keep full control of your DynamoDB operations.
Installation [#installation]
```bash
npm install @cipherstash/stack @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
```
If you're using Bun, version 1.3 or later is required.
The DynamoDB integration is included in `@cipherstash/stack` and imports from `@cipherstash/stack/dynamodb`.
How it works [#how-it-works]
CipherStash encrypts each attribute into two DynamoDB attributes:
| Original attribute | Stored as | Purpose |
| ------------------ | --------------- | -------------------------------------------------------- |
| `email` | `email__source` | Encrypted ciphertext |
| `email` | `email__hmac` | HMAC for equality lookups (only if `.equality()` is set) |
Non-encrypted attributes pass through unchanged. On decryption, the `__source` and `__hmac` attributes are recombined back into the original attribute name with the plaintext value.
Setup [#setup]
Define an encrypted schema [#define-an-encrypted-schema]
Use `encryptedTable` and `encryptedColumn` from `@cipherstash/stack/schema` to declare which attributes to encrypt:
```typescript filename="schema.ts"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality(), // searchable via HMAC
name: encryptedColumn("name"), // encrypt-only, no search
phone: encryptedColumn("phone"), // encrypt-only
metadata: encryptedColumn("metadata").dataType("json"), // encrypted JSON
})
```
Only attributes with `.equality()` get an `__hmac` attribute for querying. Attributes without it are encrypted but cannot be searched.
`encryptedColumn` supports `.orderAndRange()`, `.freeTextSearch()`, and `.searchableJson()` index methods. Only `.equality()` produces HMAC values usable for DynamoDB key condition queries.
See [Schema definition](/stack/cipherstash/encryption/schema) for full details on index types, data types, and nested objects.
Initialize the clients [#initialize-the-clients]
```typescript filename="setup.ts"
import { DynamoDBClient } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb"
import { Encryption } from "@cipherstash/stack"
import { encryptedDynamoDB } from "@cipherstash/stack/dynamodb"
const dynamoClient = new DynamoDBClient({ region: "us-east-1" })
const docClient = DynamoDBDocumentClient.from(dynamoClient)
const encryptionClient = await Encryption({ schemas: [users] })
const dynamo = encryptedDynamoDB({ encryptionClient })
```
Optional: logger and error handler [#optional-logger-and-error-handler]
```typescript filename="setup-with-options.ts"
const dynamo = encryptedDynamoDB({
encryptionClient,
options: {
logger: {
error: (message, error) => console.error(`[DynamoDB] ${message}`, error),
},
errorHandler: (error) => {
console.error(`[${error.code}] ${error.message}`)
},
},
})
```
Encrypt and write [#encrypt-and-write]
Single item [#single-item]
Encrypt a model, then pass the result to a standard DynamoDB `PutCommand`:
```typescript filename="encrypt-single.ts"
import { PutCommand } from "@aws-sdk/lib-dynamodb"
const user = {
pk: "user#1",
email: "alice@example.com", // will be encrypted
name: "Alice Smith", // will be encrypted
role: "admin", // not in schema, passes through
}
const result = await dynamo.encryptModel(user, users)
if (result.failure) {
console.error("Encryption failed:", result.failure.message)
} else {
await docClient.send(new PutCommand({
TableName: "Users",
Item: result.data,
// result.data looks like:
// {
// pk: "user#1",
// email__source: "",
// email__hmac: "",
// name__source: "",
// role: "admin",
// }
}))
}
```
Bulk items [#bulk-items]
```typescript filename="encrypt-bulk.ts"
import { BatchWriteCommand } from "@aws-sdk/lib-dynamodb"
const items = [
{ pk: "user#1", email: "alice@example.com", name: "Alice" },
{ pk: "user#2", email: "bob@example.com", name: "Bob" },
]
const result = await dynamo.bulkEncryptModels(items, users)
if (!result.failure) {
await docClient.send(new BatchWriteCommand({
RequestItems: {
Users: result.data.map(item => ({
PutRequest: { Item: item },
})),
},
}))
}
```
Read and decrypt [#read-and-decrypt]
Single item [#single-item-1]
```typescript filename="decrypt-single.ts"
import { GetCommand } from "@aws-sdk/lib-dynamodb"
const getResult = await docClient.send(new GetCommand({
TableName: "Users",
Key: { pk: "user#1" },
}))
const result = await dynamo.decryptModel(getResult.Item, users)
if (!result.failure) {
console.log(result.data)
// { pk: "user#1", email: "alice@example.com", name: "Alice Smith", role: "admin" }
}
```
Bulk items [#bulk-items-1]
```typescript filename="decrypt-bulk.ts"
import { BatchGetCommand } from "@aws-sdk/lib-dynamodb"
const batchResult = await docClient.send(new BatchGetCommand({
RequestItems: {
Users: {
Keys: [{ pk: "user#1" }, { pk: "user#2" }],
},
},
}))
const result = await dynamo.bulkDecryptModels(
batchResult.Responses?.Users ?? [],
users,
)
if (!result.failure) {
for (const user of result.data) {
console.log(user.email) // plaintext
}
}
```
Querying with encrypted keys [#querying-with-encrypted-keys]
DynamoDB queries use key conditions, so you need to encrypt the search value into its HMAC form. Use `encryptionClient.encryptQuery()` to get the HMAC, then use it in your key condition.
Encrypted partition key [#encrypted-partition-key]
When an encrypted attribute is the partition key (e.g., `email__hmac`):
```typescript filename="query-partition-key.ts"
import { QueryCommand } from "@aws-sdk/lib-dynamodb"
// 1. Encrypt the search value to get the HMAC
const queryResult = await encryptionClient.encryptQuery([{
value: "alice@example.com",
column: users.email,
table: users,
queryType: "equality",
}])
if (queryResult.failure) {
throw new Error(`Query encryption failed: ${queryResult.failure.message}`)
}
const emailHmac = queryResult.data[0]?.hm
// 2. Use the HMAC in a DynamoDB query
const result = await docClient.send(new QueryCommand({
TableName: "Users",
KeyConditionExpression: "email__hmac = :email",
ExpressionAttributeValues: {
":email": emailHmac,
},
}))
// 3. Decrypt the results
const decrypted = await dynamo.bulkDecryptModels(result.Items ?? [], users)
```
Encrypted sort key [#encrypted-sort-key]
When an encrypted attribute is used as a sort key:
```typescript filename="query-sort-key.ts"
const result = await docClient.send(new GetCommand({
TableName: "Users",
Key: {
pk: "org#1", // partition key (plain)
email__hmac: emailHmac, // sort key (encrypted HMAC)
},
}))
const decrypted = await dynamo.decryptModel(result.Item, users)
```
Encrypted attribute in a GSI [#encrypted-attribute-in-a-gsi]
When querying a Global Secondary Index where the GSI key is an encrypted HMAC:
```typescript filename="query-gsi.ts"
const result = await docClient.send(new QueryCommand({
TableName: "Users",
IndexName: "EmailIndex",
KeyConditionExpression: "email__hmac = :email",
ExpressionAttributeValues: {
":email": emailHmac,
},
Limit: 1,
}))
if (result.Items?.length) {
const decrypted = await dynamo.decryptModel(result.Items[0], users)
}
```
Nested objects [#nested-objects]
The DynamoDB helper supports nested object encryption using `encryptedField`:
```typescript filename="schema.ts"
import { encryptedTable, encryptedColumn, encryptedField } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email"),
profile: {
name: encryptedField("profile.name"),
address: {
street: encryptedField("profile.address.street"),
},
},
})
```
```typescript filename="nested-encrypt.ts"
const user = {
id: "1",
email: "user@example.com",
profile: {
name: "Alice Johnson",
address: {
street: "123 Main St",
city: "Sydney", // not in schema — unchanged
},
},
}
const encryptedResult = await dynamo.encryptModel(user, users)
```
Nested objects support encryption up to 3 levels deep. Searchable encryption is not supported on nested fields.
Table design considerations [#table-design-considerations]
Key schema patterns [#key-schema-patterns]
| Pattern | Partition key | Sort key | Use case |
| ------------ | ------------- | ------------- | ----------------------------------------------------------------- |
| Plain PK | `pk` (plain) | — | Standard lookup by ID |
| Encrypted PK | `email__hmac` | — | Lookup by encrypted attribute |
| Encrypted SK | `pk` (plain) | `email__hmac` | Composite key with encrypted sort |
| GSI on HMAC | `pk` (plain) | — | Query by encrypted attribute via GSI with `email__hmac` as GSI PK |
What you can query [#what-you-can-query]
* Equality on `__hmac` attributes (exact match only)
* `attribute_exists(email__source)` / `attribute_not_exists(email__source)` in condition expressions
What you cannot query [#what-you-cannot-query]
* Range or comparison on encrypted attributes (no `BETWEEN`, `<`, `>` on `__source`)
* Substring matching on encrypted attributes (no `begins_with`, `contains` on `__source`)
* `__source` values are encrypted binary — only equality via `__hmac` is supported
Audit logging [#audit-logging]
All operations support `.audit()` chaining for audit metadata:
```typescript filename="audit.ts"
const result = await dynamo
.encryptModel(user, users)
.audit({
metadata: {
sub: "user-id-123",
action: "user_registration",
timestamp: new Date().toISOString(),
},
})
```
Error handling [#error-handling]
All operations return `Result` with either `data` or `failure`:
```typescript filename="error-handling.ts"
const result = await dynamo.encryptModel(user, users)
if (result.failure) {
console.error(result.failure.message)
console.error(result.failure.code) // ProtectErrorCode | "DYNAMODB_ENCRYPTION_ERROR"
console.error(result.failure.details)
}
```
See [Error handling](/stack/reference/error-handling) for details on error codes and handling patterns.
API reference [#api-reference]
encryptedDynamoDB(config) [#encrypteddynamodbconfig]
```typescript filename="api-reference.ts"
import { encryptedDynamoDB } from "@cipherstash/stack/dynamodb"
const dynamo = encryptedDynamoDB({
encryptionClient: EncryptionClient,
options?: {
logger?: { error: (message: string, error: Error) => void }
errorHandler?: (error: EncryptedDynamoDBError) => void
}
})
```
Instance methods [#instance-methods]
| Method | Signature | Returns |
| ------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `encryptModel` | `(item: T, table: EncryptedTable)` | `EncryptModelOperation` |
| `bulkEncryptModels` | `(items: T[], table: EncryptedTable)` | `BulkEncryptModelsOperation` |
| `decryptModel` | `(item: Record, table: EncryptedTable)` | `DecryptModelOperation` (resolves to `Decrypted`) |
| `bulkDecryptModels` | `(items: Record[], table: EncryptedTable)` | `BulkDecryptModelsOperation` (resolves to `Decrypted[]`) |
All operations are thenable (awaitable) and support `.audit({ metadata })` chaining.
Querying encrypted attributes [#querying-encrypted-attributes]
Use the encryption client directly (not the DynamoDB helper):
```typescript filename="query-single.ts"
// Single value form (recommended for DynamoDB lookups):
const result = await encryptionClient.encryptQuery(
"search-value",
{ column: schema.fieldName, table: schema, queryType: "equality" }
)
const hmac = result.data?.hm
```
```typescript filename="query-batch.ts"
// Batch array form:
const batchResult = await encryptionClient.encryptQuery([{
value: "search-value",
column: schema.fieldName,
table: schema,
queryType: "equality",
}])
const hmac = batchResult.data[0]?.hm // Use in DynamoDB key conditions
```
Complete example [#complete-example]
```typescript filename="complete-example.ts"
import { DynamoDBClient } from "@aws-sdk/client-dynamodb"
import {
DynamoDBDocumentClient,
PutCommand,
GetCommand,
QueryCommand,
} from "@aws-sdk/lib-dynamodb"
import { Encryption } from "@cipherstash/stack"
import { encryptedDynamoDB } from "@cipherstash/stack/dynamodb"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
// Schema
const users = encryptedTable("users", {
email: encryptedColumn("email").equality(),
name: encryptedColumn("name"),
})
// Clients
const dynamoClient = new DynamoDBClient({ region: "us-east-1" })
const docClient = DynamoDBDocumentClient.from(dynamoClient)
const encryptionClient = await Encryption({ schemas: [users] })
const dynamo = encryptedDynamoDB({ encryptionClient })
// Write
const user = { pk: "user#1", email: "alice@example.com", name: "Alice" }
const encResult = await dynamo.encryptModel(user, users)
if (!encResult.failure) {
await docClient.send(new PutCommand({
TableName: "Users",
Item: encResult.data,
}))
}
// Read by primary key
const getResult = await docClient.send(new GetCommand({
TableName: "Users",
Key: { pk: "user#1" },
}))
const decResult = await dynamo.decryptModel(getResult.Item, users)
if (!decResult.failure) {
console.log(decResult.data.email) // "alice@example.com"
}
// Query by encrypted email (via HMAC)
const queryEnc = await encryptionClient.encryptQuery([{
value: "alice@example.com",
column: users.email,
table: users,
queryType: "equality",
}])
const hmac = queryEnc.data[0]?.hm
const queryResult = await docClient.send(new QueryCommand({
TableName: "Users",
IndexName: "EmailIndex",
KeyConditionExpression: "email__hmac = :e",
ExpressionAttributeValues: { ":e": hmac },
}))
const decrypted = await dynamo.bulkDecryptModels(queryResult.Items ?? [], users)
```
# Encrypt and decrypt
All operations return a `Result` object with either a `data` key (success) or a `failure` key (error).
See [Error handling](/stack/reference/error-handling) for details.
Single values [#single-values]
Encrypt [#encrypt]
```typescript filename="encrypt.ts"
const encrypted = await client.encrypt("user@example.com", {
column: users.email,
table: users,
})
if (encrypted.failure) {
console.error(encrypted.failure.type, encrypted.failure.message)
} else {
console.log(encrypted.data)
}
```
Decrypt [#decrypt]
```typescript filename="decrypt.ts"
const decrypted = await client.decrypt(encrypted.data)
if (decrypted.failure) {
console.error(decrypted.failure.message)
} else {
console.log(decrypted.data) // "user@example.com"
}
```
Model operations [#model-operations]
Encrypt or decrypt an entire object. Only fields matching your schema are encrypted. Other fields pass through unchanged.
Encrypt a model [#encrypt-a-model]
```typescript filename="encrypt-model.ts"
const user = {
id: "1",
email: "user@example.com", // defined in schema -> encrypted
address: "123 Main St", // defined in schema -> encrypted
createdAt: new Date(), // not in schema -> unchanged
metadata: { role: "admin" }, // not in schema -> unchanged
}
const encryptedResult = await client.encryptModel(user, users)
if (encryptedResult.failure) {
console.error("Encryption failed:", encryptedResult.failure.message)
return
}
const encryptedUser = encryptedResult.data
// {
// id: '1',
// email: { c: 'encrypted_data...' },
// address: { c: 'encrypted_data...' },
// createdAt: Date,
// metadata: { role: 'admin' }
// }
```
Decrypt a model [#decrypt-a-model]
`decryptModel` automatically detects and decrypts any encrypted fields:
```typescript filename="decrypt-model.ts"
const decryptedResult = await client.decryptModel(encryptedUser)
if (decryptedResult.failure) {
console.error("Decryption failed:", decryptedResult.failure.message)
return
}
const decryptedUser = decryptedResult.data
```
Schema-aware return types [#schema-aware-return-types]
The return type of `encryptModel` is schema-aware: fields matching the table schema are typed as `Encrypted`, while other fields retain their original types. Let TypeScript infer the type parameters from the arguments:
```typescript filename="encrypt-model.ts"
const result = await client.encryptModel(user, users)
// result.data.email is typed as Encrypted
// result.data.id is typed as string
// result.data.createdAt is typed as Date
```
Passing an explicit type parameter (e.g., `client.encryptModel(...)`) is supported for backward compatibility. The return type degrades to `User`:
```typescript filename="encrypt-model.ts"
type User = {
id: string
email: string | null
address: string | null
createdAt: Date
}
const result = await client.encryptModel(user, users)
const back = await client.decryptModel(encryptedUser)
```
For full schema-aware types with explicit parameters, provide both the model type and the schema type:
```typescript filename="encrypt-model.ts"
const result = await client.encryptModel(user, users)
```
Bulk operations [#bulk-operations]
All bulk methods make a single call to ZeroKMS regardless of the number of records, while still using a unique key per value.
Bulk encrypt and decrypt values [#bulk-encrypt-and-decrypt-values]
```typescript filename="bulk-encrypt.ts"
const plaintexts = [
{ id: "u1", plaintext: "alice@example.com" },
{ id: "u2", plaintext: "bob@example.com" },
{ id: "u3", plaintext: null }, // null values are preserved
]
const encrypted = await client.bulkEncrypt(plaintexts, {
column: users.email,
table: users,
})
```
```typescript filename="bulk-decrypt.ts"
const decrypted = await client.bulkDecrypt(encrypted.data)
for (const item of decrypted.data) {
if ("data" in item) {
console.log(`${item.id}: ${item.data}`)
} else {
console.error(`${item.id} failed: ${item.error}`)
}
}
```
Bulk encrypt and decrypt models [#bulk-encrypt-and-decrypt-models]
```typescript filename="bulk-encrypt-models.ts"
const userModels = [
{ id: "1", email: "alice@example.com", address: "123 Main St" },
{ id: "2", email: "bob@example.com", address: "456 Oak Ave" },
]
const encrypted = await client.bulkEncryptModels(userModels, users)
const decrypted = await client.bulkDecryptModels(encrypted.data)
```
Bulk model operations also support type parameters:
```typescript filename="bulk-encrypt-models.ts"
const result = await client.bulkEncryptModels(userModels, users)
const back = await client.bulkDecryptModels(encrypted.data)
```
Identity-aware operations [#identity-aware-operations]
Any encrypt or decrypt operation can be scoped to a specific user with a lock context.
See [Identity-aware encryption](/stack/cipherstash/encryption/identity) for details.
```typescript filename="identity-encrypt.ts"
const encrypted = await client
.encryptModel(user, users)
.withLockContext(lockContext)
const decrypted = await client
.decryptModel(encryptedUser)
.withLockContext(lockContext)
// Also works with bulk operations
const bulkEncrypted = await client
.bulkEncryptModels(userModels, users)
.withLockContext(lockContext)
const bulkDecrypted = await client
.bulkDecryptModels(encryptedUsers)
.withLockContext(lockContext)
```
Audit logging [#audit-logging]
All operations support `.audit()` for attaching metadata to the ZeroKMS audit trail. Pass any structured metadata you want recorded alongside the operation.
```typescript filename="audit.ts"
const result = await client
.encrypt(plaintext, { column: users.email, table: users })
.audit({ metadata: { action: "create" } })
```
Chain `.audit()` with `.withLockContext()` on the same operation:
```typescript filename="audit-model.ts"
const result = await client
.encryptModel(user, users)
.withLockContext(lockContext)
.audit({ metadata: { action: "user-signup", requestId: "abc-123" } })
```
# Identity-aware encryption
Lock encryption to a specific user by requiring a valid JWT for decryption. When a value is encrypted with a lock context, it can only be decrypted by presenting the same user's identity token.
How it works [#how-it-works]
Lock contexts require a Business or Enterprise workspace plan.
Lock contexts are useful for:
* Multi-tenant applications where each user's data must be isolated
* Compliance requirements that demand per-user encryption boundaries
* Applications where you need to prove that only authorized users accessed specific records
The flow is:
1. Create a `LockContext` instance.
2. Identify the user with their JWT.
3. Pass the lock context to encrypt and decrypt operations.
Basic usage [#basic-usage]
```typescript filename="identity.ts"
import { LockContext } from "@cipherstash/stack/identity"
// 1. Create a lock context (defaults to the "sub" claim)
const lc = new LockContext()
// 2. Identify the user with their JWT
const identifyResult = await lc.identify(userJwt)
if (identifyResult.failure) {
throw new Error(identifyResult.failure.message)
}
const lockContext = identifyResult.data
// 3. Encrypt with lock context
const encrypted = await client
.encrypt("sensitive data", { column: users.email, table: users })
.withLockContext(lockContext)
// 4. Decrypt with the same lock context
const decrypted = await client
.decrypt(encrypted.data)
.withLockContext(lockContext)
```
Supported operations [#supported-operations]
Lock contexts work with all encrypt and decrypt operations:
```typescript filename="identity.ts"
// Single operations
const encrypted = await client
.encryptModel(user, users)
.withLockContext(lockContext)
const decrypted = await client
.decryptModel(encryptedUser)
.withLockContext(lockContext)
// Bulk operations
const bulkEncrypted = await client
.bulkEncryptModels(userModels, users)
.withLockContext(lockContext)
const bulkDecrypted = await client
.bulkDecryptModels(encryptedUsers)
.withLockContext(lockContext)
// Query operations
const term = await client
.encryptQuery("user@example.com", {
column: users.email,
table: users,
})
.withLockContext(lockContext)
```
Custom identity claims [#custom-identity-claims]
Override the default context by specifying which identity claims to use:
```typescript filename="identity.ts"
const lc = new LockContext({
context: {
identityClaim: ["sub"], // this is the default
},
})
```
| Identity claim | Description |
| -------------- | ---------------------------------------- |
| `sub` | The user's subject identifier |
| `scopes` | The user's scopes set by your IDP policy |
Combine claims for identity and permissions scoping:
```typescript filename="identity.ts"
const lc = new LockContext({
context: {
identityClaim: ["sub", "scopes"],
},
})
```
Using with Clerk and Next.js [#using-with-clerk-and-nextjs]
Install the `@cipherstash/nextjs` package for automatic CTS token setup with [Clerk](https://clerk.com/):
```bash
npm install @cipherstash/nextjs
```
Set up middleware [#set-up-middleware]
In your `middleware.ts`, use `protectClerkMiddleware` to automatically generate CTS tokens for every user session:
```typescript filename="middleware.ts"
import { clerkMiddleware } from "@clerk/nextjs/server"
import { protectClerkMiddleware } from "@cipherstash/nextjs/clerk"
export default clerkMiddleware(async (auth, req) => {
return protectClerkMiddleware(auth, req)
})
```
Retrieve the CTS token [#retrieve-the-cts-token]
Use `getCtsToken` to get the CTS token for the current user:
```typescript filename="page.tsx"
import { getCtsToken } from "@cipherstash/nextjs"
export default async function Page() {
const ctsToken = await getCtsToken()
if (!ctsToken.success) {
// handle error
}
// ctsToken is ready to use
}
```
Create a LockContext with an existing CTS token [#create-a-lockcontext-with-an-existing-cts-token]
Since the CTS token is already available from the middleware, construct the `LockContext` directly. The CTS token has the shape `{ accessToken: string, expiry: number }`. Passing it directly avoids a second round-trip to CTS.
```typescript filename="page.tsx"
import { LockContext } from "@cipherstash/stack/identity"
import { getCtsToken } from "@cipherstash/nextjs"
export default async function Page() {
const ctsToken = await getCtsToken()
if (!ctsToken.success) {
// handle error
}
const lockContext = new LockContext({ ctsToken })
// Use lockContext with encrypt/decrypt operations
}
```
`getCtsToken` returns `{ success: true, ctsToken: CtsToken }` on success or `{ success: false, error: string }` on failure.
Error handling [#error-handling]
The `identify` method returns a `Result` type:
```typescript filename="identity.ts"
const result = await lc.identify(userJwt)
if (result.failure) {
// result.failure.type is 'CtsTokenError'
console.error("CTS token exchange failed:", result.failure.message)
}
```
Common failure scenarios:
| Scenario | Error type | Description |
| ----------------- | ------------------ | ------------------------------------------------ |
| Invalid JWT | `CtsTokenError` | The JWT token was rejected by CTS |
| Network failure | `CtsTokenError` | Could not reach the CTS endpoint |
| Missing workspace | Runtime error | `CS_WORKSPACE_CRN` is not configured |
| Expired CTS token | `LockContextError` | The CTS token has expired. Call `identify` again |
# /ENCRYPTION
Searchable field-level encryption for Postgres. Every value encrypted with its own unique key via [ZeroKMS](/stack/cipherstash/kms). Range queries, exact match, free-text fuzzy search, and JSON queries over ciphertext. Sub-millisecond overhead on existing indexes.
This is the primitive everything else depends on.
What you get [#what-you-get]
1. **Unique key per value.** Not a shared table key. Each field gets its own data encryption key, derived via ZeroKMS.
2. **Searchable encryption.** Exact match, free-text search, range queries, and ordering over ciphertext in PostgreSQL.
3. **Encrypted JSONB.** Query encrypted JSON fields using JSONPath selectors and containment operators.
4. **Bulk operations.** Encrypt or decrypt thousands of values in a single ZeroKMS call.
5. **Identity-aware encryption.** Tie encryption to a user's JWT. Only that user decrypts their data.
6. **Tenant isolation via keysets.** One [keyset](/stack/cipherstash/kms/keysets) per customer. Provable cryptographic separation, not policy enforcement.
7. **TypeScript-first.** Strongly typed schemas, results, and model operations.
How it works [#how-it-works]
1. **Initialize your project:** Run `npx stash init` to authenticate, install EQL, scaffold the encryption client, and write `.cipherstash/context.json`.
2. **Draft a plan:** Run `npx stash plan` to hand off to a coding agent, which produces a reviewable plan at `.cipherstash/plan.md`.
3. **Execute the plan:** Run `npx stash impl` to confirm the plan and let the agent wire up encryption in your codebase.
4. **Encrypt and store:** Encrypt values before writing to your database.
5. **Query encrypted data:** Encrypt query terms and run them against your encrypted columns.
6. **Decrypt on read:** Decrypt values when reading from the database.
All key management (key generation, derivation, and isolation) is handled by [ZeroKMS](/stack/cipherstash/kms). Encryption keys are organized into [Key Sets](/stack/cipherstash/kms/keysets), the same primitive that will power Secrets (coming soon) environment isolation.
Integration paths [#integration-paths]
| | Encryption SDK | CipherStash Proxy |
| ------------ | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| **Best for** | Teams who want fine-grained control over data encryption directly in their application | DevOps teams who want to add encryption to existing PostgreSQL apps with little to no code changes |
| **Setup** | `npx stash init` | Docker container, configure environment variables |
| **Database** | PostgreSQL (full searchable encryption) | PostgreSQL (transparent proxy) |
Performance [#performance]
* **Latency**: \< 5ms overhead for most operations ([benchmarks](https://app.artillery.io/share/sh_75edb9d22a060633bfdce04777ffd60d1bcfc88989393dc38d3a4924b83fc6cd))
* **Throughput**: Scales with your application performance
* **Setup time**: Running in local dev in \< 1 hour, production in \< 3 days
CipherStash CLI [#cipherstash-cli]
[CipherStash CLI](/stack/cipherstash/cli) (`stash`) is the dev-time CLI for setting up your database. It handles installing the EQL extension, validating schemas, and managing the encryption lifecycle. Think of it like Drizzle Kit or Prisma CLI: a companion tool that sets up the database while `@cipherstash/stack` handles runtime encryption.
`stash init` scaffolds the encryption client, installs EQL, and writes `.cipherstash/context.json`. Then run `stash plan` to draft an encryption plan and `stash impl` to execute it.
```bash
npx stash init # Interactive project setup (auth, EQL install, client scaffold)
npx stash plan # Draft a reviewable encryption plan
npx stash impl # Execute the plan with a coding agent
npx stash db validate # Check schema for misconfigurations
npx stash db status # Show EQL installation state
```
Next steps [#next-steps]
# Setting up indexes
Setting up indexes [#setting-up-indexes]
Encrypted columns need PostgreSQL indexes for fast queries. Without an index, the database performs a sequential scan: correct but slow at scale.
Index syntax differs between deployment types. Self-hosted PostgreSQL with full EQL installed supports custom operator classes and can use B-tree indexes directly on `eql_v2_encrypted` columns. Managed databases like Supabase cannot install operator families (they require superuser), so indexes must use extraction functions instead.
Deployment matrix [#deployment-matrix]
| Query type | Self-hosted (full EQL) | Supabase |
| ----------------- | ------------------------------------------------------------------------ | ---------------------------------------- |
| Equality | `USING btree (col)` with opclass, or `USING hash (eql_v2.hmac_256(col))` | `USING hash (eql_v2.hmac_256(col))` only |
| Range / ORDER BY | `USING btree (col)` with opclass | None (OPE-index work in progress) |
| Pattern match | `USING gin (eql_v2.bloom_filter(col))` | Same |
| JSONB containment | `USING gin (eql_v2.ste_vec(col))` | Same |
Range filters (`>`, `>=`, `<`, `<=`) work on Supabase without a range index (they use a sequential scan). `ORDER BY` on encrypted columns is not supported on Supabase at all. Sort application-side after decrypting results. Operator family support for Supabase is in development.
***
Equality [#equality]
Equality indexes speed up `WHERE col = $1` queries and `IN` lists.
**Self-hosted (B-tree with operator class):**
```sql
CREATE INDEX ON users USING btree (email);
```
This works because the full EQL install registers a B-tree operator class for `eql_v2_encrypted` that compares HMAC terms.
**Self-hosted or Supabase (hash on extraction function):**
```sql
CREATE INDEX ON users USING hash (eql_v2.hmac_256(email));
```
This form works on both deployment types. Use it when you want one index that works everywhere, or when you are on Supabase.
See queries: [Equality queries](/stack/cipherstash/encryption/queries#equality)
***
Match [#match]
Match indexes speed up `WHERE col LIKE $1` and `ILIKE` queries. They use a GIN index on the Bloom filter extracted from each encrypted value.
```sql
CREATE INDEX ON users USING gin (eql_v2.bloom_filter(name));
```
This form is identical for self-hosted and Supabase.
See queries: [Match queries](/stack/cipherstash/encryption/queries#match-free-text)
***
Range and order [#range-and-order]
Range indexes support `>`, `>=`, `<`, `<=`, `BETWEEN`, and `ORDER BY` on encrypted columns.
**Self-hosted (B-tree with operator class):**
```sql
CREATE INDEX ON users USING btree (age);
```
Requires the EQL operator family (`CREATE OPERATOR FAMILY`) to be installed. The full EQL install includes this. The `--exclude-operator-family` install flag omits it.
**Supabase:**
Functional range indexes for Supabase are not yet available. Range *filters* work without an index (sequential scan). `ORDER BY` on encrypted columns is not supported on Supabase.
See queries: [Range queries](/stack/cipherstash/encryption/queries#range-and-ordering)
***
JSONB [#jsonb]
JSONB indexes support path existence and containment queries on encrypted JSON columns.
```sql
CREATE INDEX ON documents USING gin (eql_v2.ste_vec(metadata));
```
This form is identical for self-hosted and Supabase.
See queries: [JSONB queries](/stack/cipherstash/encryption/queries#jsonb-queries)
***
Supabase query forms [#supabase-query-forms]
This is the most common source of silent performance problems with encrypted columns on Supabase.
A functional index on `eql_v2.hmac_256(email)` is only engaged when the query uses the same extraction function. A bare `WHERE email = $1` query does not use the index, even if the index exists. The database falls back to a sequential scan: your query returns correct results, but it scans every row.
**Wrong (does not use functional index):**
```sql
SELECT * FROM users WHERE email = $1::eql_v2_encrypted;
```
**Right (engages the functional index):**
```sql
SELECT * FROM users WHERE eql_v2.hmac_256(email) = eql_v2.hmac_256($1::eql_v2_encrypted);
```
SDK wrappers (Drizzle adapter, Supabase wrapper) generate the correct query form automatically. This only matters when you write raw SQL queries against Supabase encrypted columns. If you are using the Drizzle adapter or Supabase wrapper, no action is needed.
The same principle applies to `eql_v2.bloom_filter` and `eql_v2.ste_vec` indexes: the extraction function must appear in both the index definition and the query predicate.
***
Complete example [#complete-example]
```sql filename="migrations/add_encrypted_indexes.sql"
-- Equality index (Supabase-compatible form)
CREATE INDEX users_email_eq_idx ON users USING hash (eql_v2.hmac_256(email));
-- Match index
CREATE INDEX users_name_match_idx ON users USING gin (eql_v2.bloom_filter(name));
-- JSONB index
CREATE INDEX documents_metadata_ste_idx ON documents USING gin (eql_v2.ste_vec(metadata));
-- Range index (self-hosted only — requires operator family)
CREATE INDEX users_age_range_idx ON users USING btree (age);
```
***
Related [#related]
* [Searchable encryption queries](/stack/cipherstash/encryption/queries): Query patterns for each index type
* [Searchable encryption overview](/stack/cipherstash/encryption/searchable-encryption): How searchable indexes work
* [Supabase integration](/stack/cipherstash/supabase): Supabase-specific setup and limitations
* [EQL guide](/stack/reference/eql-guide): Full reference for EQL types and functions
# Model operations
Model operations [#model-operations]
Model methods encrypt or decrypt an entire object in one call. The SDK inspects your schema to find which fields to encrypt and leaves all other fields on the object unchanged.
This is the recommended approach when working with database records: pass the object in, get the encrypted (or decrypted) version back, and write it to the database.
For full method signatures, see the [`EncryptionClient` API reference](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient).
How schema-driven selection works [#how-schema-driven-selection-works]
When you call `encryptModel(record, schema)`, the SDK compares the object's keys against the columns declared in your `encryptedTable` schema. Fields that match a schema column are encrypted. Everything else passes through as-is.
Given this schema:
```typescript filename="schema.ts"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch(),
ssn: encryptedColumn("ssn").equality(),
})
```
And this record:
```typescript
const user = {
id: "user_123", // not in schema
email: "alice@example.com", // in schema
ssn: "123-45-6789", // in schema
createdAt: new Date(), // not in schema
role: "admin", // not in schema
}
```
The field selection looks like this:
| Field | In schema | After `encryptModel` |
| ----------- | --------- | -------------------- |
| `id` | No | `string` (unchanged) |
| `email` | Yes | `Encrypted` |
| `ssn` | Yes | `Encrypted` |
| `createdAt` | No | `Date` (unchanged) |
| `role` | No | `string` (unchanged) |
encryptModel [#encryptmodel]
Encrypts one object. Returns a `Result` wrapping the encrypted object.
```typescript filename="encrypt-model.ts"
import { Encryption } from "@cipherstash/stack"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch(),
ssn: encryptedColumn("ssn").equality(),
})
const client = await Encryption({ schemas: [users] })
const user = {
id: "user_123",
email: "alice@example.com",
ssn: "123-45-6789",
createdAt: new Date(),
}
const result = await client.encryptModel(user, users)
if (result.failure) {
throw new Error(`Encryption failed: ${result.failure.message}`)
}
const encryptedUser = result.data
// encryptedUser.email → Encrypted
// encryptedUser.ssn → Encrypted
// encryptedUser.id → "user_123" (unchanged)
```
Schema-aware types [#schema-aware-types]
TypeScript infers the return type from the schema. Let the compiler do the work: do not pass an explicit type parameter unless you need backward compatibility.
```typescript filename="encrypt-model-types.ts"
// Let TypeScript infer — the return type reflects exactly which fields are encrypted
const result = await client.encryptModel(user, users)
// result.data.email is typed as Encrypted
// result.data.id is typed as string
// Explicit type parameter — return type degrades to User
const result = await client.encryptModel(user, users)
// Explicit model and schema types — fully schema-aware
const result = await client.encryptModel(user, users)
```
decryptModel [#decryptmodel]
Decrypts one encrypted object. The SDK detects which fields are encrypted payloads and decrypts them. Non-encrypted fields pass through.
```typescript filename="decrypt-model.ts"
const decResult = await client.decryptModel(encryptedUser)
if (decResult.failure) {
throw new Error(`Decryption failed: ${decResult.failure.message}`)
}
const decryptedUser = decResult.data
// decryptedUser.email → "alice@example.com"
// decryptedUser.ssn → "123-45-6789"
// decryptedUser.id → "user_123"
```
`decryptModel` does not require a schema argument. It detects encrypted fields by inspecting the payload structure.
bulkEncryptModels [#bulkencryptmodels]
Encrypts an array of objects in a single ZeroKMS round-trip. All records share one network call, while each field in each record still gets its own unique key.
```typescript filename="bulk-encrypt-models.ts"
const records = [
{ id: "1", email: "alice@example.com", ssn: "111-22-3333", role: "admin" },
{ id: "2", email: "bob@example.com", ssn: "444-55-6666", role: "user" },
{ id: "3", email: "cara@example.com", ssn: "777-88-9999", role: "user" },
]
const result = await client.bulkEncryptModels(records, users)
if (result.failure) {
throw new Error(`Bulk encryption failed: ${result.failure.message}`)
}
// result.data is an array of encrypted records, same order as input
const encryptedRecords = result.data
```
Writing encrypted records to PostgreSQL [#writing-encrypted-records-to-postgresql]
```typescript filename="bulk-insert.ts"
import { Pool } from "pg"
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
async function createUsers(users: { email: string; ssn: string; role: string }[]) {
const result = await client.bulkEncryptModels(users, usersSchema)
if (result.failure) {
throw new Error(result.failure.message)
}
const values = result.data.map((r) => [r.email, r.ssn, r.role])
await pool.query(
`INSERT INTO users (email, ssn, role)
SELECT * FROM UNNEST($1::jsonb[], $2::jsonb[], $3::text[])`,
[
result.data.map((r) => r.email),
result.data.map((r) => r.ssn),
result.data.map((r) => r.role),
],
)
}
```
bulkDecryptModels [#bulkdecryptmodels]
Decrypts an array of encrypted records in a single ZeroKMS call. Returns `Decrypted[]` — an array of plain objects with all encrypted fields resolved back to their original types.
```typescript filename="bulk-decrypt-models.ts"
const decResult = await client.bulkDecryptModels(encryptedRecords)
if (decResult.failure) {
throw new Error(`Bulk decryption failed: ${decResult.failure.message}`)
}
for (const user of decResult.data) {
console.log(user.email, user.ssn)
}
```
Decrypting database query results [#decrypting-database-query-results]
Fetch rows from PostgreSQL and pass the array directly to `bulkDecryptModels`:
```typescript filename="fetch-decrypt.ts"
const { rows } = await pool.query("SELECT * FROM users LIMIT 100")
const decResult = await client.bulkDecryptModels(rows)
if (decResult.failure) {
throw new Error(decResult.failure.message)
}
const users = decResult.data
```
Failure handling [#failure-handling]
All model methods return a `Result` object. The top-level `failure` fires when the entire operation fails (network error, auth failure, invalid credentials). There is no per-item failure for model operations: if any record fails, the whole call fails.
```typescript filename="error-handling.ts"
const result = await client.bulkEncryptModels(records, users)
if (result.failure) {
console.error(result.failure.type) // e.g. "EncryptionError"
console.error(result.failure.message) // human-readable description
}
```
See [Error handling](/stack/reference/error-handling) for the full set of error types.
Identity-aware model operations [#identity-aware-model-operations]
Chain `.withLockContext()` to bind encryption to a user's JWT:
```typescript filename="model-with-identity.ts"
import { LockContext } from "@cipherstash/stack/identity"
const lc = new LockContext()
const lockContext = (await lc.identify(userJwt)).data!
// Single record
const encrypted = await client
.encryptModel(user, users)
.withLockContext(lockContext)
// Bulk records — one ZeroKMS call, all locked to the same identity
const bulkEncrypted = await client
.bulkEncryptModels(records, users)
.withLockContext(lockContext)
const bulkDecrypted = await client
.bulkDecryptModels(encryptedRecords)
.withLockContext(lockContext)
```
See [Identity-aware encryption](/stack/cipherstash/encryption/identity) for the full flow.
When to use model methods vs raw bulk [#when-to-use-model-methods-vs-raw-bulk]
| Scenario | Recommended method |
| --------------------------------------------------------------------- | ------------------------------------ |
| Inserting new records from an API payload | `encryptModel` / `bulkEncryptModels` |
| Reading records from a database and decrypting for display | `decryptModel` / `bulkDecryptModels` |
| Encrypting one specific field across many records (migration, import) | `bulkEncrypt` |
| Encrypting a single value for a query term | `encrypt` |
Use model methods when you have whole records to round-trip. Use raw bulk methods when you are targeting a single field across many records.
See [Bulk operations](/stack/cipherstash/encryption/bulk-operations) for `bulkEncrypt` and `bulkDecrypt`.
ORM integrations [#orm-integrations]
The Drizzle, Supabase, and DynamoDB adapters wrap model methods behind their own APIs:
* [Drizzle ORM](/stack/cipherstash/encryption/drizzle): `encryptModel` and `bulkEncryptModels` used behind `db.insert()`
* [Supabase](/stack/cipherstash/encryption/supabase): `encryptedSupabase` handles model encryption transparently
* [DynamoDB](/stack/cipherstash/encryption/dynamodb): `encryptedDynamoDB` wraps `PutItem` and `GetItem`
Next steps [#next-steps]
* [Bulk operations](/stack/cipherstash/encryption/bulk-operations): raw-value bulk encrypt and decrypt
* [Schema definition](/stack/cipherstash/encryption/schema): declare which fields to encrypt
* [Storing encrypted data](/stack/cipherstash/encryption/storing-data): raw SQL insert and retrieve patterns
* [Identity-aware encryption](/stack/cipherstash/encryption/identity): scope encryption to a user's JWT
# Prisma Next
Prisma Next [#prisma-next]
CipherStash provides first-class [Prisma Next](https://www.npmjs.com/package/@prisma-next/cli) integration through [`@cipherstash/prisma-next`](https://www.npmjs.com/package/@cipherstash/prisma-next). Declare encrypted columns directly in `schema.prisma` with `cipherstash.Encrypted*()` constructors, and use auto-encrypting query operators that make encrypted queries look like standard Prisma Next code.
The Prisma Next integration has a meaningfully shorter onboarding path than the [Drizzle](/stack/cipherstash/encryption/drizzle) or [Supabase](/stack/cipherstash/encryption/supabase) integrations because the framework's migration system absorbs the database-side install. **You do not run `stash db install` for the EQL bundle** — `prisma-next migration apply` installs the EQL extension in the same control-plane sweep that creates your application tables.
Installation [#installation]
```bash
npm install @cipherstash/stack @cipherstash/prisma-next
```
`@cipherstash/prisma-next` ships subpath exports for the control plane, the runtime plane, the bulk-encrypt middleware, and the all-in-one `./stack` setup helper. See the [subpath reference](#subpath-exports) below.
Authentication [#authentication]
CipherStash uses a per-developer identity model so every encrypt and decrypt against your workspace is attributable to a real user — not a shared `.env` file.
```bash
stash auth login # one-time, per developer
```
`stash auth login` runs a PKCE flow and caches the resulting credentials in your OS keychain. The `@cipherstash/stack` encryption client picks them up automatically with no env-var threading in local development.
The four `CS_*` env vars (`CS_WORKSPACE_CRN`, `CS_CLIENT_ID`, `CS_CLIENT_KEY`, `CS_CLIENT_ACCESS_KEY`) are reserved for production deployments and CI runners (application accounts where no human is at the keyboard). Provision them via the [CipherStash dashboard](https://dashboard.cipherstash.com/workspaces) → Settings → Access Keys when you're ready to deploy.
This identity story is universal across CipherStash integrations — Drizzle, Supabase, and Prisma Next all use the same PKCE / env-var split.
Database setup [#database-setup]
Prisma Next models its database state as **contract spaces**, and `@cipherstash/prisma-next` ships its EQL bundle (the `eql_v2_configuration` table, the `eql_v2_encrypted` composite type, the `ore_*` types, the `eql_v2.*` functions and operators) as one of those contract spaces. Registering the extension is the only thing you need to do — the framework handles plan, apply, and verify the same way it manages your application schema.
Register the extension [#register-the-extension]
```typescript filename="prisma-next.config.ts"
import { defineConfig } from "@prisma-next/cli/config-types"
import postgresAdapter from "@prisma-next/adapter-postgres/control"
import postgresDriver from "@prisma-next/driver-postgres/control"
import sql from "@prisma-next/family-sql/control"
import { prismaContract } from "@prisma-next/sql-contract-psl/provider"
import postgres from "@prisma-next/target-postgres/control"
import cipherstash from "@cipherstash/prisma-next/control"
export default defineConfig({
family: sql,
target: postgres,
driver: postgresDriver,
adapter: postgresAdapter,
extensionPacks: [cipherstash],
contract: prismaContract("./prisma/schema.prisma", {
output: "src/prisma/contract.json",
target: postgres,
}),
migrations: { dir: "migrations" },
})
```
Adding `cipherstash` to `extensionPacks` hands the framework the EQL bundle contract space; everything downstream (plan, apply, codec registration, `add_search_config` migration ops) follows from that single registration.
Install EQL + apply your schema in one step [#install-eql--apply-your-schema-in-one-step]
```bash
npx prisma-next contract emit # PSL → contract.{json,d.ts}
npx prisma-next migration plan --name initial
npx prisma-next migration apply # installs EQL bundle + your app schema
```
The apply step runs **two** contract spaces' migrations in the same transaction: the cipherstash extension's baseline (installs the EQL bundle SQL byte-for-byte, creates the `eql_v2_configuration` table, registers `eql_v2_encrypted`) and your application's own migrations (creates your tables with `eql_v2_encrypted` columns and `eql_v2_encrypted_constraint_*` check constraints). One command, both halves of the install.
Schema definition [#schema-definition]
Use `cipherstash.Encrypted*()` to declare encrypted columns directly in `schema.prisma`. Search-mode flags (`equality`, `freeTextSearch`, `orderAndRange`, `searchableJson`) default to `true`, so searchable encryption is the default for every cipherstash column.
```prisma filename="prisma/schema.prisma"
model User {
id String @id
email cipherstash.EncryptedString()
salary cipherstash.EncryptedDouble()
accountId cipherstash.EncryptedBigInt() @map("accountid")
birthday cipherstash.EncryptedDate()
emailVerified cipherstash.EncryptedBoolean() @map("emailverified")
preferences cipherstash.EncryptedJson()
@@map("users")
}
```
This is the **only** place per-column search-mode flags are declared. The stack-side `encryptedTable(...)` / `encryptedColumn(...)` schema, the EQL `add_search_config` migration ops, and the SDK's per-column index set are all derived from this file at runtime via [`cipherstashFromStack({ contractJson })`](#initialization).
Encrypted column types [#encrypted-column-types]
Each PSL constructor maps a JS plaintext to an EQL `cast_as` value and ships with a per-codec set of search-mode flags. Every flag defaults to `true`; pass `false` to opt out (`cipherstash.EncryptedString({ freeTextSearch: false })`).
| PSL constructor | JS plaintext | EQL `cast_as` | Search-mode flags |
| ------------------------------ | ----------------------- | ------------- | --------------------------------------------- |
| `cipherstash.EncryptedString` | `string` | `text` | `equality`, `freeTextSearch`, `orderAndRange` |
| `cipherstash.EncryptedDouble` | `number` (IEEE-754) | `double` | `equality`, `orderAndRange` |
| `cipherstash.EncryptedBigInt` | `bigint` | `big_int` | `equality`, `orderAndRange` |
| `cipherstash.EncryptedDate` | `Date` (calendar date) | `date` | `equality`, `orderAndRange` |
| `cipherstash.EncryptedBoolean` | `boolean` | `boolean` | `equality` |
| `cipherstash.EncryptedJson` | JSON-serialisable value | `jsonb` | `searchableJson` |
Each enabled flag installs one EQL search-config index — see [Indexes](/stack/cipherstash/encryption/indexes) for the index families.
When in doubt, leave every flag enabled. Enabling unused flags costs migration time (one extra `add_search_config` DDL op per flag per column) and EQL index storage; it does not affect the encrypt / decrypt path.
TypeScript schema authoring [#typescript-schema-authoring]
If you author your contract in TypeScript instead of PSL, the column factories are exported from `@cipherstash/prisma-next/column-types`:
```typescript filename="contract.ts"
import {
encryptedBigInt,
encryptedBoolean,
encryptedDate,
encryptedDouble,
encryptedJson,
encryptedString,
} from "@cipherstash/prisma-next/column-types"
import cipherstash from "@cipherstash/prisma-next/pack"
import sqlFamily from "@prisma-next/family-sql/pack"
import { defineContract, field, model } from "@prisma-next/sql-contract-ts/contract-builder"
import postgres from "@prisma-next/target-postgres/pack"
export const contract = defineContract({
family: sqlFamily,
target: postgres,
extensionPacks: { cipherstash },
models: {
User: model("User", {
fields: {
id: field.column({ codecId: "pg/int4@1", nativeType: "int4" })
.defaultSql("autoincrement()").id(),
email: field.column(encryptedString({ orderAndRange: true })),
salary: field.column(encryptedDouble()),
accountId: field.column(encryptedBigInt()).columnName("accountid"),
birthday: field.column(encryptedDate()),
emailVerified: field.column(encryptedBoolean()).columnName("emailverified"),
profile: field.column(encryptedJson()),
},
}).sql({ table: "users" }),
},
})
```
PSL- and TS-authored contracts emit byte-identical `contract.json` for every codec.
Initialization [#initialization]
`cipherstashFromStack({ contractJson })` is the one-call setup. It derives the stack encryption schemas from your contract, constructs the `@cipherstash/stack` `EncryptionClient`, builds the SDK adapter, and returns ready-to-spread `extensions` and `middleware` for `postgres({...})`.
```typescript filename="src/db.ts"
import "dotenv/config"
import { cipherstashFromStack } from "@cipherstash/prisma-next/stack"
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({
contractJson,
extensions: cipherstash.extensions,
middleware: cipherstash.middleware,
})
```
That's the entire setup. The bundled `cipherstash.encryptionClient` is also exposed on the return value if you need to talk to the SDK directly outside the ORM path (e.g. encrypting a search term for a custom raw-SQL query).
The schema-derivation step keeps one source of truth: `schema.prisma`. You never write a second `encryptedTable(...)` declaration that has to stay in lockstep with the PSL constructors — if you do supply an override `schemas` array, `cipherstashFromStack` validates it against the contract and throws on divergence at setup time.
Subpath exports [#subpath-exports]
| Subpath | Purpose |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `@cipherstash/prisma-next/stack` | One-call setup against `@cipherstash/stack`: `cipherstashFromStack`, `deriveStackSchemas`, `createCipherstashSdk` |
| `@cipherstash/prisma-next/control` | `SqlControlExtensionDescriptor` (contract space + pack meta + codec lifecycle hooks) |
| `@cipherstash/prisma-next/runtime` | Six envelope classes + `CipherstashSdk` + codec runtime + `decryptAll` + four free-standing helpers |
| `@cipherstash/prisma-next/middleware` | `bulkEncryptMiddleware(sdk)` |
| `@cipherstash/prisma-next/pack` | `cipherstashPackMeta` for TypeScript contract authoring |
| `@cipherstash/prisma-next/column-types` | Six TS factories: `encryptedString`, `encryptedDouble`, `encryptedBigInt`, `encryptedDate`, `encryptedBoolean`, `encryptedJson` |
`./control`, `./runtime`, `./middleware`, and `./stack` are tree-shakable along the same seams documented in the [DEVELOPING guide](https://github.com/cipherstash/stack/blob/main/packages/prisma-next/DEVELOPING.md). A runtime consumer never pulls the EQL bundle SQL; a control-plane consumer never pulls the envelope classes.
Insert encrypted data [#insert-encrypted-data]
Wrap each plaintext value in the corresponding `Encrypted*.from(...)` factory. The bulk-encrypt middleware coalesces every plaintext placeholder across a query into one `bulkEncrypt` SDK round-trip per `(table, column)` group — so an insert with N rows and M encrypted columns runs `min(N, M)` ZeroKMS round-trips, not N × M.
```typescript filename="insert.ts"
import {
EncryptedBigInt,
EncryptedBoolean,
EncryptedDate,
EncryptedDouble,
EncryptedJson,
EncryptedString,
} from "@cipherstash/prisma-next/runtime"
import { db } from "./db"
await db.orm.User.create({
id: "user-0",
email: EncryptedString.from("alice@example.com"),
salary: EncryptedDouble.from(95_000),
accountId: EncryptedBigInt.from(100_000_000_001n),
birthday: EncryptedDate.from(new Date("1990-04-12")),
emailVerified: EncryptedBoolean.from(true),
preferences: EncryptedJson.from({ theme: "dark", notifications: true }),
})
```
Query encrypted data [#query-encrypted-data]
Predicate operators are surfaced as **column methods** on the model accessor — `m.email.cipherstashEq(...)`. Sort and JSON SELECT-expression helpers are **free-standing functions** imported from `@cipherstash/prisma-next/runtime`.
Equality [#equality]
```typescript filename="query-equality.ts"
const rows = await db.orm.User
.where((u) => u.email.cipherstashEq("alice@example.com"))
.all()
```
Text search [#text-search]
```typescript filename="query-text-search.ts"
// ILIKE — case-insensitive substring match (requires freeTextSearch: true)
const rows = await db.orm.User
.where((u) => u.email.cipherstashIlike("%@example.com"))
.all()
```
Range queries [#range-queries]
```typescript filename="query-range.ts"
// Greater than
const rows = await db.orm.User
.where((u) => u.salary.cipherstashGt(100_000))
.all()
// Between (inclusive)
const rows = await db.orm.User
.where((u) =>
u.birthday.cipherstashBetween(
new Date("1985-01-01"),
new Date("1995-12-31"),
),
)
.all()
```
Array operators [#array-operators]
```typescript filename="query-array.ts"
// IN [...] on bigint
const rows = await db.orm.User
.where((u) =>
u.accountId.cipherstashInArray([100_000_000_001n, 100_000_000_004n]),
)
.all()
// Equality on a boolean column is expressed as a single-element InArray —
// booleans only carry the equality trait, so cipherstashEq is not surfaced.
const rows = await db.orm.User
.where((u) => u.emailVerified.cipherstashInArray([true]))
.all()
```
Sorting [#sorting]
Sort helpers are free-standing functions, imported from `@cipherstash/prisma-next/runtime`:
```typescript filename="query-sort.ts"
import { cipherstashAsc, cipherstashDesc } from "@cipherstash/prisma-next/runtime"
const rows = await db.orm.User
.orderBy((u) => cipherstashAsc(u.email))
.all()
```
Sort lowering uses the bare-column form (`ORDER BY ASC|DESC`) against the EQL ORE index — there's no `eql_v2.order_by_*(col)` wrapper to remember.
Combining conditions [#combining-conditions]
```typescript filename="query-combined.ts"
import { and } from "@prisma-next/sql-orm-client"
const rows = await db.orm.User
.where((u) =>
and(
u.email.cipherstashIlike("%@example.com"),
u.salary.cipherstashGt(50_000),
u.birthday.cipherstashLt(new Date("1990-01-01")),
),
)
.all()
```
JSONB queries [#jsonb-queries]
Encrypted JSON columns (`cipherstash.EncryptedJson`) support SELECT-expression helpers for path extraction. These require `searchableJson: true` (the default).
Extract value at path [#extract-value-at-path]
```typescript filename="query-jsonb-extract.ts"
import { cipherstashJsonbPathQueryFirst } from "@cipherstash/prisma-next/runtime"
// `cipherstashJsonbPathQueryFirst` returns Expression
// — the same encrypted type as the column — so it chains into follow-on
// JSON helpers or predicates.
const expr = cipherstashJsonbPathQueryFirst(u.preferences, "$.theme")
```
Get value with -> operator [#get-value-with---operator]
```typescript filename="query-jsonb-get.ts"
import { cipherstashJsonbGet } from "@cipherstash/prisma-next/runtime"
const expr = cipherstashJsonbGet(u.preferences, "$.theme")
```
`cipherstashJsonbPathExists` exists in the predicate surface but currently returns zero rows against the live EQL bundle — the EQL `jsonb_path_exists` function expects a client-side-hashed STE-VEC selector. Workaround: project all rows with `cipherstashJsonbPathQueryFirst` / `cipherstashJsonbGet` and apply client-side post-filtering. Tracked at [TML-2504](https://linear.app/prisma-company/issue/TML-2504).
Decrypt results [#decrypt-results]
Every read-side envelope carries the `(table, column)` it was decoded from, so `decrypt` and `decryptAll` route their bulk SDK calls by that key.
```typescript filename="decrypt.ts"
import { decryptAll } from "@cipherstash/prisma-next/runtime"
const rows = await db.orm.User.where(/* … */).all()
// One bulkDecrypt SDK round-trip per (table, column) group across the
// full result set, then synchronous plaintext reads off the cached envelopes.
await decryptAll(rows)
for (const row of rows) {
console.log(await row.email.decrypt()) // sync after decryptAll
console.log(await row.salary.decrypt())
}
```
Without `decryptAll`, each `await row.column.decrypt()` would round-trip individually.
Operator reference [#operator-reference]
Predicate operators (column methods) [#predicate-operators-column-methods]
| Operator | Required flag | Lowering | Applies to |
| -------------------------------------- | ---------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `cipherstashEq(plaintext)` | `equality` | `eql_v2.eq(self, $N)` | every cipherstash codec |
| `cipherstashNe(plaintext)` | `equality` | `NOT eql_v2.eq(self, $N)` | every cipherstash codec |
| `cipherstashInArray([p1, p2, ...])` | `equality` | `(eql_v2.eq(self, $1) OR eql_v2.eq(self, $2) OR ...)` | every cipherstash codec |
| `cipherstashNotInArray([p1, p2, ...])` | `equality` | `NOT (eql_v2.eq(self, $1) OR ...)` | every cipherstash codec |
| `cipherstashIlike(pattern)` | `freeTextSearch` | `eql_v2.ilike(self, $N)` | `EncryptedString` |
| `cipherstashNotIlike(pattern)` | `freeTextSearch` | `NOT eql_v2.ilike(self, $N)` | `EncryptedString` |
| `cipherstashGt(plaintext)` | `orderAndRange` | `eql_v2.gt(self, $N)` | `EncryptedString`, `EncryptedDouble`, `EncryptedBigInt`, `EncryptedDate` |
| `cipherstashGte(plaintext)` | `orderAndRange` | `eql_v2.gte(self, $N)` | as above |
| `cipherstashLt(plaintext)` | `orderAndRange` | `eql_v2.lt(self, $N)` | as above |
| `cipherstashLte(plaintext)` | `orderAndRange` | `eql_v2.lte(self, $N)` | as above |
| `cipherstashBetween(lo, hi)` | `orderAndRange` | `eql_v2.gte(self, $1) AND eql_v2.lte(self, $2)` | as above |
| `cipherstashNotBetween(lo, hi)` | `orderAndRange` | `NOT (eql_v2.gte(self, $1) AND eql_v2.lte(self, $2))` | as above |
| `cipherstashJsonbPathExists(path)` | `searchableJson` | `eql_v2.jsonb_path_exists(self, $N)` | `EncryptedJson` (see [TML-2504](https://linear.app/prisma-company/issue/TML-2504)) |
Free-standing helpers [#free-standing-helpers]
| Helper | Required flag | Returns | Applies to |
| ------------------------------------------- | ---------------- | ------------------------------------- | ------------------------------------------------------------------------ |
| `cipherstashAsc(col)` | `orderAndRange` | `OrderByItem` (`ORDER BY ASC`) | `EncryptedString`, `EncryptedDouble`, `EncryptedBigInt`, `EncryptedDate` |
| `cipherstashDesc(col)` | `orderAndRange` | `OrderByItem` (`ORDER BY DESC`) | as above |
| `cipherstashJsonbPathQueryFirst(col, path)` | `searchableJson` | `Expression` | `EncryptedJson` |
| `cipherstashJsonbGet(col, path)` | `searchableJson` | `Expression` | `EncryptedJson` |
Why cipherstash-namespaced operators? [#why-cipherstash-namespaced-operators]
EQL ciphertexts contain randomized nonces, so SQL `=` / `<` / `>` against an `eql_v2_encrypted` column always returns `false` for two encrypts of the same plaintext. The cipherstash codecs declare zero of the framework's built-in traits, so `m.email.eq(...)` is a **compile-time error** on a cipherstash column — no wrong-SQL footgun is possible. The namespaced replacements (`cipherstashEq`, etc.) lower to the corresponding `eql_v2.*` functions which short-circuit through the per-column EQL search-config index.
Override surface [#override-surface]
`cipherstashFromStack` accepts two optional overrides:
* **`schemas`** — extra `EncryptedTable` declarations for tables the contract doesn't model (e.g. legacy tables not yet under Prisma Next). For tables the contract **does** declare, the override must agree on column names, `cast_as`, and the installed index set — divergence throws at setup time so ZeroKMS can't end up with an index set that disagrees with the EQL bundle's installed configuration.
* **`encryptionConfig`** — pass-through to `Encryption({ config })` (keyset overrides, logging, alternate workspace).
If `cipherstashFromStack` isn't a fit (custom SDK against a non-stack KMS, multi-tenant routing), drop one layer down to the primitives exported from the same subpath:
* **`deriveStackSchemas(contractJson)`** — pure function returning `EncryptedTable[]` derived from the contract.
* **`createCipherstashSdk(encryptionClient, schemas)`** — wraps an already-constructed stack `EncryptionClient` in the framework-native `CipherstashSdk` shape.
Complete example [#complete-example]
A runnable end-to-end example lives at [`cipherstash/stack:examples/prisma`](https://github.com/cipherstash/stack/tree/main/examples/prisma). It bundles a docker-compose Postgres, a six-codec `User` schema, and a flow that exercises every operator category against a live ZeroKMS workspace.
```bash
git clone https://github.com/cipherstash/stack
cd stack/examples/prisma
cp .env.example .env
stash auth login
docker compose up -d
pnpm install
pnpm emit
pnpm migration:plan --name initial
pnpm migration:apply
pnpm start
```
Expected output:
```text
--- Insert (mixed-codec round-trip) ---
Inserted 4 rows across six cipherstash codecs.
--- cipherstashEq (string equality) ---
Found 1 row(s) for alice@example.com.
user-0: alice@example.com
--- cipherstashIlike (string free-text-search) ---
Found 3 row(s) matching %@example.com.
user-0: alice@example.com
user-1: bob@example.com
user-2: carol@example.com
--- cipherstashGt (double order-and-range) ---
Found 2 user(s) with salary > 100,000.
--- cipherstashBetween (date order-and-range) ---
Found 3 user(s) born between 1985 and 1995.
--- cipherstashInArray (bigint equality) ---
Found 2 user(s) whose accountId is in the supplied array.
--- cipherstashInArray (boolean equality-only) ---
Found 3 user(s) with emailVerified = true.
--- cipherstashAsc (bare-column ORDER BY) ---
user-0: email=alice@example.com
user-1: email=bob@example.com
user-2: email=carol@example.com
user-3: email=dave@otherorg.test
```
Security model [#security-model]
* **Plaintext lifetime.** Write-side handles retain their plaintext slot post-encrypt — JS strings are immutable and zeroing is best-effort, so the GC-driven lifecycle is the sufficient bound. The `Encrypted.from(plaintext)` envelope's `decrypt()` returns the plaintext synchronously without consulting the SDK.
* **Ciphertext routing.** Every read-side envelope carries the `(table, column)` it was decoded from; `decrypt` / `decryptAll` route their bulk SDK calls by that key so the SDK picks the right key material per column.
* **Operator semantics.** Encrypted equality uses `eql_v2.eq` (deterministic-index lookup over the `unique` index); free-text uses `eql_v2.ilike` (bloom-filter lookup over `match`); range uses `eql_v2.gt` / `lt` / `eql_v2_encrypted` operator overloads (order-revealing-encryption lookup over `ore`). The framework's built-in `eq` / `gt` / `ilike` are unreachable on cipherstash columns — no wrong-SQL footgun where a randomized EQL ciphertext is compared with `=` directly.
* **Plaintext redaction in implicit serialisation paths.** Every envelope's `toJSON`, `toString`, `valueOf`, `Symbol.toPrimitive`, and `Symbol.for('nodejs.util.inspect.custom')` paths return `[REDACTED]` (or, for `toJSON`, a `{ "$encryptedX": "" }` placeholder). Accidental `console.log`, `JSON.stringify`, template-literal interpolation, error string construction, and `util.inspect` paths cannot leak plaintext. Explicit access is via `envelope.expose()`.
* **Cancellation.** Every cipherstash-internal SDK call accepts an `AbortSignal`; mid-flight cancellation surfaces a `RUNTIME.ABORTED` envelope with a phase tag (`bulk-encrypt`, `decrypt`, or `decrypt-all`).
Known limitations [#known-limitations]
* **`cipherstashJsonbPathExists` predicate** — currently returns zero rows against the live EQL bundle because the function expects a client-side-hashed STE-VEC selector. The non-predicate JSON helpers (`cipherstashJsonbPathQueryFirst`, `cipherstashJsonbGet`) work correctly. Tracked at [TML-2504](https://linear.app/prisma-company/issue/TML-2504).
* **`EncryptedBigInt` capped at `Number.MAX_SAFE_INTEGER`** — `@cipherstash/stack`'s SDK accepts numeric plaintexts only for the `big_int` cast, and `createCipherstashSdk` converts `bigint → Number` at the SDK boundary with an eager safe-integer bounds check (throws on overflow). Values beyond `Number.MAX_SAFE_INTEGER` cannot be encrypted today.
* **Encrypted timestamp / datetime** — lexical comparison over text-serialised timestamps is correctness-fragile (timezones, DST, format variation). CipherStash currently offers only calendar-date encryption via `EncryptedDate`.
* **Non-bigint integer variants** — EQL supports `int`, `small_int`, `big_int`, `real`; the extension currently ships `bigint` (`big_int`) and IEEE-754 (`double`) only.
* **Re-encryption migration** — flipping a populated column from plain to encrypted requires re-encrypting existing rows. The codec lifecycle hook emits the right search-config DDL but does not touch row data. Use a hand-authored `dataTransform` migration until a framework primitive lands.
References [#references]
* [`@cipherstash/prisma-next` on npm](https://www.npmjs.com/package/@cipherstash/prisma-next)
* [Package README](https://github.com/cipherstash/stack/blob/main/packages/prisma-next/README.md) — authoritative technical spec.
* [Runnable example](https://github.com/cipherstash/stack/tree/main/examples/prisma).
* [Prisma Next CLI](https://www.npmjs.com/package/@prisma-next/cli).
* [EQL reference](/stack/reference/eql-guide) — encrypted operator semantics and search-config index types.
* [Indexes](/stack/cipherstash/encryption/indexes) — `unique`, `match`, `ore`, `ste_vec` index family details.
* [CipherStash CLI](/stack/cipherstash/cli) — `stash auth login`, workspace management, secrets.
# Searchable encryption queries
Searchable encryption queries [#searchable-encryption-queries]
This page covers the three query families available for encrypted columns: equality, match (free-text), and range/order. Each section shows the SDK predicate, the raw SQL form, the underlying EQL index, and links to the corresponding index setup.
For index creation (the `CREATE INDEX` statements your database needs), see [Setting up indexes](/stack/cipherstash/encryption/indexes).
For a conceptual overview of how searchable encryption works, see [Searchable encryption](/stack/cipherstash/encryption/searchable-encryption).
Equality [#equality]
Exact match on an encrypted column. Uses the `unique` (HMAC-SHA256) index.
**Schema:**
```typescript filename="src/schema.ts"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality(),
})
```
**SDK (single value):**
```typescript filename="src/queries.ts"
const term = await client.encryptQuery("alice@example.com", {
column: users.email,
table: users,
queryType: "equality",
})
const result = await pgClient.query(
"SELECT * FROM users WHERE email = $1",
[term.data],
)
```
**SDK (IN list):**
```typescript filename="src/queries.ts"
const terms = await client.encryptQuery([
{ value: "alice@example.com", column: users.email, table: users, queryType: "equality" as const },
{ value: "bob@example.com", column: users.email, table: users, queryType: "equality" as const },
])
// Use each term.data as a separate parameter, or build an ANY($1) query.
```
**Drizzle:**
```typescript filename="src/queries.ts"
const results = await db
.select()
.from(usersTable)
.where(await encryptionOps.eq(usersTable.email, "alice@example.com"))
```
**Supabase wrapper:**
```typescript filename="src/queries.ts"
const { data } = await eSupabase
.from("users", users)
.select("id, email")
.eq("email", "alice@example.com")
```
**Raw SQL (self-hosted with EQL operator classes):**
```sql
SELECT * FROM users WHERE email = $1::eql_v2_encrypted;
```
**Raw SQL (Supabase / functional index form):**
```sql
SELECT * FROM users WHERE eql_v2.hmac_256(email) = eql_v2.hmac_256($1::eql_v2_encrypted);
```
On Supabase, bare `WHERE email = $1` does not use the functional index. Wrap both sides with `eql_v2.hmac_256()` to engage the hash index. The SDK wrappers (Drizzle, Supabase wrapper) handle this automatically. See [Index setup: Supabase callout](/stack/cipherstash/encryption/indexes#supabase-query-forms).
**Underlying index:** [Equality index setup](/stack/cipherstash/encryption/indexes#equality)
***
Match (free-text) [#match-free-text]
Substring and full-text search on an encrypted column. Uses the `match` (Bloom filter) index. Corresponds to `LIKE` / `ILIKE` semantics.
**Schema:**
```typescript filename="src/schema.ts"
const users = encryptedTable("users", {
name: encryptedColumn("name").freeTextSearch(),
})
```
**SDK:**
```typescript filename="src/queries.ts"
const term = await client.encryptQuery("alice", {
column: users.name,
table: users,
queryType: "freeTextSearch",
})
const result = await pgClient.query(
"SELECT * FROM users WHERE name LIKE $1",
[term.data],
)
```
**Drizzle:**
```typescript filename="src/queries.ts"
const results = await db
.select()
.from(usersTable)
.where(await encryptionOps.ilike(usersTable.name, "%alice%"))
```
**Supabase wrapper:**
```typescript filename="src/queries.ts"
const { data } = await eSupabase
.from("users", users)
.select("id, name")
.ilike("name", "%alice%")
```
**Raw SQL:**
```sql
SELECT * FROM users WHERE name LIKE $1;
```
The Bloom filter index uses a GIN index on the extracted filter term. See [Match index setup](/stack/cipherstash/encryption/indexes#match).
**Underlying index:** [Match index setup](/stack/cipherstash/encryption/indexes#match)
***
Range and ordering [#range-and-ordering]
Comparison (`>`, `>=`, `<`, `<=`, `BETWEEN`) and `ORDER BY` on an encrypted column. Uses the `ore` (Order Revealing Encryption) index.
**Schema:**
```typescript filename="src/schema.ts"
const users = encryptedTable("users", {
age: encryptedColumn("age").dataType("number").orderAndRange(),
})
```
**SDK (range filter):**
```typescript filename="src/queries.ts"
const term = await client.encryptQuery(21, {
column: users.age,
table: users,
queryType: "orderAndRange",
})
const result = await pgClient.query(
"SELECT * FROM users WHERE age > $1",
[term.data],
)
```
**SDK, ORDER BY (self-hosted only):**
```typescript filename="src/queries.ts"
// Self-hosted PostgreSQL with EQL operator families installed:
const result = await pgClient.query(
"SELECT * FROM users ORDER BY age ASC",
)
// Without operator family support (Supabase, or --exclude-operator-family):
const result = await pgClient.query(
"SELECT * FROM users ORDER BY eql_v2.ore_block_u64_8_256(age) ASC",
)
```
**Drizzle:**
```typescript filename="src/queries.ts"
// Range
const results = await db
.select()
.from(usersTable)
.where(await encryptionOps.gte(usersTable.age, 18))
// Sort (requires operator family support; not available on Supabase)
const results = await db
.select()
.from(usersTable)
.orderBy(encryptionOps.asc(usersTable.age))
```
**Supabase wrapper:**
```typescript filename="src/queries.ts"
// Range filter works
const { data } = await eSupabase
.from("users", users)
.select("id, age")
.gte("age", 18)
// ORDER BY on encrypted columns is not supported on Supabase.
// Sort application-side after decrypting.
```
`ORDER BY` on encrypted columns requires EQL operator families, which need superuser access to install. Supabase does not grant superuser. Range *filters* (`>`, `>=`, `<`, `<=`) work on both self-hosted and Supabase. Sorting on encrypted columns is not currently supported on Supabase. Sort application-side after decrypting results. Operator family support for Supabase is being developed in collaboration with the Supabase and CipherStash teams.
**Underlying index:** [Range index setup](/stack/cipherstash/encryption/indexes#range-and-order)
***
JSONB queries [#jsonb-queries]
Query encrypted JSON columns using path existence or containment. Uses the `ste_vec` index.
**Schema:**
```typescript filename="src/schema.ts"
const documents = encryptedTable("documents", {
metadata: encryptedColumn("metadata").searchableJson(),
})
```
**SDK (path existence):**
```typescript filename="src/queries.ts"
const term = await client.encryptQuery("$.user.role", {
column: documents.metadata,
table: documents,
})
const result = await pgClient.query(
"SELECT * FROM documents WHERE cs_ste_vec_v2(metadata) @> $1",
[term.data],
)
```
**SDK (containment):**
```typescript filename="src/queries.ts"
const term = await client.encryptQuery({ role: "admin" }, {
column: documents.metadata,
table: documents,
})
```
**Drizzle:**
```typescript filename="src/queries.ts"
const results = await db
.select()
.from(documentsTable)
.where(await encryptionOps.jsonbPathExists(documentsTable.metadata, "$.user.role"))
```
**Underlying index:** [JSONB index setup](/stack/cipherstash/encryption/indexes#jsonb)
# Schema definition
Schemas tell the SDK which database columns to encrypt and what types of queries to support on the encrypted data.
Creating schema files [#creating-schema-files]
Declare your encryption schema in TypeScript, either in a single file or split across multiple files:
```txt
src/encryption/
└── schema.ts # single file
```
```txt
src/encryption/schemas/
├── users.ts # per-table files
└── posts.ts
```
Defining a schema [#defining-a-schema]
A schema maps your database tables and columns using `encryptedTable` and `encryptedColumn`:
```typescript filename="schema.ts"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
// TypeScript name Database table name
// ↓ ↓
export const protectedUsers = encryptedTable("users", {
// TypeScript name Database column name
// ↓ ↓
email: encryptedColumn("email"),
})
```
Index types [#index-types]
Full searchable encryption is only supported in Postgres. See [Searchable encryption](/stack/cipherstash/encryption/searchable-encryption) for details.
Index types determine what queries you can run on encrypted data. Methods are chainable. Call as many as you need on a single column.
```typescript filename="schema.ts"
export const protectedUsers = encryptedTable("users", {
email: encryptedColumn("email")
.equality() // exact match queries
.freeTextSearch() // full-text search
.orderAndRange(), // sorting and range queries
})
```
| Method | Purpose | SQL equivalent |
| ------------------- | -------------------------------------------- | -------------------------------------- |
| `.equality()` | Exact match lookups | `WHERE email = 'user@example.com'` |
| `.freeTextSearch()` | Full-text / fuzzy search | `WHERE description LIKE '%example%'` |
| `.orderAndRange()` | Sorting, comparison, range queries | `ORDER BY price ASC` |
| `.searchableJson()` | Encrypted JSONB path and containment queries | `WHERE metadata @> '{"role":"admin"}'` |
Only enable the indexes you need. Each additional index type has a performance cost.
Equality token filters [#equality-token-filters]
The `.equality()` method accepts an optional array of token filters that are applied before indexing:
```typescript filename="schema.ts"
email: encryptedColumn("email").equality([{ kind: "downcase" }])
```
| Filter | Description |
| ---------------------- | ------------------------------------------------------------------------------------------- |
| `{ kind: 'downcase' }` | Converts values to lowercase before comparison, enabling case-insensitive equality matching |
For columns storing JSON data, `.searchableJson()` is the recommended index. It automatically configures the column for encrypted JSONB path and containment queries. See [Searchable encryption](/stack/cipherstash/encryption/searchable-encryption) for details.
Data types [#data-types]
Use `.dataType()` to specify the plaintext type for a column:
```typescript filename="schema.ts"
encryptedColumn("age").dataType("number").orderAndRange()
```
| Data type | Description |
| ----------- | --------------------------------------------------------------- |
| `'string'` | Text values. This is the default. |
| `'text'` | Long-form text values. |
| `'number'` | Numeric values (integers and floats). |
| `'boolean'` | Boolean `true` / `false` values. |
| `'date'` | Date or timestamp values. |
| `'bigint'` | Large integer values. |
| `'json'` | JSON objects. Automatically set when using `.searchableJson()`. |
Free-text search options [#free-text-search-options]
Customize the tokenizer and filter settings for `.freeTextSearch()`:
```typescript filename="schema.ts"
encryptedColumn("bio").freeTextSearch({
tokenizer: { kind: "ngram", token_length: 3 }, // or { kind: "standard" }
token_filters: [{ kind: "downcase" }],
k: 6,
m: 2048,
include_original: false,
})
```
| Option | Type | Default | Description |
| ------------------ | ------------------------------------------------------------------- | ------------------------------------ | -------------------------------------------------- |
| `tokenizer` | `{ kind: 'standard' }` or `{ kind: 'ngram', token_length: number }` | `{ kind: 'ngram', token_length: 3 }` | Tokenization strategy |
| `token_filters` | `TokenFilter[]` | `[{ kind: 'downcase' }]` | Filters applied to tokens before indexing |
| `k` | `number` | `6` | Number of hash functions for the bloom filter |
| `m` | `number` | `2048` | Size of the bloom filter in bits |
| `include_original` | `boolean` | `true` | Whether to include the original value in the index |
Nested objects [#nested-objects]
CipherStash Encryption supports nested objects in your schema, allowing you to encrypt nested properties. You can define nested objects up to 3 levels deep using `encryptedField`.
Searchable encryption is not supported on nested objects. This is most useful for NoSQL databases or less structured data.
Using nested objects is not recommended for SQL databases. You should either use a JSON data type and encrypt the entire object, or use a separate column for each nested property.
```typescript filename="schema.ts"
import { encryptedTable, encryptedColumn, encryptedField } from "@cipherstash/stack/schema"
export const protectedUsers = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch(),
profile: {
name: encryptedField("profile.name"),
address: {
street: encryptedField("profile.address.street"),
location: {
coordinates: encryptedField("profile.address.location.coordinates"),
},
},
},
})
```
When working with nested objects:
* Each level can have its own encrypted fields
* The maximum nesting depth is 3 levels
* Null and undefined values are supported at any level
* Optional nested objects are supported
The schema builder does not validate values passed to `encryptedField` or `encryptedColumn`. Values must be unique. Duplicate or incorrect values cause unexpected behavior.
Encrypted JSONB [#encrypted-jsonb]
For columns that store JSON objects, use `.searchableJson()` to enable encrypted JSONB queries:
```typescript filename="schema.ts"
const documents = encryptedTable("documents", {
metadata: encryptedColumn("metadata").searchableJson(),
})
```
This enables both JSONPath selector queries and containment queries on the encrypted data.
Multiple tables [#multiple-tables]
Pass multiple schemas when initializing the client:
```typescript filename="encryption/index.ts"
import { Encryption } from "@cipherstash/stack"
const client = await Encryption({ schemas: [protectedUsers, documents] })
```
Type inference [#type-inference]
Infer plaintext and encrypted types from your schema:
```typescript filename="schema.ts"
import type { InferPlaintext, InferEncrypted } from "@cipherstash/stack/schema"
type UserPlaintext = InferPlaintext
// { email: string; ... }
type UserEncrypted = InferEncrypted
// { email: Encrypted; ... }
```
Client-safe exports [#client-safe-exports]
For client-side code where the native FFI module is not available, import schema builders from the `@cipherstash/stack/client` subpath:
```typescript filename="schema.ts"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/client"
```
This exports schema builders and types only — no native module dependency.
# Searchable encryption
Query encrypted data without decryption. Standard encryption breaks queries: encrypt a column and `WHERE`, `ORDER BY`, and `LIKE` stop working. CipherStash solves this with searchable encryption indexes that enable queries over ciphertext.
How it works [#how-it-works]
Each encrypted column can have one or more searchable indexes. When you encrypt a value, CipherStash creates index terms alongside the ciphertext. These terms are themselves encrypted — they reveal nothing about the plaintext — but they allow PostgreSQL to evaluate queries without decrypting.
The result: your data is encrypted at rest, in transit, and during query evaluation. CipherStash is [410,000x faster](https://github.com/cipherstash/tfhe-ore-bench) than homomorphic encryption, with sub-millisecond overhead on existing PostgreSQL indexes. Every decryption event is logged in [ZeroKMS](/stack/cipherstash/kms), giving you an audit trail for compliance with [SOC 2](https://cipherstash.com/compliance/soc2) and [BDSG](https://cipherstash.com/compliance/bdsg).
Supported query types [#supported-query-types]
| Query type | Index | Example |
| ------------------ | ---------------- | ----------------------------------------- |
| Exact match | `equality` | `WHERE email = ?` |
| Free-text search | `freeTextSearch` | `WHERE name LIKE '%alice%'` |
| Range / comparison | `orderAndRange` | `WHERE age > 21`, `ORDER BY created_at` |
| JSON containment | `searchableJson` | `WHERE metadata @> '{"role": "admin"}'` |
| Ordering | `orderAndRange` | `ORDER BY salary DESC` |
| Grouping | `equality` | `GROUP BY department` |
| Uniqueness | `equality` | `UNIQUE` constraints on encrypted columns |
Prerequisites [#prerequisites]
1. Install [EQL](/stack/reference/eql-guide) in your PostgreSQL database using the [CipherStash CLI](/stack/cipherstash/cli):
```bash
npx stash db install
```
2. Define your encryption schema with the appropriate search indexes
3. Create PostgreSQL indexes on your encrypted columns. See [Setting up indexes](/stack/cipherstash/encryption/indexes) for the correct `CREATE INDEX` syntax for your deployment (self-hosted vs Supabase).
Index creation syntax differs between self-hosted PostgreSQL and Supabase. On Supabase, queries against encrypted columns require a specific function-wrapped form to engage functional indexes. See [Setting up indexes](/stack/cipherstash/encryption/indexes) for the full guide.
What is EQL? [#what-is-eql]
EQL (Encrypt Query Language) is a set of PostgreSQL extensions that enable searching and sorting on encrypted data. It provides custom data types, comparison functions, and index support for encrypted values.
Any encrypted column must use the `eql_v2_encrypted` type:
```sql
CREATE TABLE users (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email eql_v2_encrypted
);
```
The encryptQuery function [#the-encryptquery-function]
Encrypt a query term so you can search encrypted data in PostgreSQL:
```typescript filename="search.ts"
const term = await client.encryptQuery("user@example.com", {
column: schema.email,
table: schema,
})
if (term.failure) {
// Handle the error
}
console.log(term.data) // encrypted query term
```
Properties [#properties]
| Property | Description |
| ------------ | ----------------------------------------------------------------------------------------------------------- |
| `value` | The value to search for |
| `column` | The column to search in |
| `table` | The table to search in |
| `queryType` | *(optional)* The query type — auto-inferred from the column's indexes when omitted |
| `returnType` | *(optional)* The output format — `'eql'` (default), `'composite-literal'`, or `'escaped-composite-literal'` |
returnType options [#returntype-options]
| `returnType` | Return type | Description |
| ----------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `'eql'` (default) | `Encrypted` object | Raw EQL JSON payload. Use with parameterized queries (`$1`) or ORMs that accept JSON objects. |
| `'composite-literal'` | `string` | PostgreSQL composite literal format `("json")`. Use with Supabase `.eq()` or other APIs that require a string value. |
| `'escaped-composite-literal'` | `string` | Escaped composite literal `"(\"json\")"`. Use when the query string will be embedded inside another string or JSON value. |
Batch queries [#batch-queries]
Encrypt multiple query terms in a single call:
```typescript filename="search.ts"
const terms = await client.encryptQuery([
{ value: "user@example.com", column: schema.email, table: schema },
{ value: "18", column: schema.age, table: schema },
])
```
Query types [#query-types]
Exact matching [#exact-matching]
Use `.equality()` for exact match lookups:
```typescript filename="search.ts"
const term = await client.encryptQuery("user@example.com", {
column: schema.email,
table: schema,
})
const result = await pgClient.query(
"SELECT * FROM users WHERE email_encrypted = $1",
[term.data]
)
```
Free-text search [#free-text-search]
Use `.freeTextSearch()` for text-based searches:
```typescript filename="search.ts"
const term = await client.encryptQuery("example", {
column: schema.email,
table: schema,
})
const result = await pgClient.query(
"SELECT * FROM users WHERE email_encrypted LIKE $1",
[term.data]
)
```
Sorting and range queries [#sorting-and-range-queries]
Use `.orderAndRange()` for sorting and range operations:
If your PostgreSQL database does not support EQL Operator families, use the `eql_v2.ore_block_u64_8_256()` function for `ORDER BY`. Databases with Operator family support can use `ORDER BY` directly on the encrypted column name.
```typescript filename="search.ts"
const result = await pgClient.query(
"SELECT * FROM users ORDER BY eql_v2.ore_block_u64_8_256(age_encrypted) ASC"
)
```
JSONB queries with .searchableJson() [#jsonb-queries-with-searchablejson]
For columns storing JSON data, `.searchableJson()` is the recommended approach. It automatically infers the correct query operation from the plaintext value type.
```typescript filename="schema.ts"
const documents = encryptedTable("documents", {
metadata: encryptedColumn("metadata_encrypted").searchableJson(),
})
```
Auto-inference [#auto-inference]
| Plaintext type | Inferred operation | Use case |
| ----------------------------------- | ------------------ | ------------------------- |
| `string` (e.g. `'$.user.email'`) | `steVecSelector` | JSONPath selector queries |
| `object` (e.g. `{ role: 'admin' }`) | `steVecTerm` | Containment queries |
| `array` (e.g. `['admin', 'user']`) | `steVecTerm` | Containment queries |
| `null` | Returns `null` | Null handling |
JSONPath selector queries [#jsonpath-selector-queries]
Pass a string to query by JSON path:
```typescript filename="search.ts"
const pathTerm = await client.encryptQuery("$.user.email", {
column: documents.metadata,
table: documents,
})
// Nested path
const nestedTerm = await client.encryptQuery("$.user.profile.role", {
column: documents.metadata,
table: documents,
})
// Array index
const arrayTerm = await client.encryptQuery("$.items[0].name", {
column: documents.metadata,
table: documents,
})
```
Use the `toJsonPath` helper to convert dot-notation paths:
```typescript filename="search.ts"
import { toJsonPath } from "@cipherstash/stack"
toJsonPath("user.email") // '$.user.email'
toJsonPath("$.user.email") // '$.user.email' (unchanged)
toJsonPath("name") // '$.name'
```
Containment queries [#containment-queries]
Pass an object or array to query by containment:
```typescript filename="search.ts"
// Key-value containment
const roleTerm = await client.encryptQuery({ role: "admin" }, {
column: documents.metadata,
table: documents,
})
// Nested object containment
const nestedTerm = await client.encryptQuery(
{ user: { profile: { role: "admin" } } },
{ column: documents.metadata, table: documents }
)
// Array containment
const tagsTerm = await client.encryptQuery(["admin", "user"], {
column: documents.metadata,
table: documents,
})
```
Bare numbers and booleans are not supported as top-level `searchableJson` query values. For `orderAndRange` queries, bare numbers are supported directly. Wrap them in an object or array for `searchableJson`.
```typescript filename="search.ts"
// Wrong for searchableJson: will fail (works for orderAndRange)
await client.encryptQuery(42, { column: documents.metadata, table: documents })
// Correct — wrap in an object
await client.encryptQuery({ value: 42 }, { column: documents.metadata, table: documents })
```
Use the `buildNestedObject` helper to construct nested containment queries:
```typescript filename="search.ts"
import { buildNestedObject } from "@cipherstash/stack"
buildNestedObject("user.role", "admin")
// Returns: { user: { role: 'admin' } }
```
Using JSONB queries in SQL [#using-jsonb-queries-in-sql]
Specify `returnType: 'composite-literal'` for direct use in SQL:
```typescript filename="search.ts"
const term = await client.encryptQuery([{
value: "$.user.email",
column: documents.metadata,
table: documents,
returnType: "composite-literal",
}])
const result = await pgClient.query(
"SELECT * FROM documents WHERE cs_ste_vec_v2(metadata_encrypted) @> $1",
[term.data[0]]
)
```
Batch JSONB queries [#batch-jsonb-queries]
Use `encryptQuery` with an array to encrypt multiple JSONB query terms in a single call. Each item can have a different plaintext type:
```typescript filename="search.ts"
const terms = await client.encryptQuery([
{
value: "$.user.email", // string -> JSONPath selector
column: documents.metadata,
table: documents,
},
{
value: { role: "admin" }, // object -> containment
column: documents.metadata,
table: documents,
},
{
value: ["tag1", "tag2"], // array -> containment
column: documents.metadata,
table: documents,
},
])
```
Advanced: Explicit query types [#advanced-explicit-query-types]
For advanced use cases, you can specify the query type explicitly instead of relying on auto-inference:
| Approach | `queryType` | When to use |
| -------------------------------- | ----------------------------- | ------------------------------------------------------------- |
| **searchableJson** (recommended) | `'searchableJson'` or omitted | Auto-infers from plaintext type. Use for most JSONB queries. |
| **steVecSelector** (explicit) | `'steVecSelector'` | When you want to be explicit about JSONPath selector queries. |
| **steVecTerm** (explicit) | `'steVecTerm'` | When you want to be explicit about containment queries. |
```typescript filename="search.ts"
// Explicit steVecSelector
const selectorTerm = await client.encryptQuery("$.user.email", {
column: documents.metadata,
table: documents,
queryType: "steVecSelector",
})
// Explicit steVecTerm
const containTerm = await client.encryptQuery({ role: "admin" }, {
column: documents.metadata,
table: documents,
queryType: "steVecTerm",
})
```
Implementation example [#implementation-example]
Using the pg client [#using-the-pg-client]
```typescript filename="search.ts"
import { Client } from "pg"
import { Encryption } from "@cipherstash/stack"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const schema = encryptedTable("users", {
email: encryptedColumn("email_encrypted")
.equality()
.freeTextSearch()
.orderAndRange(),
})
const pgClient = new Client({ connectionString: process.env.DATABASE_URL })
const client = await Encryption({ schemas: [schema] })
// Insert encrypted data
const encryptedData = await client.encryptModel({ email: "user@example.com" }, schema)
await pgClient.query(
"INSERT INTO users (email_encrypted) VALUES ($1::jsonb)",
[encryptedData.data.email_encrypted]
)
// Search encrypted data
const searchTerm = await client.encryptQuery("example.com", {
column: schema.email,
table: schema,
})
const result = await pgClient.query(
"SELECT * FROM users WHERE email_encrypted LIKE $1",
[searchTerm.data]
)
// Decrypt results
const decryptedData = await client.bulkDecryptModels(result.rows)
```
Best practices [#best-practices]
Schema design [#schema-design]
* Use `.equality()` for exact matches (most efficient)
* Use `.freeTextSearch()` for text-based searches (more expensive)
* Use `.orderAndRange()` for numerical data and sorting (most expensive)
* Only enable features you need to minimize performance impact
* Use `eql_v2_encrypted` column type in your database schema for encrypted columns
Security [#security]
* Never store unencrypted sensitive data
* Keep your CipherStash secrets secure
* Use parameterized queries to prevent SQL injection
Performance [#performance]
* Index your encrypted columns appropriately
* Use bulk operations (`bulkEncryptModels`, `bulkDecryptModels`) when working with multiple records
* Monitor query performance and consider the impact of search operations on your database
* Cache frequently accessed data
Error handling [#error-handling]
* Always check for failures with any `@cipherstash/stack` method
* Handle encryption errors aggressively
* Handle decryption errors gracefully
# Storing encrypted data
This guide shows how to persist encrypted values in your database and retrieve them later using raw SQL. If you're using an ORM, see the [Drizzle integration](/stack/cipherstash/encryption/drizzle) or [Supabase integration](/stack/cipherstash/encryption/supabase) instead.
Database compatibility [#database-compatibility]
The Encryption SDK works with any database that supports JSON or JSONB column types. Searchable encryption is available in PostgreSQL (via [EQL](/stack/reference/eql-guide)) and [DynamoDB](/stack/cipherstash/encryption/dynamodb).
| Database | Standard encryption | Searchable encryption |
| ----------------------------------- | ------------------- | --------------------- |
| PostgreSQL 15+ | Yes | Yes |
| AWS RDS PostgreSQL | Yes | Yes |
| AWS Aurora PostgreSQL | Yes | Yes |
| GCP Cloud SQL for PostgreSQL | Yes | Yes |
| Azure Database for PostgreSQL | Yes | Yes |
| OCI Database Service for PostgreSQL | Yes | Yes |
| DynamoDB | Yes | Yes |
| Supabase | Yes | Yes |
| Neon Postgres | Yes | — |
| MySQL | Yes | — |
| CockroachDB | Yes | — |
PostgreSQL guide [#postgresql-guide]
Install EQL (optional) [#install-eql-optional]
To enable searchable encryption in PostgreSQL, install [EQL](/stack/reference/eql-guide) using the [CipherStash CLI](/stack/cipherstash/cli) so you can use the `eql_v2_encrypted` data type. If you don't need searchable encryption, use `jsonb` instead (you can migrate to EQL later).
```bash
npx stash db install
```
* Use `eql_v2_encrypted` if you need searchable encryption (or want the option later)
* Use `jsonb` if you don't need searchable encryption or don't mind changing the data type later
Create a table [#create-a-table]
Encrypted values ([CipherCells](/stack/reference/cipher-cell)) are stored as either `jsonb` or `eql_v2_encrypted` in PostgreSQL.
With EQL:
```sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email eql_v2_encrypted NOT NULL
);
```
With JSONB:
```sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email jsonb NOT NULL
);
```
Encrypt and insert data [#encrypt-and-insert-data]
Encrypt the plaintext value and insert the resulting CipherCell into your database:
```typescript filename="insert.ts"
import { Encryption } from "@cipherstash/stack"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
import { Pool } from "pg"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality(),
})
const client = await Encryption({ schemas: [users] })
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
port: parseInt(process.env.DB_PORT || "5432"),
})
async function insertUser(email: string) {
const encryptResult = await client.encrypt(email, {
column: users.email,
table: users,
})
if (encryptResult.failure) {
throw new Error(`Encryption failed: ${encryptResult.failure.message}`)
}
// Use the ::jsonb cast to ensure Postgres treats the data as JSONB
const result = await pool.query(
"INSERT INTO users (email) VALUES ($1::jsonb) RETURNING id",
[encryptResult.data]
)
return result.rows[0].id
}
```
Always use the `::jsonb` cast when inserting encrypted values, even when using the `eql_v2_encrypted` column type. This ensures PostgreSQL correctly handles the CipherCell object.
Retrieve and decrypt data [#retrieve-and-decrypt-data]
Query the database and pass the CipherCell to the `decrypt` method:
```typescript filename="retrieve.ts"
async function getUserEmail(userId: number) {
const fetchResult = await pool.query(
"SELECT email FROM users WHERE id = $1",
[userId]
)
if (fetchResult.rows.length === 0) {
throw new Error("User not found")
}
const decryptResult = await client.decrypt(fetchResult.rows[0].email)
if (decryptResult.failure) {
throw new Error(`Decryption failed: ${decryptResult.failure.message}`)
}
return decryptResult.data
}
```
Bulk insert with UNNEST [#bulk-insert-with-unnest]
When inserting many rows, use `bulkEncrypt` with PostgreSQL's `UNNEST` for efficient batch inserts:
```typescript filename="bulk-insert.ts"
async function insertUsers(emails: string[]) {
const plaintexts = emails.map((email) => ({ plaintext: email }))
const encryptedResult = await client.bulkEncrypt(plaintexts, {
column: users.email,
table: users,
})
if (encryptedResult.failure) {
throw new Error(`Bulk encryption failed: ${encryptedResult.failure.message}`)
}
const result = await pool.query(
`INSERT INTO users (email)
SELECT * FROM UNNEST($1::jsonb[])`,
[encryptedResult.data.map((item) => item.data)]
)
return result.rowCount
}
```
Using `bulkEncrypt` instead of calling `encrypt` in a loop is significantly faster. It batches key derivation requests to ZeroKMS.
Next steps [#next-steps]
* [Searchable encryption](/stack/cipherstash/encryption/searchable-encryption): query encrypted data without decrypting it
* [Drizzle ORM integration](/stack/cipherstash/encryption/drizzle): encrypted queries with Drizzle
* [Bulk operations](/stack/cipherstash/encryption/encrypt-decrypt): encrypt and decrypt multiple values efficiently
* [Identity-aware encryption](/stack/cipherstash/encryption/identity): bind operations to authenticated identities
# Supabase
The `encryptedSupabase` wrapper makes encrypted queries look nearly identical to normal Supabase queries. It automatically handles encryption, decryption, `::jsonb` casts, and search term formatting.
Install [EQL](/stack/cipherstash/supabase#how-this-works) in your Supabase database using the [CipherStash CLI](/stack/cipherstash/cli) so encrypted columns can use the `eql_v2_encrypted` type.
```bash
npx stash db install --supabase
```
The `--supabase` flag installs a Supabase-compatible version of EQL and grants the required permissions on the `eql_v2` schema to `anon`, `authenticated`, and `service_role`.
Database schema [#database-schema]
Encrypted columns must be stored as `eql_v2_encrypted` or JSONB if you don't want searchable capabilities.
```sql filename="schema.sql"
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email eql_v2_encrypted NOT NULL, -- encrypted column
name eql_v2_encrypted NOT NULL, -- encrypted column
age eql_v2_encrypted, -- encrypted column (numeric)
role VARCHAR(50), -- regular column (not encrypted)
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
Setup [#setup]
```typescript filename="app/db.ts"
import { createClient } from "@supabase/supabase-js"
import { Encryption } from "@cipherstash/stack"
import { encryptedSupabase } from "@cipherstash/stack/supabase"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email")
.equality()
.freeTextSearch(),
name: encryptedColumn("name")
.equality()
.freeTextSearch(),
age: encryptedColumn("age")
.dataType("number")
.equality()
.orderAndRange(),
})
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
)
const encryptionClient = await Encryption({ schemas: [users] })
const eSupabase = encryptedSupabase({
encryptionClient,
supabaseClient: supabase,
})
```
All queries go through `eSupabase.from(tableName, schema)`:
```typescript filename="app/db.ts"
const { data, error } = await eSupabase
.from("users", users)
.select("id, email, name")
.eq("email", "alice@example.com")
```
Inserting data [#inserting-data]
The wrapper encrypts fields before insertion:
```typescript filename="app/actions.ts"
// Single insert
const { data, error } = await eSupabase
.from("users", users)
.insert({
email: "alice@example.com", // encrypted automatically
name: "Alice Smith", // encrypted automatically
age: 30, // encrypted automatically
role: "admin", // not in schema, passed through
})
.select("id")
// Bulk insert
const { data, error } = await eSupabase
.from("users", users)
.insert([
{ email: "alice@example.com", name: "Alice", age: 30, role: "admin" },
{ email: "bob@example.com", name: "Bob", age: 25, role: "user" },
])
.select("id")
```
Selecting data [#selecting-data]
The wrapper decrypts results automatically:
```typescript filename="app/queries.ts"
// List query — returns decrypted array
const { data, error } = await eSupabase
.from("users", users)
.select("id, email, name, role")
// data: [{ id: 1, email: "alice@example.com", name: "Alice Smith", role: "admin" }]
// Single result
const { data, error } = await eSupabase
.from("users", users)
.select("id, email, name")
.eq("id", 1)
.single()
// data: { id: 1, email: "alice@example.com", name: "Alice Smith" }
// Maybe single (returns null if no match)
const { data, error } = await eSupabase
.from("users", users)
.select("id, email")
.eq("email", "nobody@example.com")
.maybeSingle()
// data: null
```
You must list columns explicitly in `select()` — using `select('*')` will throw an error. The wrapper automatically adds `::jsonb` casts to encrypted columns so PostgreSQL parses them correctly.
`select()` also accepts an optional second parameter: `select(columns, { head?: boolean, count?: 'exact' | 'planned' | 'estimated' })`.
Query filters [#query-filters]
All filter values for encrypted columns are automatically encrypted before the query executes. Multiple filters are batch-encrypted in a single ZeroKMS call for efficiency.
Equality filters [#equality-filters]
```typescript filename="app/queries.ts"
// Exact match (requires .equality() on column)
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.eq("email", "alice@example.com")
// Not equal
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.neq("email", "alice@example.com")
// IN array (requires .equality())
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.in("name", ["Alice Smith", "Bob Jones"])
// NULL check (no encryption needed)
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.is("email", null)
```
Text search filters [#text-search-filters]
```typescript filename="app/queries.ts"
// LIKE — case sensitive (requires .freeTextSearch())
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.like("name", "%alice%")
// ILIKE — case insensitive (requires .freeTextSearch())
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.ilike("email", "%example.com%")
```
Range and comparison filters [#range-and-comparison-filters]
```typescript filename="app/queries.ts"
// Greater than (requires .orderAndRange())
const { data } = await eSupabase
.from("users", users)
.select("id, name, age")
.gt("age", 21)
// Greater than or equal
const { data } = await eSupabase
.from("users", users)
.select("id, name, age")
.gte("age", 18)
// Less than
const { data } = await eSupabase
.from("users", users)
.select("id, name, age")
.lt("age", 65)
// Less than or equal
const { data } = await eSupabase
.from("users", users)
.select("id, name, age")
.lte("age", 100)
```
Match (multi-column equality) [#match-multi-column-equality]
```typescript filename="app/queries.ts"
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.match({ email: "alice@example.com", name: "Alice Smith" })
```
OR conditions [#or-conditions]
```typescript filename="app/queries.ts"
// String format
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.or("email.eq.alice@example.com,email.eq.bob@example.com")
// Structured format (more type-safe)
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.or([
{ column: "email", op: "eq", value: "alice@example.com" },
{ column: "email", op: "eq", value: "bob@example.com" },
])
```
Both forms encrypt values for encrypted columns automatically.
NOT filter [#not-filter]
```typescript filename="app/queries.ts"
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.not("email", "eq", "alice@example.com")
```
Raw filter [#raw-filter]
```typescript filename="app/queries.ts"
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.filter("email", "eq", "alice@example.com")
```
Combining encrypted and non-encrypted filters [#combining-encrypted-and-non-encrypted-filters]
```typescript filename="app/queries.ts"
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.eq("email", "alice@example.com") // encrypted
.eq("role", "admin") // passed through as-is
```
Updating and deleting [#updating-and-deleting]
```typescript filename="app/actions.ts"
// Update
const { data } = await eSupabase
.from("users", users)
.update({ name: "Alice Johnson" }) // encrypted automatically
.eq("id", 1)
.select("id, name")
// Upsert
const { data } = await eSupabase
.from("users", users)
.upsert(
{ id: 1, email: "alice@example.com", name: "Alice", role: "admin" },
{ onConflict: "id" },
)
.select("id, email, name")
// Delete
const { error } = await eSupabase
.from("users", users)
.delete()
.eq("id", 1)
```
Transforms [#transforms]
These are passed through to Supabase directly:
```typescript filename="app/queries.ts"
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.eq("name", "Alice")
.order("name", { ascending: true })
.limit(10)
.range(0, 9)
```
The following transform methods are also supported:
```typescript filename="app/queries.ts"
.csv()
.abortSignal(signal)
.throwOnError()
.returns()
```
Lock context and audit [#lock-context-and-audit]
Chain `.withLockContext()` to tie encryption to a specific user's JWT:
```typescript filename="app/actions.ts"
import { LockContext } from "@cipherstash/stack/identity"
const lc = new LockContext()
const identified = await lc.identify(userJwt)
if (identified.failure) throw new Error(identified.failure.message)
const lockContext = identified.data
const { data } = await eSupabase
.from("users", users)
.insert({ email: "alice@example.com", name: "Alice" })
.withLockContext(lockContext)
.select("id")
```
Lock contexts work with all operations (insert, select, update, delete):
```typescript filename="app/queries.ts"
const { data } = await eSupabase
.from("users", users)
.select("id, email, name")
.eq("email", "alice@example.com")
.withLockContext(lockContext)
.audit({ metadata: { action: "user-lookup", requestId: "abc-123" } })
```
Error handling [#error-handling]
Encryption errors are surfaced with an additional `encryptionError` field:
```typescript filename="app/queries.ts"
const { data, error } = await eSupabase
.from("users", users)
.select("id, email")
if (error) {
if (error.encryptionError) {
console.error("Encryption error:", error.encryptionError)
}
}
```
Response type [#response-type]
```typescript filename="types.ts"
type EncryptedSupabaseResponse = {
data: T | null // Decrypted rows
error: EncryptedSupabaseError | null
count: number | null
status: number
statusText: string
}
```
Errors can come from Supabase (API errors) or from encryption operations. Check `error.encryptionError` for encryption-specific failures.
The full `EncryptedSupabaseError` type:
```typescript filename="types.ts"
type EncryptedSupabaseError = {
message: string
details?: string // Supabase error details
hint?: string // Supabase error hint
code?: string // Supabase/PostgreSQL error code
encryptionError?: EncryptionError // CipherStash encryption-specific error
}
```
Filter to index mapping [#filter-to-index-mapping]
| Filter Method | Required Index | Query Type |
| ------------------------ | ------------------- | ---------------------------------- |
| `eq`, `neq`, `in` | `.equality()` | `'equality'` |
| `like`, `ilike` | `.freeTextSearch()` | `'freeTextSearch'` |
| `gt`, `gte`, `lt`, `lte` | `.orderAndRange()` | `'orderAndRange'` |
| `is` | None | No encryption (NULL/boolean check) |
Exported types [#exported-types]
`@cipherstash/stack/supabase` also exports the following types:
* `EncryptedSupabaseConfig`
* `EncryptedSupabaseInstance`
* `EncryptedQueryBuilder`
* `PendingOrCondition`
* `SupabaseClientLike`
Exposing EQL schema for Supabase [#exposing-eql-schema-for-supabase]
If you installed EQL with `npx stash db install --supabase`, the role grants are already applied. You still need to expose the schema in the Supabase dashboard:
Go to [API settings](https://supabase.com/dashboard/project/_/settings/api) and add `eql_v2` to **Exposed schemas**.
If you installed EQL manually (without `--supabase`), you also need to grant permissions. See [CipherStash CLI — Supabase install](/stack/cipherstash/cli/install#supabase-install) for the required grants.
How it works [#how-it-works]
`encryptedSupabase` uses a deferred query builder pattern. All chained operations (`.select()`, `.eq()`, `.insert()`) are recorded synchronously. When you `await` the query, the builder:
1. **Encrypts mutation data** — calls `encryptModel` / `bulkEncryptModels` and converts to PG composites
2. **Adds `::jsonb` casts** — parses your select string and adds `::jsonb` to encrypted columns
3. **Batch-encrypts filter values** — collects all filter values for encrypted columns and encrypts them in a single `encryptQuery()` call (one round-trip to ZeroKMS)
4. **Executes the real Supabase query** — chains all operations on the underlying Supabase client
5. **Decrypts results** — calls `decryptModel` / `bulkDecryptModels` on the returned data
Complete example [#complete-example]
```typescript filename="app/example.ts"
import { createClient } from "@supabase/supabase-js"
import { Encryption } from "@cipherstash/stack"
import { encryptedSupabase } from "@cipherstash/stack/supabase"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
// Schema
const users = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch(),
name: encryptedColumn("name").equality().freeTextSearch(),
age: encryptedColumn("age").dataType("number").equality().orderAndRange(),
})
// Clients
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!)
const encryptionClient = await Encryption({ schemas: [users] })
const eSupabase = encryptedSupabase({ encryptionClient, supabaseClient: supabase })
// Insert
await eSupabase
.from("users", users)
.insert([
{ email: "alice@example.com", name: "Alice", age: 30 },
{ email: "bob@example.com", name: "Bob", age: 25 },
])
// Query with multiple filters
const { data } = await eSupabase
.from("users", users)
.select("id, email, name, age")
.gte("age", 18)
.lte("age", 35)
.ilike("name", "%ali%")
// data is fully decrypted:
// [{ id: 1, email: "alice@example.com", name: "Alice", age: 30 }]
```
# Access keys
Access keys are used to authenticate programmatic access to CipherStash [CTS](/stack/cipherstash/kms/cts) and [ZeroKMS](/stack/cipherstash/kms).
Access keys are primarily needed for **production and CI/CD** environments.
For local development, device-based authentication handles access automatically — no access keys required.
See [Getting started](/stack/quickstart) to set up device auth.
Creating an access key [#creating-an-access-key]
In the [CipherStash Dashboard](https://dashboard.cipherstash.com), you can create an access key by clicking the **Create access key** button in the [Access Keys](https://dashboard.cipherstash.com/workspaces/_/access-keys) section for your workspace.
Roles [#roles]
Each access key is assigned a role that determines its permissions.
Use the role with the *least required privileges* and avoid higher privileges unless absolutely necessary.
Member [#member]
The member role is used for authenticating client keys for cryptographic operations.
These are the scopes that are available to the member role:
```text
keyset:list
data_key:generate
data_key:retrieve
```
Control [#control]
The control role is used for workspace automation tasks.
It has access to the CipherStash API endpoints for creating, listing, enabling, disabling, granting, modifying, and revoking keysets and client keys.
These are the scopes that are available to the control role:
```text
keyset:create
keyset:list
keyset:enable
keyset:disable
keyset:grant
keyset:modify
keyset:revoke
client:list
```
Admin [#admin]
In production environments, it is recommended to never use the admin role.
Use the member role for authenticating client keys, and the control role for workspace automation tasks.
The admin role is "god" mode.
It has access to all the CipherStash API endpoints and can authenticate client keys for cryptographic operations.
These are the scopes that are available to the admin role:
```text
keyset:create
keyset:list
keyset:enable
keyset:disable
keyset:grant
keyset:modify
keyset:revoke
data_key:generate
data_key:retrieve
client:create
client:list
client:delete
access_key:create
access_key:list
access_key:delete
```
# Client keys
A client key is the cryptographic credential assigned to an instance of [CipherStash Proxy](/stack/cipherstash/proxy) or an application using an SDK like the [Encryption SDK](/stack/cipherstash/encryption). Each client key gets a unique ID and key pair, so you can revoke its access at any time.
Types of client keys [#types-of-client-keys]
CipherStash has two types of client keys:
Device-backed client keys [#device-backed-client-keys]
Device-backed client keys are created automatically when a developer runs `npx stash init`. They are tied to a specific developer's user account and device.
* Created automatically during initialization — no manual setup required
* Each developer gets their own unique client key
* Used for **local development only**
* Access can be revoked per developer without affecting other team members
Application client keys [#application-client-keys]
Application client keys are created manually in the Dashboard for production, CI/CD, and other non-interactive environments.
* No device attached — identified solely by `CS_CLIENT_ID` and `CS_CLIENT_KEY`
* Used for **production and CI/CD** where there is no interactive login
* Credentials are set via environment variables
See [Going to production](/stack/deploy/going-to-production) for a guide to creating application client keys.
Client keys and keysets [#client-keys-and-keysets]
When created, each client key is associated with a [keyset](/stack/cipherstash/kms/keysets). This allows the client key to perform encryption and decryption operations within the keyset.
Granting access to additional keysets [#granting-access-to-additional-keysets]
When creating a new client key, you can associate it with one or more keysets to grant access to the encrypted data in those keysets.
Revoking access to a client key [#revoking-access-to-a-client-key]
At any time, you can revoke access to a client key by removing the association with the keysets. This prohibits that client key from accessing the encrypted data in those keysets.
Creating a client key [#creating-a-client-key]
Automatic (local development) [#automatic-local-development]
Run `npx stash init` to create a device-backed client key automatically. See [Getting started](/stack/quickstart).
Manual (production) [#manual-production]
In the [CipherStash Dashboard](https://dashboard.cipherstash.com), you can create an application client key by clicking the **Create a new client** button in the [Clients](https://dashboard.cipherstash.com/workspaces/_/clients) page.
# Configuration
KMS configuration [#kms-configuration]
Local development [#local-development]
For local development, ZeroKMS credentials are handled automatically by device-based authentication.
Run `npx stash init` to set up your device — see [Getting started](/stack/quickstart).
Production credentials [#production-credentials]
In production and CI/CD environments, ZeroKMS is configured through the same credentials used by the Encryption SDK. The `CS_WORKSPACE_CRN` identifies your workspace in CRN format (e.g., `crn:ap-southeast-2.aws:your-workspace-id`).
For the full configuration reference (environment variables, programmatic config, and logging), see [Encryption SDK configuration](/stack/cipherstash/encryption/configuration).
The following credentials are required for production:
* **Workspace CRN:** identifies your workspace and region
* **Client ID:** identifies your application client key
* **Client key:** your half of the [dual-party key split](/stack/reference/security-architecture#key-hierarchy)
* **Access key:** API key for authenticating with CipherStash
See [Going to production](/stack/deploy/going-to-production) for a step-by-step guide to generating these credentials.
Keysets [#keysets]
To use a specific keyset for multi-tenant isolation, pass the `keyset` option:
```typescript filename="keyset-config.ts"
const client = await Encryption({
schemas: [users],
config: {
keyset: { name: "tenant-a" },
},
})
```
See [Keysets](/stack/cipherstash/kms/keysets) for more details on multi-tenant key isolation.
# CipherStash Token Service (CTS)
CipherStash Token Service (CTS) is an authentication and identity federation service.
CTS issues tokens that grant client keys access to [ZeroKMS](/stack/cipherstash/kms).
That access is temporary, limited, and can be granularly scoped.
If you are familiar with the [AWS Security Token Service](https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html) (AWS STS), CTS fulfills a similar role to that.
How it works [#how-it-works]
At a high level:
* Client keys authenticate to CTS
* CTS issues temporary tokens to those authenticated client keys
* Those temporary tokens can be used to make requests to ZeroKMS
A client key can be held by either [CipherStash Proxy](/stack/cipherstash/proxy) or an application using the [Encryption SDK](/stack/cipherstash/encryption).
Tokens [#tokens]
Client keys can authenticate to CTS via either:
* [Federating with an IDP](#federation)
* [Access keys](#access-keys)
Temporary tokens are valid for a maximum of 15 minutes.
Federation [#federation]
Federation allows you to use an existing source of identities to authenticate to CTS, and onwards to ZeroKMS.
This allows you to rapidly grant and revoke people's access based on your product or organisation's onboarding and offboarding processes.
By default, CTS federates with CipherStash Cloud's IDP.
Bringing your own Identity Provider (IDP) [#bringing-your-own-identity-provider-idp]
If you want to bring your own [IDP](/stack/reference/glossary#idp-identity-provider) to CTS, you can configure your workspace to use [Auth0](https://auth0.com/), [Okta](https://okta.com/), [Clerk](https://clerk.com/), or [Supabase](https://supabase.com/).
You can add and manage OIDC providers from your workspace settings in the [CipherStash Dashboard](https://dashboard.cipherstash.com).
Access keys [#access-keys]
[Access keys](/stack/cipherstash/kms/access-keys) are a persistent credential you can use for application-to-service access to CTS.
You can create access keys in the [CipherStash Dashboard](https://dashboard.cipherstash.com).
# Disaster recovery
ZeroKMS is designed to protect your encrypted data with robust disaster recovery capabilities. Your encrypted data remains recoverable even in the event of a complete service disruption.
Separation of keys and data [#separation-of-keys-and-data]
ZeroKMS uses a fundamental architectural principle: **your encrypted data stays in your database**. We only manage the key material needed to decrypt that data. This separation means:
* Your encrypted data is never stored in CipherStash infrastructure
* A CipherStash outage affects key access, not your data
* Recovery doesn't require migrating or restoring potentially terabytes of encrypted data
Key recovery process [#key-recovery-process]
ZeroKMS uses a hierarchical key structure that enables you to regenerate data keys on-demand without storing every individual key. This means:
* Key material is cryptographically reproducible
* No database of individual data keys needs to be restored
* Recovery time is measured in hours, not days
Zero data loss [#zero-data-loss]
In a disaster recovery scenario, no data loss occurs. All encrypted data remains intact and fully recoverable. The key material used to generate your data keys is preserved and can be regenerated.
Fast recovery in case of regional failure [#fast-recovery-in-case-of-regional-failure]
In the event of a complete regional failure, CipherStash can restore ZeroKMS service within a few hours. During this time:
* Your encrypted data remains safely stored in your database
* Applications cannot decrypt existing data or encrypt new data
* Once service is restored, all operations resume normally
What happens during a ZeroKMS outage? [#what-happens-during-a-zerokms-outage]
Data access during an outage [#data-access-during-an-outage]
**During a CipherStash outage, can we access our encrypted data?**
No. While ZeroKMS is unavailable, your applications cannot perform cryptographic operations (encrypt or decrypt). This is an intentional security design: cryptographic operations require active key management infrastructure.
**Is our encrypted data at risk?**
No. Your encrypted data remains safely stored in your own database. A CipherStash outage does not expose, corrupt, or delete your data.
After recovery [#after-recovery]
Once service is restored to a new region or infrastructure:
1. Your applications reconnect to the restored ZeroKMS endpoint
2. Key material is cryptographically regenerated
3. All encrypted data becomes accessible again
4. No data migration or reindexing is required
Architecture benefits [#architecture-benefits]
Compared to traditional approaches [#compared-to-traditional-approaches]
Many encryption and data protection solutions require storing both encrypted data and keys together in a "vault." During disaster recovery, these solutions must restore the entire vault, potentially terabytes of data, taking hours or days.
**ZeroKMS is different:**
* Only lightweight key material (megabytes) needs to be restored
* Your encrypted data never moves. It stays in your database.
* Recovery is based on cryptographic regeneration, not data restoration
Security during recovery [#security-during-recovery]
* Encrypted data in your database remains protected
* Key material is restored through cryptographically secure processes
* Access controls and audit logging resume immediately when service is restored
* No temporary "recovery windows" that might expose data
Questions? [#questions]
If you have specific disaster recovery requirements or need to discuss your resilience planning, please contact [support@cipherstash.com](mailto:support@cipherstash.com).
# ZeroKMS
ZeroKMS is the key management layer that powers [Encryption](/stack/cipherstash/encryption) and Secrets (coming soon). Every encrypted value gets its own unique key. Keys derived on demand, never stored. Identity and policy baked into every key.
100x faster than AWS KMS.
Zero-knowledge by design [#zero-knowledge-by-design]
Existing key management solutions reveal either data or keys to intermediaries. ZeroKMS uses proxy symmetric re-encryption (patent pending). Key seeds are returned to the application. The application creates data keys locally. Data keys are never seen by third parties. Never sent across the network.
How it works [#how-it-works]
* **Unique key per value:** Each encrypted field uses a distinct data encryption key, not a shared table-level key.
* **AWS KMS backed:** Root keys are stored in AWS KMS. ZeroKMS handles key derivation and wrapping.
* **Zero-knowledge:** CipherStash never sees your plaintext data or unwrapped keys. When a data key is requested, ZeroKMS generates and returns key seeds to the application to create the data key locally. Data keys are never seen by third parties and are never sent across the network.
* **Multi-tenant isolation:** Use keysets to isolate encryption keys per tenant, customer, or business unit.
* **Bulk operations:** ZeroKMS supports bulk encryption and decryption operations, enabling a unique data key per record without sacrificing performance.
* **Multi-region:** ZeroKMS is highly available and deployed in [multiple cloud regions globally](/stack/cipherstash/kms/regions). It can also be deployed within your own cloud account or on-prem.
Key Sets [#key-sets]
[Key Sets](/stack/cipherstash/kms/keysets) are ZeroKMS's core primitive for cryptographic isolation. A keyset is the unit of isolation. Data encrypted under one keyset cannot be decrypted with another.
Keysets are managed in the CipherStash Dashboard as a cloud primitive. How you use them is up to your architecture:
* **Tenant isolation**: one keyset per customer or business unit, giving per-tenant cryptographic boundaries with zero key management overhead. See [Encryption configuration](/stack/cipherstash/encryption/configuration#keysets).
* **Environment isolation**: separate keysets for production, staging, and development. Secrets (coming soon) maps its `environment` parameter to a keyset automatically.
* **Regional or compliance boundaries**: isolate data by jurisdiction or regulatory requirement.
* **Any boundary your application needs**: keysets are general-purpose. Combine them however your architecture requires.
Read the whitepaper [#read-the-whitepaper]
If you'd like to learn more about ZeroKMS, read the whitepaper on the [Trust Center](https://trust.cipherstash.com/resources).
Next steps [#next-steps]
# Keysets
Keysets are used, in combination with ZeroKMS key material, to derive unique data keys per record.
Each keyset maintains its own set of data encryption keys, so data encrypted under one keyset cannot be decrypted with another.
Key Sets are the foundational primitive that powers [multi-tenant encryption](/stack/cipherstash/encryption/configuration#keysets) in the Encryption SDK and environment isolation in Secrets (coming soon).
Default keyset [#default-keyset]
Each workspace has a default keyset, which is used to derive data keys for all records in the workspace and meets the needs for most use cases.
The default keyset is created automatically when you create a workspace.
You can create additional keysets to meet your specific needs.
When a developer runs `npx stash init`, their device-backed client key is automatically granted access to the default keyset. This means no additional configuration is needed for local development.
Create a client key with a keyset [#create-a-client-key-with-a-keyset]
By ID [#by-id]
```typescript
import { Encryption } from "@cipherstash/stack"
import { users } from "./schema"
const client = await Encryption({
schemas: [users],
config: {
keyset: { id: "123e4567-e89b-12d3-a456-426614174000" },
},
})
```
By name [#by-name]
```typescript
const client = await Encryption({
schemas: [users],
config: {
keyset: { name: "Company A" },
},
})
```
In Secrets (coming soon) [#in-secrets-coming-soon]
Secrets environments map directly to keysets. When you specify an `environment`, the Secrets SDK automatically uses the corresponding keyset for encryption and decryption.
How it works [#how-it-works]
When you specify a keyset, ZeroKMS derives all data encryption keys from that keyset's root key.
This means:
* Data encrypted under keyset A cannot be decrypted with keyset B.
* You can rotate or revoke keys at the keyset level.
* Each tenant's data is cryptographically isolated from every other tenant.
Use cases [#use-cases]
* **Multi-tenant SaaS:** Give each customer their own keyset so their data is cryptographically separated. See [Encryption configuration](/stack/cipherstash/encryption/configuration#keysets).
* **Environment isolation:** Use different keysets for production, staging, and development. Secrets (coming soon) will manage these mappings automatically.
* **Compliance boundaries:** Isolate data by jurisdiction or regulatory requirement.
* **Custom isolation boundaries:** Key Sets are a general-purpose primitive. Use them for any cryptographic boundary your application needs: per-region, per-department, or per-feature.
Keysets and client keys [#keysets-and-client-keys]
Keysets can be associated with any number of client keys to grant each client key access to encryption and decryption operations on data in that keyset.
Granting access to a client key [#granting-access-to-a-client-key]
When creating a new keyset, you must choose which client keys will be granted access to the keyset.
You can manage this by clicking **Manage** on a particular keyset in the [Keysets](https://dashboard.cipherstash.com/workspaces/_/keysets) page of the [CipherStash Dashboard](https://dashboard.cipherstash.com).
Revoking access to a keyset [#revoking-access-to-a-keyset]
At any time, you can revoke access to a client key by removing the association with the keysets. This prohibits that client key from accessing the encrypted data in those keysets.
You can manage this by clicking **Manage** on a particular client key in the [Clients](https://dashboard.cipherstash.com/workspaces/_/clients) page of the [CipherStash Dashboard](https://dashboard.cipherstash.com).
Managing keysets [#managing-keysets]
Via the Dashboard [#via-the-dashboard]
In the [CipherStash Dashboard](https://dashboard.cipherstash.com), you can create a keyset by clicking the **Create Keyset** button in the [Keysets](https://dashboard.cipherstash.com/workspaces/_/keysets) page.
Via the API [#via-the-api]
To create a keyset via the API, follow these steps:
Step 1: Create an access key [#step-1-create-an-access-key]
First, create an access key in the [CipherStash Dashboard](https://dashboard.cipherstash.com) with the role of "Control".
Ensure you are using the access key with the "Control" role for workspace automation tasks.
These access keys only have access to the CipherStash API CRUD operations and are not able to authenticate cryptographic operations.
Step 2: Get a service token [#step-2-get-a-service-token]
Use your access key to get a service token:
```bash
curl https://ap-southeast-2.aws.auth.viturhosted.net/api/authorize \
-H "Content-Type: application/json" \
-d '{"accessKey": "your_access_key_set_with_control_role"}'
```
The response contains the access token and expiry information:
```json
{
"accessToken": "token_value",
"expiry": 1757529498
}
```
Step 3: Export the token [#step-3-export-the-token]
Export the access token as an environment variable:
```bash
export CTS_TOKEN="the_accessToken_from_previous_step"
```
Step 4: Create the keyset [#step-4-create-the-keyset]
Create a keyset using the service token (make sure your workspace is in ap-southeast-2 or update your endpoint):
```bash
curl https://ap-southeast-2.aws.viturhosted.net/create-keyset \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $CTS_TOKEN" \
-d '{"name":"my_automated_keyset","description":"automated"}' -v
```
The response contains the ID of the created keyset:
```json
{
"id": "key_set_id",
"name": "my_key_set_name",
"description": "my_key_set_description"
}
```
Step 5: Grant access to a client key [#step-5-grant-access-to-a-client-key]
Grant access to a client key by granting access to the keyset.
You can find your client ID in the [CipherStash Dashboard](https://dashboard.cipherstash.com) on the [Clients](https://dashboard.cipherstash.com/workspaces/_/clients) page for your workspace.
```bash
curl https://ap-southeast-2.aws.viturhosted.net/grant-keyset \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $CTS_TOKEN" \
-d '{"keyset_id":"key_set_id","client_id":"client_id_to_grant_access_to"}' -v
```
Returns HTTP 200 on success.
# OIDC Providers
OIDC Providers let you federate your existing identity provider (IdP) with CipherStash.
Once registered, end users can authenticate cryptographic operations using the identity tokens your IdP issues, not just application credentials.
This pairs with [identity-aware encryption](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext) via `LockContext`, which binds encryption operations to a specific user identity.
If you are only encrypting data at rest with application credentials, you do not need an OIDC provider.
This feature is for workspaces where end-user identity should be bound to encryption operations.
Why this matters [#why-this-matters]
Without an OIDC provider, encryption operations are authenticated by [access keys](/stack/cipherstash/kms/access-keys) (long-lived, application-scoped) or PKCE device login (developer laptops).
With one, the identity token from your IdP can be bound to encryption and decryption operations — useful for per-user keys, audit trails, and identity-aware data access controls.
Supported providers [#supported-providers]
CipherStash supports the following identity providers:
* **Auth0**
* **Okta**
* **Clerk**
* **Supabase** — register from the dashboard setup hub or manually with the project issuer URL
Registering a provider [#registering-a-provider]
Go to your workspace in the [CipherStash Dashboard](https://dashboard.cipherstash.com/workspaces/_/oidc-providers) → **Authentication** → **OIDC Providers** → **Add OIDC Provider**.
Select your vendor and enter the **Issuer URL** — this is the OIDC discovery endpoint that issues identity tokens for your application. The URL format depends on your provider:
| Provider | Example issuer URL |
| -------- | ------------------------------------------ |
| Auth0 | `https://your-tenant.auth0.com/` |
| Okta | `https://your-org.okta.com/` |
| Clerk | `https://your-app.clerk.accounts.dev/` |
| Supabase | `https://{projectRef}.supabase.co/auth/v1` |
Submit the form. The provider is now registered for that workspace.
Supabase (from the dashboard) [#supabase-from-the-dashboard]
If you [connected Supabase in the dashboard](/stack/reference/dashboard-supabase-integration), open **Settings → Integrations → Supabase**, select your project, and click **Configure OIDC for this project**.
The dashboard registers vendor `supabase` with issuer `https://{projectRef}.supabase.co/auth/v1` for the selected project ref.
What gets stored [#what-gets-stored]
Each registered OIDC provider has three fields:
* **ID** — assigned by CipherStash on registration
* **Vendor** — one of `auth0`, `okta`, `clerk`, or `supabase`
* **Issuer** — the URL you provided
No client secret or shared key is stored. CipherStash validates tokens by fetching the JWKS from the issuer's discovery endpoint.
Using it from code [#using-it-from-code]
Once a provider is registered, use `LockContext` in the Encryption SDK to bind encryption operations to a user's identity token.
See the [`LockContext` API reference](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext) for the full API.
# Regions
Each workspace is tied to a specific region and is deployed in that region's [ZeroKMS instance](/stack/cipherstash/kms).
Supported ZeroKMS regions [#supported-zerokms-regions]
* Asia Pacific (Sydney)
* Europe (Frankfurt)
* Europe (Ireland)
* US East (N. Virginia)
* US East (Ohio)
* US West (N. California)
* US West (Oregon)
Requesting a new region [#requesting-a-new-region]
If you need a region that is not listed above, please contact us at [support@cipherstash.com](mailto:support@cipherstash.com).
# Audit features
CipherStash Proxy provides comprehensive data access auditing for PostgreSQL. These features work transparently. They require no changes to your SQL or application code.
Statement fingerprinting [#statement-fingerprinting]
Statement fingerprints identify unique SQL statements by examining the raw parse tree using [pg\_query](https://pganalyze.com/blog/pg-query-2-0-postgres-query-parser#fingerprints-in-pg_query-a-better-way-to-check-if-two-queries-are-identical). Fingerprints ignore query differences when they result in the same query intent. They are unique across environments and time, providing a useful mechanism for identifying query patterns.
**Example**
| SQL | Fingerprint |
| -------------------- | ------------------ |
| `SELECT a, b FROM c` | `fb1f305bea85c2f6` |
| `SELECT b, a FROM c` | `fb1f305bea85c2f6` |
Both queries produce the same fingerprint because they access the same columns from the same table. Column order doesn't affect the intent.
Statement redaction [#statement-redaction]
The statement SQL is redacted before being included in an event payload. All static values in the SQL string are stripped. Table names, column names, and function names are retained.
If parsing fails or another issue prevents redaction, Proxy does not transmit the statement.
Most PostgreSQL libraries and frameworks default to using parameterized statements and the PostgreSQL Extended Protocol, in which case values will not be included in the SQL.
**Example**
| Statement SQL | Redacted SQL |
| ----------------------------------- | ------------------------------------------ |
| `SELECT a, b FROM c` | `SELECT a, b FROM c` |
| `SELECT a, b FROM c WHERE id = '1'` | `SELECT a, b FROM c WHERE id = {REDACTED}` |
Primary key injection [#primary-key-injection]
Primary key injection connects SQL statements to record identifiers. It transparently ensures that any data access event includes the actual record identifiers. No need to instrument or change your SQL.
CipherStash Proxy uses the database schema to identify SQL statements that do not reference a primary key. It injects the missing primary keys into the SQL before execution. Primary keys of accessed records can then be tracked with the data access event.
The performance impact on the database is negligible as the primary key is by definition indexed, and the referenced tables are already present in the SQL statement.
When combined with [identity-aware encryption](/stack/cipherstash/encryption/identity), the events are also linked to client identity, providing an end-to-end view of data access.
Record reconciliation [#record-reconciliation]
Record reconciliation extracts the record identifiers, maps them to the appropriate tables, and includes them in the data access event payload sent to Audit.
Injected primary keys are always removed from the SQL results before being returned to the client. The process is internal to CipherStash Proxy. The format of the result set always matches the original query.
How it works together [#how-it-works-together]
These four features form a pipeline:
1. **Fingerprint**: identify the query pattern
2. **Redact**: strip sensitive values from the SQL statement
3. **Inject**: add primary key references to track which records are accessed
4. **Reconcile**: extract record identifiers and remove injected keys from results
The result is a complete data access event containing:
* **What query** was executed (fingerprint + redacted SQL)
* **Which records** were accessed (reconciled primary keys)
* **Who** executed it (when combined with identity-aware encryption)
* **When** it happened (timestamp)
All of this happens transparently within the proxy. Your application receives unmodified query results.
# Configuration
Proxy configuration [#proxy-configuration]
CipherStash Proxy is available as a [container image](https://hub.docker.com/r/cipherstash/proxy) on Docker Hub. Deploy it locally, as a cloud sidecar, or as a standalone binary.
It speaks the PostgreSQL protocol (based on [pgcat](https://github.com/pgcat/pgcat)), meaning your application connects to Proxy exactly the same way it connects to PostgreSQL.
Installing Proxy [#installing-proxy]
The easiest way to start using CipherStash Proxy with your application is by adding a container to your application's `docker-compose.yml`:
```yaml
services:
app:
# Your app container config
db:
# Your Postgres container config
proxy:
image: cipherstash/proxy:latest
container_name: proxy
ports:
- 6432:6432
- 9930:9930
environment:
- CS_DATABASE__HOST=${CS_DATABASE__HOST}
- CS_DATABASE__PORT=${CS_DATABASE__PORT}
- CS_DATABASE__USERNAME=${CS_DATABASE__USERNAME}
- CS_DATABASE__PASSWORD=${CS_DATABASE__PASSWORD}
- CS_DATABASE__NAME=${CS_DATABASE__NAME}
- CS_WORKSPACE_CRN=${CS_WORKSPACE_CRN}
- CS_CLIENT_ACCESS_KEY=${CS_CLIENT_ACCESS_KEY}
- CS_DEFAULT_KEYSET_ID=${CS_DEFAULT_KEYSET_ID}
- CS_CLIENT_ID=${CS_CLIENT_ID}
- CS_CLIENT_KEY=${CS_CLIENT_KEY}
- CS_PROMETHEUS__ENABLED=${CS_PROMETHEUS__ENABLED:-true}
```
For a fully-working example, check out the [docker-compose.yml](https://github.com/cipherstash/proxy/blob/main/docker-compose.yml) in the Proxy repository.
Start the Proxy container:
```bash
docker compose up
```
Connect your PostgreSQL client to Proxy on TCP 6432.
Prometheus metrics are exposed on port 9930. Read more about them in the [reference documentation](/stack/reference/proxy-reference#prometheus-metrics).
Configuring Proxy [#configuring-proxy]
Unlike the Encryption SDK and Secrets SDK, CipherStash Proxy **always requires environment variables** because it runs as a separate process or container. It cannot use the host's device-based authentication.
For local development, create a client key and access key in the [Dashboard](https://dashboard.cipherstash.com) and set them in your `.env.proxy.docker` file.
For production, see [Going to production](/stack/deploy/going-to-production) to set up application client key credentials.
To run, CipherStash Proxy needs to know:
* What port to run on
* How to connect to the target PostgreSQL database
* Secrets to authenticate to CipherStash
There are two ways to configure Proxy:
* **Environment variables** that Proxy looks up on startup
* **TOML file** (`cipherstash-proxy.toml`) that Proxy reads on startup
Configuration loading order:
1. If `cipherstash-proxy.toml` is present in the current working directory, Proxy reads its config from that file
2. If `cipherstash-proxy.toml` is not present, Proxy looks up environment variables
3. If **both** are present, Proxy uses the TOML file as the base and overrides with any environment variables that are set
See the [Proxy config reference](/stack/reference/proxy-reference) for all available options.
Setting up the database schema [#setting-up-the-database-schema]
Proxy uses [EQL](/stack/reference/eql-guide) to index and search encrypted data.
When you start the Proxy container, you can install EQL by setting the `CS_DATABASE__INSTALL_EQL` environment variable:
```bash
CS_DATABASE__INSTALL_EQL=true
```
This installs the version of EQL bundled with the Proxy container.
Using `CS_DATABASE__INSTALL_EQL` is only recommended for development environments.
Install EQL by running [the installation script](https://github.com/cipherstash/encrypt-query-language/releases) as a database migration in production environments.
Check the installed EQL version:
```sql
SELECT eql_v2.version();
```
Creating encrypted columns [#creating-encrypted-columns]
When storing encrypted data in PostgreSQL with Proxy, you use the `eql_v2_encrypted` column type provided by EQL.
Create a table with an encrypted column:
```sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email eql_v2_encrypted
);
```
Adding encrypted indexes [#adding-encrypted-indexes]
Encrypted columns cannot be searched without an encrypted index. The indexes you define determine what kind of searches you can perform.
Add a `unique` index for exact match queries:
```sql
SELECT eql_v2.add_search_config(
'users',
'email',
'unique',
'text'
);
```
Add `match` and `ore` indexes for pattern matching and ordering:
```sql
SELECT eql_v2.add_search_config(
'users',
'email',
'match',
'text'
);
SELECT eql_v2.add_search_config(
'users',
'email',
'ore',
'text'
);
```
Adding, updating, or deleting encrypted indexes on columns that already contain encrypted data will not re-index that data. To use the new indexes, you must `SELECT` the data out of the column and `UPDATE` it again.
To learn about encrypted indexes for other data types (`text`, `int`, `boolean`, `date`, `jsonb`), see the [EQL documentation](https://github.com/cipherstash/encrypt-query-language/tree/main/docs).
Encrypting existing data [#encrypting-existing-data]
CipherStash Proxy includes an `encrypt` CLI tool to encrypt existing data, or to apply index changes after changes to the encryption configuration.
See the [CLI reference](/stack/reference/proxy-reference#cli-reference) for usage details.
Reference [#reference]
See the [CipherStash Proxy reference](/stack/reference/proxy-reference) for all available options.
# Encrypt tool
CipherStash Proxy includes an `encrypt` tool, a CLI application to encrypt existing plaintext data or apply index changes after updating the encryption configuration of a protected database.
Usage [#usage]
Encrypt data from a `source` column into a specified encrypted `target` column. The tool connects to CipherStash Proxy using the `cipherstash.toml` configuration or environment variables.
```bash
cipherstash-proxy encrypt [OPTIONS] --table --columns ...
```
How it works [#how-it-works]
The process for encrypting a column is:
1. Add a new encrypted destination column with the appropriate encryption configuration.
2. Use CipherStash Proxy to process the migration:
1. Select from the original plaintext column.
2. Update the encrypted column with the plaintext value.
3. Drop the original plaintext column.
4. Rename the encrypted column to the original plaintext column name.
The `encrypt` tool automates this process. Updates run in batches of 100 records (configurable via `--batch-size`). The process is idempotent and can be run repeatedly.
Configuration options [#configuration-options]
| Option | Description | Default |
| --------------------- | -------------------------------------------------------------- | -------- |
| `-t`, `--table` | Specifies the table to migrate | Required |
| `-c`, `--columns` | List of columns to migrate (space-delimited `key=value` pairs) | Required |
| `-k`, `--primary-key` | List of primary key columns (space-delimited) | `id` |
| `-b`, `--batch-size` | Number of records to process at once | `100` |
| `-d`, `--dry-run` | Loads data but does not perform updates | Optional |
| `-v`, `--verbose` | Turns on additional logging output | Optional |
| `-h`, `--help` | Displays help message | — |
Examples [#examples]
These examples assume a running CipherStash Proxy instance and a `users` table with:
* `id`: the primary key column
* `email`: the source plaintext column
* `encrypted_email`: the destination column configured for encrypted text
Encrypt a column [#encrypt-a-column]
```bash
cipherstash-proxy encrypt --table users --columns email=encrypted_email
```
Specify a primary key column [#specify-a-primary-key-column]
```bash
cipherstash-proxy encrypt --table users --columns email=encrypted_email --primary-key user_id
```
Compound primary key [#compound-primary-key]
```bash
cipherstash-proxy encrypt --table users --columns email=encrypted_email --primary-key user_id tenant_id
```
# Getting started with Proxy
Clone the repo [#clone-the-repo]
Start by cloning the [Proxy repo](https://github.com/cipherstash/proxy):
```bash
git clone https://github.com/cipherstash/proxy
cd proxy
```
Set up credentials [#set-up-credentials]
Complete the [Getting started](/stack/quickstart) guide first to initialize CipherStash on your device.
CipherStash Proxy runs inside a Docker container, so it cannot use the host's device-based authentication directly.
For local development, create a client key in the [CipherStash Dashboard](https://dashboard.cipherstash.com) and save the credentials to `.env.proxy.docker`:
```ini filename=".env.proxy.docker"
CS_WORKSPACE_CRN=
CS_CLIENT_ID=
CS_CLIENT_KEY=
CS_CLIENT_ACCESS_KEY=
```
You can find your workspace CRN in **Settings** for your workspace. Create a client key under **Clients** and an access key under **Access Keys**.
Do not commit `.env.proxy.docker` to version control. Add it to your `.gitignore`.
For production Proxy deployments, see [Going to production](/stack/deploy/going-to-production) to set up application client key credentials.
Start the containers [#start-the-containers]
```bash
docker compose up
```
This starts a PostgreSQL database on `localhost:5432` and CipherStash Proxy on `localhost:6432`.
The repo includes an example `users` table for inserting and querying encrypted data.
This example uses email, date of birth, and salary to represent sensitive data worth encrypting.
Insert and read some data [#insert-and-read-some-data]
Connect to the Proxy via `psql` and run some queries:
```bash
docker compose exec proxy psql postgres://cipherstash:3ncryp7@localhost:6432/cipherstash
```
This establishes an interactive session with the database, via CipherStash Proxy.
Insert and read some data via Proxy:
```sql
INSERT INTO users (encrypted_email, encrypted_dob, encrypted_salary)
VALUES ('alice@cipherstash.com', '1970-01-01', '100');
SELECT encrypted_email, encrypted_dob, encrypted_salary FROM users;
```
The `INSERT` inserts a record into the `users` table, and the `SELECT` reads the same record back.
Notice that it looks like nothing happened: the data in the `INSERT` was unencrypted, and the data in the `SELECT` is also unencrypted.
Now connect to the database directly via `psql` and see what the data actually looks like:
```bash
docker compose exec proxy psql postgres://cipherstash:3ncryp7@postgres:5432/cipherstash
```
Query the database directly:
```sql
SELECT encrypted_email, encrypted_dob, encrypted_salary FROM users;
```
The output is significantly larger because the `SELECT` returns the raw encrypted data.
The data is transparently encrypted and decrypted by Proxy.
Update data with a WHERE clause [#update-data-with-a-where-clause]
In your `psql` connection to Proxy, update the data and read it back:
```sql
UPDATE users SET encrypted_dob = '1978-02-01'
WHERE encrypted_email = 'alice@cipherstash.com';
SELECT encrypted_dob FROM users
WHERE encrypted_email = 'alice@cipherstash.com';
```
The `=` comparison operation in the `WHERE` clause is evaluated against **encrypted** data.
The `SELECT` returns `1978-02-01`.
Search encrypted data [#search-encrypted-data]
Insert more records via Proxy and search them:
```sql
INSERT INTO users (encrypted_email, encrypted_dob, encrypted_salary)
VALUES ('bob@cipherstash.com', '1991-03-06', '10');
INSERT INTO users (encrypted_email, encrypted_dob, encrypted_salary)
VALUES ('carol@cipherstash.com', '2005-12-30', '1000');
-- Range query on encrypted salary
SELECT encrypted_email, encrypted_dob, encrypted_salary
FROM users WHERE encrypted_salary <= 100;
-- Pattern match on encrypted email
SELECT encrypted_email, encrypted_dob, encrypted_salary
FROM users WHERE encrypted_email LIKE 'alice';
-- Range query on encrypted date
SELECT encrypted_email, encrypted_dob, encrypted_salary
FROM users WHERE encrypted_dob > '2000-01-01';
```
All comparison operations are evaluated against **encrypted** data. The literal values are transparently encrypted by Proxy before being compared in the database.
CipherStash Proxy provides:
* Completely transparent encryption of sensitive data in PostgreSQL
* All data remains searchable, protected with non-deterministic AES-256-GCM encryption
* Zero changes required to your application's database queries
# /PROXY
Transparent, searchable encryption for your existing PostgreSQL database. No application code changes. Connect through the proxy. Your data is encrypted at the field level. Your queries still work.
What you get [#what-you-get]
1. **Zero code changes.** Configure encryption for specific tables and columns. Your SQL stays the same.
2. **Queries over ciphertext.** Equality, comparison, ordering, grouping over encrypted values.
3. **Written in Rust.** High performance, strongly-typed mapping of SQL statements.
4. **Backed by ZeroKMS.** Up to 14x the performance of AWS KMS.
5. **Prometheus built in.** Production monitoring out of the box.
6. **Runs in a container** or as a standalone CLI tool.
CipherStash Proxy uses the [Encrypt Query Language (EQL)](/stack/reference/eql-guide) to index and search encrypted data.
When to use Proxy vs SDK [#when-to-use-proxy-vs-sdk]
| | CipherStash Proxy | Encryption SDK |
| ---------------- | ---------------------------------------------------------- | ----------------------------------------------------- |
| **Best for** | DevOps teams adding encryption to existing PostgreSQL apps | Engineering teams building new applications |
| **Code changes** | Zero. Drop-in replacement for your database connection. | Application-level integration with schema definitions |
| **Setup** | Docker container, configure env vars | npm install, define schemas, integrate into app |
| **Control** | Automatic, table/column configuration | Fine-grained, per-field control |
Next steps [#next-steps]
# Message flow
This page explains the internal message handling flow for advanced users debugging unmappable statements or unexpected proxy behaviour. CipherStash Proxy sits between your application and PostgreSQL and intercepts the PostgreSQL extended query protocol. It encrypts parameters before they reach the database and decrypts values before they reach your application.
Extended query protocol overview [#extended-query-protocol-overview]
The PostgreSQL extended query protocol uses a sequence of messages to execute parameterised queries. The two most relevant messages for encryption are:
* **Parse**: the client sends a SQL statement with parameter placeholders (`$1`, `$2`, ...). Proxy inspects the statement and maps column references to their encryption config.
* **Bind**: the client sends parameter values to bind to a prepared statement. If Proxy recognised the statement during Parse, it encrypts the parameters here before forwarding them.
Parse flow [#parse-flow]
When Proxy receives a Parse message, it determines whether the SQL statement references encrypted columns.
1. Proxy checks whether the statement is encryptable (i.e., it references at least one column with an active encryption config).
2. If it is encryptable, Proxy maps the column references to their encryption configuration.
3. If the statement includes literal parameter values, Proxy rewrites them as encrypted values immediately.
4. Proxy adds the statement and its column config to the connection context for use during Bind.
5. Proxy forwards the (possibly rewritten) Parse message to PostgreSQL.
If the statement is not encryptable (no encrypted columns referenced), Proxy forwards it unchanged.
Bind flow [#bind-flow]
When Proxy receives a Bind message, it looks up the corresponding statement in the connection context.
1. Proxy checks whether the statement that this Bind message references is in the context (i.e., was processed during Parse).
2. If it is, Proxy encrypts each parameter value according to the column config mapped during Parse.
3. Proxy rewrites the parameter values in the Bind message with the encrypted payloads.
4. Proxy creates a portal for the bound statement and adds it to the context.
5. Proxy forwards the rewritten Bind message to PostgreSQL.
If the statement is not in context, Proxy creates a portal without encryption and forwards it unchanged.
Pipelining [#pipelining]
PostgreSQL supports pipelining: the client can send multiple messages without waiting for responses. Proxy must track Describe and Execute messages to correlate server responses with the right statements or portals, since responses arrive in order but may interleave.
```
Sequential Pipelined
| Client | Server | | Client | Server |
|----------------|-----------------| |----------------|-----------------|
| send query 1 | | | send query 1 | |
| | process query 1 | | send query 2 | process query 1 |
| receive rows 1 | | | send query 3 | process query 2 |
| send query 2 | | | receive rows 1 | process query 3 |
| | process query 2 | | receive rows 2 | |
| receive rows 2 | | | receive rows 3 | |
```
The PostgreSQL server always processes queries in sequential order, even when pipelined. Proxy preserves this ordering when encrypting parameters and decrypting results.
Unmappable statements [#unmappable-statements]
Some statements cannot be mapped to an encryption config. This happens when:
* The statement uses a function or expression that obscures the column reference (e.g., `CAST(email AS text)`)
* The column is referenced through a view, subquery, or CTE that Proxy cannot resolve
* The statement uses a SQL feature Proxy does not yet parse (e.g., certain `COPY` forms)
When a statement is unmappable, Proxy forwards it to PostgreSQL unmodified. No encryption or decryption occurs. The `cipherstash_proxy_statements_unmappable_total` Prometheus metric tracks how often this happens. Enable `CS_LOG__MAPPER_LEVEL=debug` to see which statements are unmappable and why.
Related reference [#related-reference]
* [Proxy configuration reference](/stack/reference/proxy-reference)
* [Proxy errors](/stack/reference/proxy-errors)
* [Prometheus metrics](/stack/reference/proxy-reference#prometheus-metrics)
# Multitenant operation
import { Callout } from "fumadocs-ui/components/callout";
Multitenant operation [#multitenant-operation]
CipherStash Proxy supports multitenant applications using ZeroKMS keysets to provide strong cryptographic separation between tenants.
In multitenant operation, each tenant is associated with a keyset. Data is protected by separate encryption keys. You can scope a proxy connection to a specific keyset at runtime using the `SET CIPHERSTASH.KEYSET` SQL commands:
* `SET CIPHERSTASH.KEYSET_ID`
* `SET CIPHERSTASH.KEYSET_NAME`
Once a keyset is set for a connection, all subsequent operations are scoped to that keyset. Data can only be decrypted by the same keyset that encrypted it.
A keyset `name` is unique to a workspace and functions as an alias. Using a keyset name lets you associate a keyset with an arbitrary identifier such as an internal `TenantId`.
The proxy must be configured without a `DEFAULT_KEYSET_ID` to enable multitenant operation and use of the `SET KEYSET` commands.
Keyset commands [#keyset-commands]
SET CIPHERSTASH.KEYSET_ID [#set-cipherstashkeyset_id]
Sets the active keyset for the current connection using a keyset UUID.
```sql
SET CIPHERSTASH.KEYSET_ID = '';
```
Example:
```sql
SET CIPHERSTASH.KEYSET_ID = '2cace9db-3a2a-4b46-a184-ba412b3e0730';
```
SET CIPHERSTASH.KEYSET_NAME [#set-cipherstashkeyset_name]
Sets the active keyset for the current connection using a keyset name.
```sql
SET CIPHERSTASH.KEYSET_NAME = '';
```
Example:
```sql
SET CIPHERSTASH.KEYSET_NAME = 'tenant-1';
```
Usage notes [#usage-notes]
* Execute `SET CIPHERSTASH.KEYSET` before performing any encryption operations.
* The keyset remains active for the duration of the connection, or until a subsequent `SET CIPHERSTASH.KEYSET` command.
* If a default keyset is configured in the proxy, these commands return an error.
* The active keyset is connection-scoped and does not affect other connections.
***
Disabling encrypted mapping [#disabling-encrypted-mapping]
CipherStash Proxy transforms the plaintext SQL statements your application issues into statements on EQL columns. This process is called encrypted mapping.
In some circumstances you may need to disable encrypted mapping for one or more SQL statements. For example, performing a data transformation with complex logic directly in the database using `plpgsql`.
Use the `SET` command to change the `CIPHERSTASH.UNSAFE_DISABLE_MAPPING` configuration parameter. The parameter is always scoped to the connection session.
If mapping is disabled, sensitive data may not be encrypted and may appear in
logs.
CipherStash Proxy and EQL provide some protection against writing or reading plaintext from encrypted columns. Always use `eql_v2.add_encrypted_constraint(table, column)` when defining encrypted columns to prevent plaintext data from being written. Unmapped `SELECT` statements return the encrypted payload. If the constraint is applied, unmapped `INSERT` and `UPDATE` statements return a PostgreSQL type error.
Disable mapping [#disable-mapping]
```sql
SET CIPHERSTASH.UNSAFE_DISABLE_MAPPING = true;
```
Enable mapping [#enable-mapping]
```sql
SET CIPHERSTASH.UNSAFE_DISABLE_MAPPING = false;
```
Prepared statements and mapping [#prepared-statements-and-mapping]
CipherStash Proxy only decrypts data from SQL statements it has explicitly checked and mapped.
If mapping is disabled, any subsequent `PREPARE` skips the mapping process. If mapping is re-enabled later, returned data from those prepared statements is not decrypted.
To enable mapping, encryption, and decryption of prepared statements, either:
* Open a new connection, or
* Prepare the statement again after re-enabling mapping.
This behaviour is expected. When a client prepares a statement, it sends the SQL in a `parse` message. Once prepared, the client refers to the statement by name and skips the `parse` step. If mapping is disabled during `parse`, the proxy does not map the statement. Data returned from subsequent executions is never decrypted, even if mapping is re-enabled later.
# Searchable JSON functions and operators
CipherStash Proxy supports a subset of PostgreSQL's JSONB functions and operators for use with encrypted columns. This page covers the supported operators, functions, and configuration required to enable searchable JSON.
Setup [#setup]
Schema [#schema]
Create a table with an `eql_v2_encrypted` column to store encrypted JSONB data:
```sql
CREATE TABLE cipherstash (
id SERIAL PRIMARY KEY,
encrypted_jsonb eql_v2_encrypted
);
```
Encrypted column configuration [#encrypted-column-configuration]
Add a `ste_vec` search index to the encrypted column:
```sql
SELECT eql_v2.add_search_config(
'cipherstash',
'encrypted_jsonb',
'ste_vec',
'jsonb',
'{"prefix": "cipherstash/encrypted_jsonb"}'
);
```
JSONB literals in `INSERT` and `UPDATE` statements work directly without explicit `::jsonb` type casts. The proxy infers the JSONB type from the target column.
Configuration options [#configuration-options]
The `ste_vec` index configuration accepts the following options:
| Option | Type | Default | Description |
| ------------------ | ---------------- | ---------- | ----------------------------------------------------------------- |
| `prefix` | string | (required) | Unique prefix for the index, typically `table/column` |
| `term_filters` | array | `[]` | Filters applied to indexed terms (e.g., `[{"kind": "downcase"}]`) |
| `array_index_mode` | string or object | `"all"` | Controls which array selectors are generated during indexing |
Array index mode [#array-index-mode]
The `array_index_mode` option controls which array selectors are generated during indexing.
Preset string values:
* `"all"` (default): Generates all selector types. Backwards compatible.
* `"none"`: Disables array indexing entirely.
Use the object form for fine-grained control:
```json
{
"item": true,
"wildcard": true,
"position": false
}
```
| Selector | JSONPath | Description |
| ---------- | ------------------ | ------------------------------------------------------------------ |
| `item` | `[@]` | EQL array element selector for functions like `jsonb_array_length` |
| `wildcard` | `[*]` | Standard JSONPath wildcard for iterating array elements |
| `position` | `[0]`, `[1]`, etc. | Positional access to specific array indices |
Enable all array selectors:
```sql
SELECT eql_v2.add_search_config(
'cipherstash',
'encrypted_jsonb',
'ste_vec',
'jsonb',
'{"prefix": "cipherstash/encrypted_jsonb", "array_index_mode": "all"}'
);
```
Disable positional indexing while keeping wildcard and item selectors:
```sql
SELECT eql_v2.add_search_config(
'events',
'payload',
'ste_vec',
'jsonb',
'{"prefix": "events/payload", "array_index_mode": {"item": true, "wildcard": true, "position": false}}'
);
```
Limitations [#limitations]
Encrypted literals cannot be passed as arguments to SQL functions. Encrypted columns can only be passed to SQL functions if the value has an encrypted search index that supports that specific function. For example, `AVG()` cannot be used on encrypted numeric values, `LOWER()` cannot be used on encrypted text, and `MIN()`/`MAX()` require an ORE index.
`CAST` operations cannot work on encrypted data. Casting requires decryption within the database, which the proxy does not support.
The `->` operator cannot be chained on `ste_vec` encrypted columns. Use `jsonb_path_query_first()` for deep nested access instead.
A selector path to an array field (`$.array`) returns the decrypted array as a JSON literal. To access an encrypted array as a set of encrypted values (for use with functions like `jsonb_array_length`), use the EQL array element selector `[@]`.
Operators [#operators]
-> (Field access) [#--field-access]
Extracts a JSON object field by key.
```sql
SELECT encrypted_jsonb -> 'number' FROM cipherstash;
-- Returns: 1
SELECT encrypted_jsonb -> 'object' FROM cipherstash;
-- Returns: { "string": "world", "number": 99 }
SELECT encrypted_jsonb -> 'string_array' FROM cipherstash;
-- Returns: ["hello","world"]
```
->> (Field access as text) [#--field-access-as-text]
Currently an alias for `->`. The data is returned as the decrypted JSON literal rather than a text string (unlike the standard PostgreSQL operator). The returned value can be cast to any valid type in the client.
```sql
SELECT encrypted_jsonb ->> 'number' FROM cipherstash;
-- Returns: 1
```
@> (Contains) [#-contains]
Tests whether the left value contains the right path/value entries at the top level. Supports string fields, numeric fields, complete arrays, and nested objects.
```sql
SELECT encrypted_jsonb @> '{"number": 1}' FROM cipherstash;
-- Returns: t
SELECT encrypted_jsonb @> '{"number": 99}' FROM cipherstash;
-- Returns: f
SELECT encrypted_jsonb @> '{"object": {"string": "world", "number": 99}}' FROM cipherstash;
-- Returns: t
```
<@ (Contained by) [#-contained-by]
Tests whether the left value is contained in the right value.
```sql
SELECT '{"number": 1}' <@ encrypted_jsonb FROM cipherstash;
-- Returns: t
```
Functions [#functions]
jsonb_path_query(target, path) [#jsonb_path_querytarget-path]
Returns all JSON items matched by the JSON path. Returns `setof eql_v2_encrypted` decrypted as `jsonb`.
```sql
SELECT jsonb_path_query(encrypted_jsonb, '$.number') FROM cipherstash;
-- Returns: 1
SELECT jsonb_path_query(encrypted_jsonb, '$.object') FROM cipherstash;
-- Returns: { "string": "world", "number": 99 }
SELECT jsonb_path_query(encrypted_jsonb, '$.object.string') FROM cipherstash;
-- Returns: "world"
SELECT jsonb_path_query(encrypted_jsonb, '$.string_array') FROM cipherstash;
-- Returns: ["hello","world"]
```
jsonb_path_query_first(target, path) [#jsonb_path_query_firsttarget-path]
Returns the first JSON item matched by the JSON path.
```sql
SELECT jsonb_path_query_first(encrypted_jsonb, '$.string_array[*]') FROM cipherstash;
-- Returns: "hello"
SELECT jsonb_path_query_first(encrypted_jsonb, '$.numeric_array[*]') FROM cipherstash;
-- Returns: 1
```
jsonb_path_exists(target, path) [#jsonb_path_existstarget-path]
Checks whether the JSON path returns any item. Returns a boolean.
```sql
SELECT jsonb_path_exists(encrypted_jsonb, '$.number') FROM cipherstash;
-- Returns: t
SELECT jsonb_path_exists(encrypted_jsonb, '$.unknown') FROM cipherstash;
-- Returns: f
```
jsonb_array_elements(target) [#jsonb_array_elementstarget]
Expands the top-level JSON array into a set of values. Requires the EQL array element selector `[@]`.
```sql
SELECT jsonb_array_elements(jsonb_path_query(encrypted_jsonb, '$.string_array[@]')) FROM cipherstash;
-- Returns: "hello", "world" (2 rows)
SELECT jsonb_array_elements(jsonb_path_query(encrypted_jsonb, '$.numeric_array[@]')) FROM cipherstash;
-- Returns: 1, 2, 3, 4 (4 rows)
```
jsonb_array_length(target) [#jsonb_array_lengthtarget]
Returns the number of elements in the top-level JSON array. Requires the EQL array element selector `[@]`.
```sql
SELECT jsonb_array_length(jsonb_path_query(encrypted_jsonb, '$.string_array[@]')) FROM cipherstash;
-- Returns: 2
SELECT jsonb_array_length(jsonb_path_query(encrypted_jsonb, '$.numeric_array[@]')) FROM cipherstash;
-- Returns: 4
```
Comparison operators in WHERE clauses [#comparison-operators-in-where-clauses]
All standard comparison operators work with JSON field extraction:
```sql
-- Equality
SELECT encrypted_jsonb FROM cipherstash WHERE encrypted_jsonb -> 'string' = 'B';
SELECT encrypted_jsonb FROM cipherstash WHERE jsonb_path_query_first(encrypted_jsonb, '$.string') = 'B';
-- Greater than
SELECT encrypted_jsonb FROM cipherstash WHERE encrypted_jsonb -> 'number' > 4;
-- Less than
SELECT encrypted_jsonb FROM cipherstash WHERE encrypted_jsonb -> 'number' < 3;
-- Greater than or equal
SELECT encrypted_jsonb FROM cipherstash WHERE encrypted_jsonb -> 'number' >= 4;
-- Less than or equal
SELECT encrypted_jsonb FROM cipherstash WHERE encrypted_jsonb -> 'number' <= 3;
```
JSONPath syntax [#jsonpath-syntax]
| Syntax | Description |
| ---------------- | ------------------------------------------------------------ |
| `$.field` | Access a top-level field |
| `$.nested.field` | Access a nested field |
| `$.array[*]` | Wildcard selector for all array elements |
| `$.array[@]` | EQL array element selector for use with processing functions |
Parameterized queries [#parameterized-queries]
All functions and operators support parameterized queries:
```sql
SELECT encrypted_jsonb -> $1 FROM cipherstash;
SELECT jsonb_path_query(encrypted_jsonb, $1) FROM cipherstash;
SELECT encrypted_jsonb @> $1 FROM cipherstash;
```
Supported data types [#supported-data-types]
The `ste_vec` index supports strings, numbers, booleans, arrays, objects, and nested structures.
# Troubleshooting
Troubleshooting ZeroKMS connections [#troubleshooting-zerokms-connections]
Recommended log settings [#recommended-log-settings]
For a quick check on ZeroKMS latency and connection issues, set:
```bash
CS_LOG__ZEROKMS_LEVEL=debug
CS_LOG__ENCRYPT_LEVEL=debug
```
For a deeper investigation, also enable slow statement detection:
```bash
CS_LOG__ZEROKMS_LEVEL=trace
CS_LOG__SLOW_STATEMENTS=true
CS_LOG__SLOW_STATEMENT_MIN_DURATION_MS=500
CS_LOG__SLOW_DB_RESPONSE_MIN_DURATION_MS=50
```
What to look for in logs [#what-to-look-for-in-logs]
| Signal | Meaning |
| --------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `Initializing ZeroKMS ScopedCipher (cache miss)` | A network call to ZeroKMS is about to happen. Frequent occurrences indicate cache churn. |
| `Connected to ZeroKMS` with high `init_duration_ms` | Slow cipher init. Healthy values are under 200ms. Values over 1s trigger a warning. |
| `Use cached ScopedCipher` | Cache hit (fast path, no network call). |
| `ScopedCipher evicted from cache` with `cause: Size` | Cache too small for workload. Increase `cipher_cache_size`. |
| `Error initializing ZeroKMS` with high `init_duration_ms` | Network timeout to ZeroKMS. |
| `Error initializing ZeroKMS` with low `init_duration_ms` | Credential or configuration error. |
Key metrics [#key-metrics]
Enable Prometheus with `CS_PROMETHEUS__ENABLED=true` and watch these metrics:
| Metric | Why |
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------- |
| `cipherstash_proxy_keyset_cipher_init_duration_seconds` | Distribution of ZeroKMS init times including network latency. |
| `cipherstash_proxy_keyset_cipher_cache_hits_total` / `cache_miss_total` | Cache hit ratio. Should be above 95% in steady state. |
| `cipherstash_proxy_statements_session_duration_seconds` minus `execution_duration_seconds` | Encryption overhead per statement. |
| `cipherstash_proxy_encryption_error_total` / `decryption_error_total` | Spikes indicate ZeroKMS connectivity issues. |
Useful PromQL queries [#useful-promql-queries]
```txt
# P99 cipher init latency
histogram_quantile(0.99, rate(cipherstash_proxy_keyset_cipher_init_duration_seconds_bucket[5m]))
# Cache hit ratio
rate(cipherstash_proxy_keyset_cipher_cache_hits_total[5m])
/ (rate(cipherstash_proxy_keyset_cipher_cache_hits_total[5m])
+ rate(cipherstash_proxy_keyset_cipher_cache_miss_total[5m]))
```
Quick checklist [#quick-checklist]
1. **Cache hit ratio low?** Tune `CS_SERVER__CIPHER_CACHE_SIZE` (default: 64) and `CS_SERVER__CIPHER_CACHE_TTL_SECONDS` (default: 3600).
2. **`init_duration_ms` above 1s?** Network latency to ZeroKMS. Check DNS, firewall rules, and regional proximity.
3. **Large gap between session and execution duration?** Overhead is in encrypt/decrypt, not the database.
4. **Frequent evictions?** Increase `cipher_cache_size` to match your workload's keyset count.
***
Slow statement logging [#slow-statement-logging]
CipherStash Proxy includes built-in slow statement logging for diagnosing performance issues.
Configuration [#configuration]
Enable slow statement logging via environment variables:
```bash
# Enable slow statement logging (required)
CS_LOG__SLOW_STATEMENTS=true
# Optional: Set minimum duration threshold (default: 2000ms)
CS_LOG__SLOW_STATEMENT_MIN_DURATION_MS=500
# Optional: Set log level (default: warn when enabled)
CS_LOG__SLOW_STATEMENTS_LEVEL=warn
# Recommended: Use structured logging for parsing
CS_LOG__FORMAT=structured
```
Slow statement log format [#slow-statement-log-format]
When a statement exceeds the threshold, the proxy logs a detailed breakdown:
```json filename="slow statement log entry"
{
"client_id": 1,
"duration_ms": 10500,
"statement_type": "INSERT",
"protocol": "extended",
"encrypted": true,
"encrypted_values_count": 3,
"param_bytes": 1024,
"query_fingerprint": "a1b2c3d4",
"keyset_id": "uuid",
"mapping_disabled": false,
"breakdown": {
"parse_ms": 5,
"encrypt_ms": 450,
"server_write_ms": 12,
"server_wait_ms": 9800,
"server_response_ms": 233
}
}
```
Query fingerprints [#query-fingerprints]
Query fingerprints are ephemeral and instance-local. Each proxy instance generates a unique random key at startup to compute `query_fingerprint` values. Fingerprints change when the proxy restarts and cannot be correlated across different proxy instances. This is intentional for security. Use fingerprints for correlation within a single proxy instance's runtime only.
Prometheus histogram labels [#prometheus-histogram-labels]
Duration histograms include labels for filtering:
* `statement_type`: `insert`, `update`, `delete`, `select`, `other`
* `protocol`: `simple`, `extended`
* `mapped`: `true`, `false`
* `multi_statement`: `true`, `false`
Example PromQL queries:
```txt
# Average INSERT duration
histogram_quantile(0.5, rate(cipherstash_proxy_statements_session_duration_seconds_bucket{statement_type="insert"}[5m]))
# Compare encrypted vs passthrough
histogram_quantile(0.99, rate(cipherstash_proxy_statements_session_duration_seconds_bucket{mapped="true"}[5m]))
```
ZeroKMS cipher init metric [#zerokms-cipher-init-metric]
`cipherstash_proxy_keyset_cipher_init_duration_seconds` measures time for cipher initialization including the ZeroKMS network call. High values indicate ZeroKMS connectivity issues.
Interpreting results [#interpreting-results]
| Symptom | Likely Cause |
| --------------------------- | ------------------------------------- |
| High `encrypt_ms` | ZeroKMS latency or large payload. |
| High `server_wait_ms` | Database latency. |
| High `cipher_init_duration` | ZeroKMS cold start or network issues. |
| High `parse_ms` | Complex SQL or schema lookup. |
# CipherStash vs AWS KMS
Encrypting data should not require managing binary buffers, base64 encoding, key ARNs, or building custom search solutions. CipherStash Encryption eliminates these complexities with a developer-friendly API.
The simple truth: Encrypting a value [#the-simple-truth-encrypting-a-value]
Start with the most basic operation: encrypting a single value.
AWS KMS: Manual work required [#aws-kms-manual-work-required]
```typescript
import { KMSClient, EncryptCommand } from '@aws-sdk/client-kms';
const client = new KMSClient({ region: 'us-west-2' });
const keyId = 'arn:aws:kms:us-west-2:123456789012:key/abcd1234-efgh-5678-ijkl-9012mnopqrst';
async function encryptWithKMS(plaintext: string): Promise {
try {
const command = new EncryptCommand({
KeyId: keyId,
Plaintext: Buffer.from(plaintext),
});
const response = await client.send(command);
const ciphertext = response.CiphertextBlob;
const base64Ciphertext = Buffer.from(ciphertext).toString('base64');
return base64Ciphertext;
} catch (error) {
console.error('Error encrypting data:', error);
throw error;
}
}
const encrypted = await encryptWithKMS('secret@squirrel.example');
```
**What you're managing:** Key ARNs, binary buffer conversions, base64 encoding/decoding, manual error handling, region configuration, AWS credential setup.
CipherStash Encryption: One simple call [#cipherstash-encryption-one-simple-call]
```typescript
import { Encryption } from '@cipherstash/stack';
import { encryptedTable, encryptedColumn } from '@cipherstash/stack/schema';
const users = encryptedTable('users', {
email: encryptedColumn('email'),
});
const client = await Encryption({
schemas: [users],
});
const encryptResult = await client.encrypt(
'secret@squirrel.example',
{ column: users.email, table: users }
);
if (encryptResult.failure) {
throw new Error(encryptResult.failure.message);
}
const ciphertext = encryptResult.data;
```
**What you get:** No key management (handled by ZeroKMS), no binary conversions, no base64 encoding, type-safe error handling, JSON payload ready for storage, zero-knowledge encryption by default.
Decryption [#decryption]
AWS KMS [#aws-kms]
```typescript
async function decryptWithKMS(base64Ciphertext: string): Promise {
try {
const ciphertextBlob = Buffer.from(base64Ciphertext, 'base64');
const command = new DecryptCommand({ CiphertextBlob: ciphertextBlob });
const response = await client.send(command);
return Buffer.from(response.Plaintext).toString('utf-8');
} catch (error) {
console.error('Error decrypting data:', error);
throw error;
}
}
```
CipherStash Encryption [#cipherstash-encryption]
```typescript
const decryptResult = await client.decrypt(ciphertext);
if (decryptResult.failure) {
throw new Error(decryptResult.failure.message);
}
const plaintext = decryptResult.data;
```
Features that AWS KMS can't do without major custom work [#features-that-aws-kms-cant-do-without-major-custom-work]
1. Searchable encryption: Built-in vs impossible [#1-searchable-encryption-built-in-vs-impossible]
**AWS KMS** requires decrypting everything and searching in memory, storing plaintext indexes, or building a custom searchable encryption solution.
**CipherStash Encryption** has searchable encryption built-in:
```typescript
const users = encryptedTable('users', {
email: encryptedColumn('email')
.freeTextSearch()
.equality()
.orderAndRange(),
});
const searchTerms = await client.encryptQuery('secret', {
column: users.email,
table: users,
});
```
2. Identity-aware encryption: Built-in vs custom implementation [#2-identity-aware-encryption-built-in-vs-custom-implementation]
**AWS KMS** has no built-in support. You must implement custom logic with encryption context as a workaround.
**CipherStash Encryption** has built-in identity-aware encryption:
```typescript
import { LockContext } from '@cipherstash/stack/identity';
const lc = new LockContext();
const lockContext = await lc.identify(userJwt);
const encryptResult = await client.encrypt(
'secret@squirrel.example',
{ column: users.email, table: users }
).withLockContext(lockContext);
const decryptResult = await client.decrypt(ciphertext)
.withLockContext(lockContext);
```
3. Bulk operations: Native API vs manual batching [#3-bulk-operations-native-api-vs-manual-batching]
**AWS KMS** has no bulk API. You must manually batch operations and handle rate limits.
**CipherStash Encryption** has native bulk encryption:
```typescript
const bulkPlaintexts = [
{ id: '1', plaintext: 'Alice' },
{ id: '2', plaintext: 'Bob' },
{ id: '3', plaintext: 'Charlie' },
];
const bulkResult = await client.bulkEncrypt(bulkPlaintexts, {
column: users.name,
table: users,
});
const encryptedMap = bulkResult.data;
```
Feature comparison [#feature-comparison]
| Feature | AWS KMS | CipherStash Encryption |
| ----------------------------- | -------------------------------------- | -------------------------------- |
| **Basic Encryption** | Requires manual buffer/base64 handling | One-line API, JSON payload |
| **Key Management** | You manage key ARNs | Zero-knowledge, automatic |
| **Searchable Encryption** | Not possible | Built-in for PostgreSQL |
| **Identity-Aware Encryption** | Custom implementation | Built-in `LockContext` |
| **Bulk Operations** | Manual batching | Native bulk API |
| **Error Handling** | Try/catch, manual types | Type-safe Result pattern |
| **Type Safety** | Manual typing | Full TypeScript inference |
| **Storage Format** | Binary (needs encoding) | JSON (database-ready) |
| **ORM Integration** | Manual integration | Built-in Drizzle support |
| **Zero-Knowledge** | AWS has key access | True zero-knowledge |
| **Setup Complexity** | Medium (AWS credentials, regions) | Low (just environment variables) |
Developer experience comparison [#developer-experience-comparison]
Error handling [#error-handling]
**AWS KMS:** Try/catch with manual error type checking:
```typescript
try {
const response = await client.send(command)
} catch (error) {
if (error.name === "AccessDeniedException") {
// Handle access denied
} else if (error.name === "InvalidKeyUsageException") {
// Handle invalid key usage
}
}
```
**CipherStash Encryption:** Type-safe Result pattern:
```typescript
const result = await client.encrypt(plaintext, options)
if (result.failure) {
switch (result.failure.type) {
case "EncryptionError":
// TypeScript knows this is an EncryptionError
break
case "ClientInitError":
// TypeScript knows this is a ClientInitError
break
}
}
```
Type safety [#type-safety]
**AWS KMS:** Manual typing with binary data handling:
```typescript
const plaintext: string = Buffer.from(response.Plaintext).toString("utf-8")
```
**CipherStash Encryption:** Full TypeScript inference:
```typescript
const plaintext = decryptResult.data // Type: string
```
Storage format [#storage-format]
**AWS KMS:** Binary data that needs encoding:
```typescript
// Returns Uint8Array, must encode for storage
const base64 = Buffer.from(ciphertext).toString("base64")
```
**CipherStash Encryption:** JSON payload ready for database:
```typescript
// Returns JSON payload ready for JSONB storage
const ciphertext = encryptResult.data
```
Complete workflow comparison [#complete-workflow-comparison]
AWS KMS: Full implementation [#aws-kms-full-implementation]
```typescript
import { KMSClient, EncryptCommand, DecryptCommand } from "@aws-sdk/client-kms"
const client = new KMSClient({ region: "us-west-2" })
const keyId = "arn:aws:kms:us-west-2:123456789012:key/abcd1234-efgh-5678-ijkl-9012mnopqrst"
async function encrypt(plaintext: string): Promise {
const command = new EncryptCommand({
KeyId: keyId,
Plaintext: Buffer.from(plaintext),
})
const response = await client.send(command)
return Buffer.from(response.CiphertextBlob).toString("base64")
}
async function decrypt(base64Ciphertext: string): Promise {
const command = new DecryptCommand({
CiphertextBlob: Buffer.from(base64Ciphertext, "base64"),
})
const response = await client.send(command)
return Buffer.from(response.Plaintext).toString("utf-8")
}
const encrypted = await encrypt("secret@squirrel.example")
const decrypted = await decrypt(encrypted)
```
\~25 lines for basic encrypt/decrypt. You manage: key ARNs, binary conversions, base64 encoding, error handling, AWS credentials, regions.
CipherStash Encryption: Full implementation [#cipherstash-encryption-full-implementation]
```typescript
import { Encryption } from "@cipherstash/stack"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email"),
})
const client = await Encryption({ schemas: [users] })
const encryptResult = await client.encrypt("secret@squirrel.example", {
column: users.email,
table: users,
})
if (encryptResult.failure) {
throw new Error(encryptResult.failure.message)
}
const decryptResult = await client.decrypt(encryptResult.data)
```
\~20 lines including setup. `@cipherstash/stack` handles everything.
When to use each [#when-to-use-each]
Use AWS KMS when: [#use-aws-kms-when]
* You need encryption for AWS services (S3, EBS, etc.)
* You're encrypting infrastructure-level resources
* You don't need to search encrypted data
* You're comfortable with manual buffer/base64 handling
Use CipherStash Encryption when: [#use-cipherstash-encryption-when]
* You're building applications with databases
* You need to search encrypted data
* You want a developer-friendly API
* You need identity-aware encryption
* You want zero-knowledge key management
* You value type safety and developer experience
References [#references]
* [AWS KMS Documentation](https://docs.aws.amazon.com/kms/)
* [CipherStash Encryption Getting Started](/stack/quickstart)
* [CipherStash Schema Reference](/stack/cipherstash/encryption/schema)
* [Searchable Encryption Concepts](/stack/cipherstash/encryption/searchable-encryption)
# CipherStash vs Homomorphic Encryption
CipherStash is often described — incorrectly — as "homomorphic encryption for databases." It isn't. CipherStash uses a **portfolio of specialised searchable-encryption schemes**, exposed through PostgreSQL via [EQL](/stack/reference/eql-guide), that are 5–6 orders of magnitude faster than today's best Fully Homomorphic Encryption (FHE) at the operations a real database actually runs.
This page explains the difference, with numbers.
TL;DR [#tldr]
| | Fully Homomorphic Encryption (FHE) | CipherStash searchable encryption |
| --------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| **Goal** | Run *arbitrary* computation on ciphertext | Make the database operations a real workload runs (equality, match, range) practical on ciphertext |
| **Approach** | One general-purpose primitive | A portfolio of specialised primitives, one per query type |
| **Per-operation cost (Apple M2)** | \~150 ms equality, \~165 ms compare, \~11 ms encrypt | \~755 ns equality, \~755 ns compare, \~375 µs encrypt |
| **Speed-up vs TFHE** | 1× | \~200,000× equality, \~215,000× greater-than, \~29× encrypt |
| **Where it runs** | Specialised libraries, custom hardware proposals | Standard PostgreSQL with [EQL](/stack/reference/eql-guide) |
| **Production-ready today** | No — orders of magnitude too slow for OLTP | Yes — sub-ms exact match, low-ms range on million-row tables |
Benchmarks: [`github.com/cipherstash/tfhe-ore-bench`](https://github.com/cipherstash/tfhe-ore-bench) (Apple M2, single-threaded; TFHE compared against CipherStash ORE).
Why the conflation happens [#why-the-conflation-happens]
Both FHE and searchable encryption answer the question *"Can I do something useful with encrypted data without decrypting it first?"* That question, framed at a whiteboard, sounds like a single problem. So when people first see CipherStash they reach for the most well-known answer — homomorphic encryption — and the comparison sticks.
It's the wrong frame. FHE and searchable encryption sit on opposite ends of a generality / performance trade-off:
* **FHE** keeps a single ciphertext format and lets you run *any* circuit over it. The price is that even simple operations cost tens to hundreds of milliseconds per ciphertext, and the ciphertext blows up in size as you compose operations. It is a remarkable theoretical achievement and an active area of research; it is not yet practical for production databases.
* **Searchable encryption** is a family of primitives, each *purpose-built* for one class of operation. Equality has its own scheme. Substring containment has its own scheme. Range and ordering have their own scheme. Each is dramatically cheaper than FHE because each does much less.
CipherStash is squarely in the second camp. The reason it can deliver microsecond-class encrypted predicates while TFHE takes 150 ms is not that we found a faster FHE — it's that we don't need FHE at all.
Two cryptographic philosophies [#two-cryptographic-philosophies]
**FHE** stakes everything on a single universal cipher and the ability to run any circuit over it. The cost is tens to hundreds of milliseconds per operation.
**CipherStash** keeps a portfolio of specialised ciphers and picks the right one per query type. The cost is sub-microsecond per operation.
For workloads that are essentially "find rows where X, sort by Y" — i.e. the workload of every line-of-business application on the planet — the second philosophy is dramatically more efficient and equally rigorous, *provided* each scheme has a published, formally-bounded leakage profile. CipherStash's schemes do (see [Security architecture](/stack/reference/security-architecture)).
Performance — measured per-operation cost [#performance--measured-per-operation-cost]
Numbers below are from the open-source benchmark harness at [`github.com/cipherstash/tfhe-ore-bench`](https://github.com/cipherstash/tfhe-ore-bench). Single-threaded, Apple M2, comparing TFHE — a leading FHE library — against CipherStash's Order-Revealing Encryption (ORE) on the primitive operations a database executes per row.
| Operation | TFHE (FHE) | CipherStash ORE | Speed-up |
| --------------------------- | ---------- | --------------- | -------------- |
| Encrypt one value | \~11 ms | \~375 µs | \~29× |
| Equality (`a == b`) | \~150 ms | \~755 ns | **\~200,000×** |
| Greater-than (`a > b`) | \~162 ms | \~755 ns | **\~215,000×** |
| Less-than (`a < b`) | \~166 ms | \~755 ns | **\~220,000×** |
| Greater-or-equal (`a >= b`) | \~25 ms | \~131 ns | **\~190,000×** |
| Less-or-equal (`a <= b`) | \~30 ms | \~131 ns | **\~225,000×** |
The implication is straightforward: a single equality check at \~150 ms is already untenable per row. Multiplied across a million-row `WHERE` clause, FHE is impossible; the same predicate in CipherStash runs in microseconds end-to-end and rides standard PostgreSQL B-tree, hash and GIN indexes.
> **Good to know**: FHE's selling point is that it can compute *anything*. CipherStash deliberately doesn't try to. We bet — correctly, for database workloads — that the long tail of "interesting" computation a database performs collapses to a small set of well-defined operations, and that specialised schemes for those operations beat a universal one by five or six orders of magnitude.
The CipherStash scheme portfolio [#the-cipherstash-scheme-portfolio]
In practice, only the 3–5 sensitive columns per table use these schemes. Foreign keys, timestamps, status flags and most other columns remain plaintext and behave exactly as they always have. That is what makes the portfolio approach tractable in real deployments.
| Scheme | Used for | Mechanism | Leakage profile |
| ------------------------------ | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **HMAC-SHA-256** | Exact equality (`=`, joins, unique indexes) | Keyed hash — equal plaintexts produce equal tags; distinct plaintexts are indistinguishable. | Equality only. Pair with `UNIQUE` to eliminate frequency on repeated values. |
| **Encrypted Bloom filter** | Token / substring containment (`LIKE`, full-text) | Trigram tokens hashed into a fixed-size bit vector; queried via GIN containment with client-side false-positive filtering. | Tuneable false-positive rate doubles as a security knob — higher FPR obscures token-frequency patterns at the cost of more client-side filtering. |
| **Block ORE — Lewi-Wu (2016)** | Range and ordering (`<`, `>`, `BETWEEN`, `ORDER BY`) | Two-component ciphertexts (left / right). Right ciphertexts are stored; left ciphertexts are query-time only. Comparison is performed via the Postgres operator class. | Semantically-secure right-ciphertexts. Because left ciphertexts are not stored, **static order is not leaked** — only the order revealed by the queries you actually run. |
Underlying every encrypted value, AES-GCM-SIV provides the authenticated encryption used for retrieval. The schemes above are *searchable indexes* alongside the ciphertext, not replacements for it. The database stores `ciphertext + indexes`; the application receives plaintext only after key-bound decryption.
For full leakage definitions and parameter-selection guidance, see [Security architecture](/stack/reference/security-architecture).
What gets encrypted, and what doesn't [#what-gets-encrypted-and-what-doesnt]
A common misconception is that "encrypted database" means every column is opaque. In reality:
* **Sensitive columns are encrypted.** Names, emails, phone numbers, financial values, health attributes, free-text notes — typically 3–5 columns per sensitive table.
* **Everything else stays plaintext.** Surrogate primary keys, foreign keys, timestamps, enums, status flags, soft-delete markers, audit columns. They behave normally — indexed normally, joined normally, replicated normally.
* **Joins usually happen on plaintext.** Foreign keys are not normally sensitive; the join keys are plaintext, and join performance is unchanged. Where a join *does* need to happen on a sensitive value, an HMAC index makes it efficient.
This single fact reframes most operational concerns about encrypted databases. The encrypted columns are an addition to your schema, not a replacement for it.
Where plaintext actually lives [#where-plaintext-actually-lives]
With the [Encryption SDK](/stack/cipherstash/encryption), plaintext exists only inside your client application, edge worker, or browser. The PostgreSQL database, the network between you and it, any logical-replication consumer, and any Postgres-aware tooling (pgAdmin, query logs, backups, debug snapshots) see only ciphertext and searchable indexes.
This matters because most production data exposure happens *outside* the database — in proxies, replicas, BI tools, debug logs, error-reporting systems. With the SDK pattern, those surfaces never see plaintext to begin with.
> **Good to know**: CipherStash also offers a server-side [Proxy](/stack/cipherstash/proxy) for drop-in adoption with no code changes. Proxy and SDK are different deployment shapes of the same primitives; the SDK pattern gives you the strongest end-to-end guarantee.
EQL — open source, pluggable, extensible [#eql--open-source-pluggable-extensible]
All of this is implemented as **EQL — Encrypt Query Language**, an open-source set of PostgreSQL types, operators, and functions ([github.com/cipherstash/encrypt-query-language](https://github.com/cipherstash/encrypt-query-language)). EQL exposes the encrypted operators as standard SQL on the `eql_v2_encrypted` type:
* HMAC-backed equality for `=`, joins, and unique constraints
* Bloom-filter–backed substring match for `LIKE` / `ILIKE`
* ORE-backed comparisons for `<`, `>`, `BETWEEN`, and `ORDER BY`
Because every encrypted predicate is just standard SQL on an `eql_v2_encrypted` column, your application SQL stays standard SQL. Your ORM, your query builder, your DBA's `EXPLAIN ANALYZE` workflow, your migration tooling — all of it keeps working.
Two architectural consequences are worth calling out:
**Schemes are swappable.** A column's index can be re-keyed or migrated to a different scheme without changing application SQL. As stronger primitives mature — lattice-based searchable schemes, structured encryption variants, eventually practical FHE for specific operations — they slot in behind the same EQL surface.
**Quantum-resistant by construction.** The current primitives (AES-GCM-SIV, HMAC-SHA-256, BLAKE3) avoid the cryptographic assumptions known to fall to a sufficiently large quantum computer. No RSA, no elliptic curves. A column encrypted today is still secure on the day post-quantum migration becomes urgent.
What this looks like in SQL [#what-this-looks-like-in-sql]
Encrypted columns participate in standard SQL — the `eql_v2_encrypted` type and its operators do the heavy lifting:
```sql title="encrypted-queries.sql"
-- Exact equality (HMAC index)
SELECT id, name
FROM users
WHERE email = $1::eql_v2_encrypted;
-- Token / substring match (Bloom filter)
SELECT id, name
FROM users
WHERE name LIKE $1::eql_v2_encrypted
LIMIT 10;
-- Range query (ORE)
SELECT id, amount
FROM transactions
WHERE amount > $1::eql_v2_encrypted
ORDER BY amount
LIMIT 100;
-- Join on a plaintext foreign key (unchanged)
SELECT u.id, t.amount
FROM users u
JOIN transactions t ON t.user_id = u.id
WHERE u.email = $1::eql_v2_encrypted;
```
In each case, `$1` is a CipherCell produced by the SDK — the application encrypts the search value with the same key as the stored data, and PostgreSQL compares the searchable encrypted metadata without ever seeing plaintext. See the [EQL guide](/stack/reference/eql-guide) for the full operator and function surface.
Operational FAQ [#operational-faq]
The questions a senior engineer typically asks before approving an encrypted-database rollout — answered directly.
Joins across encrypted columns? [#joins-across-encrypted-columns]
Supported, and effectively the same performance as plaintext. In practice the join key itself is rarely sensitive — joins happen on surrogate IDs or foreign keys, which stay plaintext. When a join *does* need to happen on a sensitive value (e.g. email), the HMAC index makes equality joins efficient.
MIN, MAX, COUNT? [#min-max-count]
`COUNT` works without modification on encrypted columns. `MIN` and `MAX` work over ORE-indexed columns. `SUM` and `AVG` of encrypted numerics are not supported by the current scheme portfolio — decrypt application-side or aggregate over a plaintext bucket column.
GROUP BY? [#group-by]
Supported on equality-indexed columns. By definition, grouping by an encrypted column reveals the histogram of distinct values to the query — that is the result the application asked for. If the histogram itself is sensitive, group on a coarser plaintext bucket column instead, or restrict who can run the query at the application layer.
Foreign keys, cascading deletes? [#foreign-keys-cascading-deletes]
Unchanged. Foreign keys are not normally encrypted; cascading deletes, referential integrity, and `ON DELETE` semantics work as they always have.
NULL, empty strings, collation? [#null-empty-strings-collation]
Preserved. Encrypted strings carry UTF-8 collation and Unicode normalization metadata so that ordering and equality remain consistent under encryption. NULL handling matches standard PostgreSQL semantics.
Logical replication, pg_dump / restore, read replicas? [#logical-replication-pg_dump--restore-read-replicas]
All supported. Encrypted values and indexes are stored as standard PostgreSQL `JSONB` — they replicate, dump, restore and stream to read replicas like any other column.
Backups and key recovery? [#backups-and-key-recovery]
Key IDs are stored alongside the ciphertext. On restore, the SDK derives the same per-record keys via [ZeroKMS](/stack/cipherstash/kms) using those IDs. There is no separate "key restore" step that has to be choreographed with the data restore — the references travel with the data.
Supabase RLS and PostgREST? [#supabase-rls-and-postgrest]
Both work. RLS policies execute against plaintext columns (typically authentication-related: `user_id`, `tenant_id`) just as they do today. PostgREST exposes encrypted columns as opaque values to clients, and the SDK handles encryption/decryption at the application boundary.
Schema migrations and re-keying? [#schema-migrations-and-re-keying]
A column's index can be re-keyed or migrated to a different scheme without changing application SQL. Online re-keying is supported via dual-write during the migration window.
Security & threat model [#security--threat-model]
Each scheme has a published, formally-bounded leakage profile:
* **HMAC-SHA-256**: equality only. Frequency leaks on repeated values; eliminated by `UNIQUE` constraints or by encrypting only high-cardinality columns.
* **Encrypted Bloom filter**: false-positive rate is a deliberate parameter; raising it obscures token-frequency patterns at the cost of additional client-side filtering. Repeated query analysis is bounded by the FPR.
* **Block ORE — Lewi-Wu (2016)**: semantically-secure right-ciphertexts. Left ciphertexts are query-time only and not stored, so an attacker reading the database at rest does **not** see static order — only the order revealed by the queries you actually choose to run.
The trust boundary in the recommended SDK deployment is your client application, edge worker, or browser. The PostgreSQL instance, the network, ZeroKMS, the operator running your Postgres, and any Postgres-aware tooling are all *outside* that boundary and never see plaintext.
For deeper analysis — formal definitions of each scheme's leakage function, parameter-selection guidance, and attack-model walk-throughs — see [Security architecture](/stack/reference/security-architecture). Full analysis is available on request under NDA.
When CipherStash isn't the right fit [#when-cipherstash-isnt-the-right-fit]
In the spirit of an honest comparison page:
* **You need general-purpose computation on ciphertext.** If your workload is "run arbitrary user-defined functions over encrypted data" — for example, evaluating an opaque ML model on encrypted inputs — searchable encryption is not the right tool. That is genuinely the FHE problem, and you should track the FHE literature.
* **The query patterns you care about aren't equality, match or range.** Searchable encryption schemes exist for many other operations (graph traversal, geometric proximity, regex), but CipherStash's portfolio today is targeted at the operations a typical OLTP / line-of-business workload runs. If your central workload is something else, [talk to us](https://cipherstash.com/contact) about what's possible.
* **You want every column encrypted with no plaintext metadata at all.** That is achievable but expensive, and almost always unnecessary. The right design encrypts the sensitive columns and leaves the rest of the schema in plaintext.
Get started [#get-started]
* **Benchmarks**: [`github.com/cipherstash/tfhe-ore-bench`](https://github.com/cipherstash/tfhe-ore-bench)
* **EQL on GitHub**: [`github.com/cipherstash/encrypt-query-language`](https://github.com/cipherstash/encrypt-query-language)
* **Encryption SDK quickstart**: [`/stack/quickstart`](/stack/quickstart)
* **Talk to us** about a specific workload: [cipherstash.com/contact](https://cipherstash.com/contact)
***
> **Bottom line**: CipherStash is *not* FHE and does not need to be. It is a portfolio of specialised, well-understood searchable-encryption schemes — HMAC for equality, encrypted Bloom filters for token match, Lewi-Wu Block ORE for range — exposed through [EQL](/stack/reference/eql-guide) on standard PostgreSQL. That is what makes it 5–6 orders of magnitude faster than TFHE at the operations a real workload runs, while keeping data encrypted end-to-end.
# Overview
Side-by-side breakdowns of CipherStash against the approaches teams most commonly weigh it against. Each page focuses on the same questions: what does each tool actually do, what are the per-operation trade-offs, and where is each one the right fit.
Related [#related]
* [Use cases](/stack/reference/use-cases) — including a comparison against data vaults like Skyflow and VGS.
* [Security architecture](/stack/reference/security-architecture) — the cryptographic primitives behind every comparison on this page.
# EQL API Reference
> **Latest Version:** 2.3.1
Complete API reference for the Encrypt Query Language (EQL) PostgreSQL extension.
Functions [#functions]
* [`->(eql_v2_encrypted, eql_v2_encrypted)`](#-eql_v2_encrypted-eql_v2_encrypted) - -> operator with encrypted selector
* [`->>()`](#) - ->> operator with encrypted selector
* [`>(eql_v2_encrypted, jsonb)`](#eql_v2_encrypted-jsonb) - > operator for encrypted value and JSONB
* [`>(eql_v2_encrypted, jsonb)`](#eql_v2_encrypted-jsonb-1) - > operator for encrypted value and JSONB
* [`bloom_filter(eql_v2_encrypted)`](#bloom_filtereql_v2_encrypted) - Extract Bloom filter index term from encrypted column value.
* [`check_encrypted(eql_v2_encrypted)`](#check_encryptedeql_v2_encrypted) - Validate encrypted composite type structure.
* [`compare(eql_v2.ste_vec_entry, eql_v2.ste_vec_entry)`](#compareeql_v2ste_vec_entry-eql_v2ste_vec_entry) - Three-way ordering on eql\_v2.ste\_vec\_entry
* [`compare_ore_block_u64_8_256_term(eql_v2.ore_block_u64_8_256_term, eql_v2.ore_block_u64_8_256_term)`](#compare_ore_block_u64_8_256_termeql_v2ore_block_u64_8_256_term-eql_v2ore_block_u64_8_256_term) - Compare two ORE block terms using cryptographic comparison.
* [`compare_ore_block_u64_8_256_terms(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#compare_ore_block_u64_8_256_termseql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256) - Compare ORE block composite types.
* [`compare_ore_block_u64_8_256_terms(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#compare_ore_block_u64_8_256_termseql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256-1) - Compare ORE block composite types.
* [`compare_ore_block_u64_8_256_terms(eql_v2.ore_block_u64_8_256_term, eql_v2.ore_block_u64_8_256_term)`](#compare_ore_block_u64_8_256_termseql_v2ore_block_u64_8_256_term-eql_v2ore_block_u64_8_256_term) - Compare arrays of ORE block terms recursively.
* [`compare_ore_cllw_term(eql_v2.ore_cllw, eql_v2.ore_cllw)`](#compare_ore_cllw_termeql_v2ore_cllw-eql_v2ore_cllw) - Variable-length CLLW ORE term comparison.
* [`compare_ore_cllw_term_bytes(bytea, bytea)`](#compare_ore_cllw_term_bytesbytea-bytea) - CLLW per-byte comparison helper.
* [`config_add_cast(text, text, text, jsonb)`](#config_add_casttext-text-text-jsonb) - Set cast type for column in configuration.
* [`config_add_column(text, text, jsonb)`](#config_add_columntext-text-jsonb) - Add column to table configuration if not present.
* [`config_add_index(text, text, text, jsonb, jsonb)`](#config_add_indextext-text-text-jsonb-jsonb) - Add search index to column configuration.
* [`config_add_table(text, jsonb)`](#config_add_tabletext-jsonb) - Add table to configuration if not present.
* [`config_check_cast(jsonb)`](#config_check_castjsonb) - Validate cast types in configuration.
* [`config_check_indexes(jsonb)`](#config_check_indexesjsonb) - Validate index types in configuration.
* [`config_check_ste_vec_mode(jsonb)`](#config_check_ste_vec_modejsonb) - Validate ste\_vec index mode option.
* [`config_check_tables(jsonb)`](#config_check_tablesjsonb) - Validate tables field presence.
* [`config_check_version(jsonb)`](#config_check_versionjsonb) - Validate version field presence.
* [`config_match_default()`](#config_match_default) - Generate default options for match index.
* [`count_encrypted_with_active_config(TEXT, TEXT)`](#count_encrypted_with_active_configtext-text) - Count rows encrypted with active configuration.
* [`create_encrypted_columns()`](#create_encrypted_columns) - Create encrypted columns for initial encryption.
* [`diff_config(JSONB, JSONB)`](#diff_configjsonb-jsonb) - Compare two configurations and find differences.
* [`eql_v2_configuration()`](#eql_v2_configuration) - Unique pending configuration constraint.
* [`has_bloom_filter()`](#has_bloom_filter) - Check if JSONB payload contains Bloom filter index term.
* [`has_hmac_256(ste_vec_entry)`](#has_hmac_256ste_vec_entry) - Check if a ste\_vec entry contains an HMAC-SHA256 index term.
* [`has_hmac_256(jsonb)`](#has_hmac_256jsonb) - Check if JSONB payload contains HMAC-SHA256 index term.
* [`has_ore_block_u64_8_256()`](#has_ore_block_u64_8_256) - Check if JSONB payload contains ORE block index term.
* [`has_ore_cllw(ste_vec_entry)`](#has_ore_cllwste_vec_entry) - Check if a ste\_vec entry contains a CLLW ORE index term.
* [`has_ore_cllw(jsonb)`](#has_ore_cllwjsonb) - Check if a raw jsonb value contains a CLLW ORE index term.
* [`hmac_256(eql_v2_encrypted)`](#hmac_256eql_v2_encrypted) - Extract HMAC-SHA256 index term from encrypted column value.
* [`ilike(eql_v2_encrypted, eql_v2_encrypted)`](#ilikeeql_v2_encrypted-eql_v2_encrypted) - Case-insensitive pattern matching helper.
* [`is_ste_vec_array(jsonb)`](#is_ste_vec_arrayjsonb) - Check if JSONB payload is marked as an STE vector array.
* [`is_ste_vec_array(eql_v2_encrypted)`](#is_ste_vec_arrayeql_v2_encrypted) - Check if encrypted column value is marked as an STE vector array.
* [`is_ste_vec_value(jsonb)`](#is_ste_vec_valuejsonb) - Check if JSONB payload is a single-element STE vector.
* [`jsonb_array(jsonb)`](#jsonb_arrayjsonb) - Extract deterministic fields as array for GIN indexing.
* [`jsonb_array_elements(jsonb)`](#jsonb_array_elementsjsonb) - Extract elements from encrypted JSONB array.
* [`jsonb_array_elements_text(jsonb)`](#jsonb_array_elements_textjsonb) - Extract encrypted array elements as ciphertext.
* [`jsonb_array_from_array_elements(jsonb)`](#jsonb_array_from_array_elementsjsonb) - Extract full encrypted JSONB elements as array.
* [`jsonb_array_length(jsonb)`](#jsonb_array_lengthjsonb) - Get length of encrypted JSONB array.
* [`jsonb_array_to_bytea_array(jsonb)`](#jsonb_array_to_bytea_arrayjsonb) - Convert JSONB hex array to bytea array.
* [`jsonb_contained_by(eql_v2_encrypted, eql_v2_encrypted)`](#jsonb_contained_byeql_v2_encrypted-eql_v2_encrypted) - GIN-indexable JSONB "is contained by" check.
* [`jsonb_contains(eql_v2_encrypted, jsonb)`](#jsonb_containseql_v2_encrypted-jsonb) - GIN-indexable JSONB containment check (encrypted, jsonb)
* [`jsonb_path_exists(jsonb, text)`](#jsonb_path_existsjsonb-text) - Check if selector path exists in encrypted JSONB.
* [`jsonb_path_query(eql_v2_encrypted, eql_v2_encrypted)`](#jsonb_path_queryeql_v2_encrypted-eql_v2_encrypted) - Query encrypted JSONB with encrypted selector.
* [`jsonb_path_query_first(eql_v2_encrypted, eql_v2_encrypted)`](#jsonb_path_query_firsteql_v2_encrypted-eql_v2_encrypted) - Get first element matching selector.
* [`log(text, text)`](#logtext-text) - Log message with context.
* [`log(text)`](#logtext) - Log message for debugging.
* [`lt(eql_v2.ste_vec_entry, eql_v2.ste_vec_entry)`](#lteql_v2ste_vec_entry-eql_v2ste_vec_entry) - Less-than backing function for eql\_v2.ste\_vec\_entry
* [`neq(eql_v2.ste_vec_entry, eql_v2.ste_vec_entry)`](#neqeql_v2ste_vec_entry-eql_v2ste_vec_entry) - Inequality backing function for eql\_v2.ste\_vec\_entry
* [`ore_block_u64_8_256(jsonb)`](#ore_block_u64_8_256jsonb) - Extract ORE block index term from JSONB payload.
* [`ore_block_u64_8_256_gt(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#ore_block_u64_8_256_gteql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256) - Greater than operator for ORE block types.
* [`ore_block_u64_8_256_gte(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#ore_block_u64_8_256_gteeql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256) - Greater than or equal operator for ORE block types.
* [`ore_block_u64_8_256_lt(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#ore_block_u64_8_256_lteql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256) - Less than operator for ORE block types.
* [`ore_block_u64_8_256_lte(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#ore_block_u64_8_256_lteeql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256) - Less than or equal operator for ORE block types.
* [`ore_block_u64_8_256_neq(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#ore_block_u64_8_256_neqeql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256) - Not equal operator for ORE block types.
* [`ore_cllw(jsonb)`](#ore_cllwjsonb) - Extract CLLW ORE index term from raw jsonb (RHS parameter helper)
* [`ore_cllw_gt(eql_v2.ore_cllw, eql_v2.ore_cllw)`](#ore_cllw_gteql_v2ore_cllw-eql_v2ore_cllw) - Greater-than operator backing function for eql\_v2.ore\_cllw
* [`ore_cllw_gte(eql_v2.ore_cllw, eql_v2.ore_cllw)`](#ore_cllw_gteeql_v2ore_cllw-eql_v2ore_cllw) - Greater-than-or-equal operator backing function for eql\_v2.ore\_cllw
* [`ore_cllw_lt(eql_v2.ore_cllw, eql_v2.ore_cllw)`](#ore_cllw_lteql_v2ore_cllw-eql_v2ore_cllw) - Less-than operator backing function for eql\_v2.ore\_cllw
* [`ore_cllw_lte(eql_v2.ore_cllw, eql_v2.ore_cllw)`](#ore_cllw_lteeql_v2ore_cllw-eql_v2ore_cllw) - Less-than-or-equal operator backing function for eql\_v2.ore\_cllw
* [`ore_cllw_neq(eql_v2.ore_cllw, eql_v2.ore_cllw)`](#ore_cllw_neqeql_v2ore_cllw-eql_v2ore_cllw) - Inequality operator backing function for eql\_v2.ore\_cllw
* [`ready_for_encryption()`](#ready_for_encryption) - Check if database is ready for encryption.
* [`reload_config()`](#reload_config) - Reload configuration from CipherStash Proxy.
* [`rename_encrypted_columns()`](#rename_encrypted_columns) - Finalize initial encryption by renaming columns.
* [`select_pending_columns()`](#select_pending_columns) - Get columns with pending configuration changes.
* [`select_target_columns()`](#select_target_columns) - Map pending columns to their encrypted target columns.
* [`selector(ste_vec_entry)`](#selectorste_vec_entry) - Extract selector value from a ste\_vec entry.
* [`selector(jsonb)`](#selectorjsonb) - Extract selector value from JSONB payload.
* [`ste_vec(eql_v2_encrypted)`](#ste_veceql_v2_encrypted) - Extract STE vector index from encrypted column value.
* [`ste_vec_contains(eql_v2_encrypted, eql_v2_encrypted)`](#ste_vec_containseql_v2_encrypted-eql_v2_encrypted) - Check if encrypted value 'a' contains all elements of encrypted value 'b'.
* [`ste_vec_contains(public.eql_v2_encrypted, eql_v2_encrypted)`](#ste_vec_containspubliceql_v2_encrypted-eql_v2_encrypted) - Check if STE vector array contains a specific encrypted element.
* [`ste_vec_contains(eql_v2_encrypted, eql_v2_encrypted)`](#ste_vec_containseql_v2_encrypted-eql_v2_encrypted-1) - Check if encrypted value 'a' contains all elements of encrypted value 'b'.
* [`to_jsonb(eql_v2_encrypted)`](#to_jsonbeql_v2_encrypted) - Convert encrypted type to JSONB.
* [`to_ste_vec_value(jsonb)`](#to_ste_vec_valuejsonb) - Convert single-element STE vector to regular encrypted value.
Private Functions [#private-functions]
* [`_encrypted_check_c(jsonb)`](#_encrypted_check_cjsonb) - Validate ciphertext field in encrypted payload.
* [`_encrypted_check_i_ct(jsonb)`](#_encrypted_check_i_ctjsonb) - Validate table and column fields in ident.
* [`_encrypted_check_v(jsonb)`](#_encrypted_check_vjsonb) - Validate version field in encrypted payload.
* [`_first_grouped_value()`](#_first_grouped_value) - State transition function for grouped\_value aggregate.
* [`_selector(eql_v2_encrypted)`](#_selectoreql_v2_encrypted) - Extract selector value from encrypted column value.
***
Functions [#functions-1]
->(eql_v2_encrypted, eql_v2_encrypted) [#-eql_v2_encrypted-eql_v2_encrypted]
-> operator with encrypted selector
Convenience overload: extracts the selector text from an encrypted selector payload and delegates to the (text) form. Inlinable.
Parameters [#parameters]
| Name | Type | Description |
| ---------- | ------------------ | -------------------------- |
| `e` | `eql_v2_encrypted` | Encrypted JSONB data |
| `selector` | `eql_v2_encrypted` | Encrypted selector payload |
Returns [#returns]
**Type:** `eql_v2.ste_vec_entry`
text Encrypted value at selector, implicitly cast from eql\_v2\_encrypted
Variants [#variants]
* `->>(eql_v2_encrypted, text)`
***
->>() [#-]
->> operator with encrypted selector
-> operator with encrypted selector
Parameters [#parameters-1]
| Name | Type | Description |
| ---------- | ---- | ------------------------ |
| `e` | | Encrypted JSONB data |
| `selector` | | Encrypted field selector |
Returns [#returns-1]
**Type:** `text`
text Encrypted value at selector, implicitly cast from eql\_v2\_encrypted
Variants [#variants-1]
* `->>(eql_v2_encrypted, text)`
***
>(eql_v2_encrypted, jsonb) [#eql_v2_encrypted-jsonb]
> operator for encrypted value and JSONB
> operator for JSONB and encrypted value
Parameters [#parameters-2]
| Name | Type | Description |
| ---- | ------------------ | ------------------------------ |
| `a` | `eql_v2_encrypted` | Left operand (encrypted value) |
| `b` | `jsonb` | Right operand |
Returns [#returns-2]
**Type:** `boolean`
Boolean True if a > b
Variants [#variants-2]
* `>(eql_v2_encrypted, eql_v2_encrypted)`
***
>(eql_v2_encrypted, jsonb) [#eql_v2_encrypted-jsonb-1]
> operator for encrypted value and JSONB
> operator for JSONB and encrypted value
Parameters [#parameters-3]
| Name | Type | Description |
| ---- | ------------------ | ------------------------------ |
| `a` | `eql_v2_encrypted` | Left operand (encrypted value) |
| `b` | `jsonb` | Right operand |
Returns [#returns-3]
**Type:** `boolean`
Boolean True if a > b
Variants [#variants-3]
* `>(eql_v2_encrypted, eql_v2_encrypted)`
***
bloom_filter(eql_v2_encrypted) [#bloom_filtereql_v2_encrypted]
Extract Bloom filter index term from encrypted column value.
Extracts the Bloom filter from an encrypted column value by accessing its underlying JSONB data field.
Parameters [#parameters-4]
| Name | Type | Description |
| ----- | ------------------ | ------------ |
| `val` | `eql_v2_encrypted` | column value |
Returns [#returns-4]
**Type:** `eql_v2.bloom_filter`
eql\_v2.bloom\_filter Bloom filter as smallint array
Variants [#variants-4]
* `bloom_filter(jsonb)`
***
check_encrypted(eql_v2_encrypted) [#check_encryptedeql_v2_encrypted]
Validate encrypted composite type structure.
Validates an eql\_v2\_encrypted composite type by checking its underlying JSONB payload. Delegates to eql\_v2.check\_encrypted(jsonb).
Parameters [#parameters-5]
| Name | Type | Description |
| ----- | ------------------ | ----------------- |
| `val` | `eql_v2_encrypted` | value to validate |
Returns [#returns-5]
**Type:** `BOOLEAN`
Boolean True if structure is valid
Exceptions [#exceptions]
* if any required field is missing or invalid
Variants [#variants-5]
* `check_encrypted(jsonb)`
***
compare(eql_v2.ste_vec_entry, eql_v2.ste_vec_entry) [#compareeql_v2ste_vec_entry-eql_v2ste_vec_entry]
Three-way ordering on eql\_v2.ste\_vec\_entry
CLLW ORE three-way comparator on ste-vec entries. Returns -1 / 0 / 1 by extracting the oc term from each entry and delegating to eql\_v2.compare\_ore\_cllw\_term. Use this when you need an int ordering out of two extracted ste-vec entries — for the boolean-form operators (\< / \<= / > / >=) on the same pair, see .
Note: the caller is responsible for extracting an eql\_v2.ste\_vec\_entry first; the (eql\_v2\_encrypted, text) form would be a natural extension but is deliberately not added here so that callers stay aware of the two-step shape (extract via ->, then compare).
Parameters [#parameters-6]
| Name | Type | Description |
| ---- | ---------------------- | ------------ |
| `a` | `eql_v2.ste_vec_entry` | First entry |
| `b` | `eql_v2.ste_vec_entry` | Second entry |
Returns [#returns-6]
**Type:** `integer`
integer -1, 0, or 1
Exceptions [#exceptions-1]
* when either entry lacks an oc term
Variants [#variants-6]
* src/operators/ste\_vec\_entry.sql
***
compare_ore_block_u64_8_256_term(eql_v2.ore_block_u64_8_256_term, eql_v2.ore_block_u64_8_256_term) [#compare_ore_block_u64_8_256_termeql_v2ore_block_u64_8_256_term-eql_v2ore_block_u64_8_256_term]
Compare two ORE block terms using cryptographic comparison.
Parameters [#parameters-7]
| Name | Type | Description |
| ---- | --------------------------------- | ----------- |
| `a` | `eql_v2.ore_block_u64_8_256_term` | |
| `b` | `eql_v2.ore_block_u64_8_256_term` | |
***
compare_ore_block_u64_8_256_terms(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#compare_ore_block_u64_8_256_termseql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256]
Compare ORE block composite types.
Parameters [#parameters-8]
| Name | Type | Description |
| ---- | ---------------------------- | ----------- |
| `a` | `eql_v2.ore_block_u64_8_256` | |
| `b` | `eql_v2.ore_block_u64_8_256` | |
***
compare_ore_block_u64_8_256_terms(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#compare_ore_block_u64_8_256_termseql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256-1]
Compare ORE block composite types.
Parameters [#parameters-9]
| Name | Type | Description |
| ---- | ---------------------------- | ----------- |
| `a` | `eql_v2.ore_block_u64_8_256` | |
| `b` | `eql_v2.ore_block_u64_8_256` | |
***
compare_ore_block_u64_8_256_terms(eql_v2.ore_block_u64_8_256_term, eql_v2.ore_block_u64_8_256_term) [#compare_ore_block_u64_8_256_termseql_v2ore_block_u64_8_256_term-eql_v2ore_block_u64_8_256_term]
Compare arrays of ORE block terms recursively.
Parameters [#parameters-10]
| Name | Type | Description |
| ---- | --------------------------------- | ----------- |
| `a` | `eql_v2.ore_block_u64_8_256_term` | |
| `b` | `eql_v2.ore_block_u64_8_256_term` | |
***
compare_ore_cllw_term(eql_v2.ore_cllw, eql_v2.ore_cllw) [#compare_ore_cllw_termeql_v2ore_cllw-eql_v2ore_cllw]
Variable-length CLLW ORE term comparison.
Parameters [#parameters-11]
| Name | Type | Description |
| ---- | ----------------- | ----------- |
| `a` | `eql_v2.ore_cllw` | First term |
| `b` | `eql_v2.ore_cllw` | Second term |
Returns [#returns-7]
**Type:** `int`
Integer -1, 0, or 1; NULL if either composite is NULL
Exceptions [#exceptions-2]
* if either composite has a NULL bytes field
Variants [#variants-7]
* eql\_v2.compare\_ore\_cllw
***
compare_ore_cllw_term_bytes(bytea, bytea) [#compare_ore_cllw_term_bytesbytea-bytea]
CLLW per-byte comparison helper.
Parameters [#parameters-12]
| Name | Type | Description |
| ---- | ------- | ----------- |
| `a` | `bytea` | |
| `b` | `bytea` | |
***
config_add_cast(text, text, text, jsonb) [#config_add_casttext-text-text-jsonb]
Set cast type for column in configuration.
Parameters [#parameters-13]
| Name | Type | Description |
| ------------- | ------- | ----------- |
| `table_name` | `text` | |
| `column_name` | `text` | |
| `cast_as` | `text` | |
| `config` | `jsonb` | |
***
config_add_column(text, text, jsonb) [#config_add_columntext-text-jsonb]
Add column to table configuration if not present.
Parameters [#parameters-14]
| Name | Type | Description |
| ------------- | ------- | ----------- |
| `table_name` | `text` | |
| `column_name` | `text` | |
| `config` | `jsonb` | |
***
config_add_index(text, text, text, jsonb, jsonb) [#config_add_indextext-text-text-jsonb-jsonb]
Add search index to column configuration.
Parameters [#parameters-15]
| Name | Type | Description |
| ------------- | ------- | ----------- |
| `table_name` | `text` | |
| `column_name` | `text` | |
| `index_name` | `text` | |
| `opts` | `jsonb` | |
| `config` | `jsonb` | |
***
config_add_table(text, jsonb) [#config_add_tabletext-jsonb]
Add table to configuration if not present.
Parameters [#parameters-16]
| Name | Type | Description |
| ------------ | ------- | ----------- |
| `table_name` | `text` | |
| `config` | `jsonb` | |
***
config_check_cast(jsonb) [#config_check_castjsonb]
Validate cast types in configuration.
Parameters [#parameters-17]
| Name | Type | Description |
| ----- | ------- | ----------- |
| `val` | `jsonb` | |
***
config_check_indexes(jsonb) [#config_check_indexesjsonb]
Validate index types in configuration.
Parameters [#parameters-18]
| Name | Type | Description |
| ----- | ------- | ----------- |
| `val` | `jsonb` | |
***
config_check_ste_vec_mode(jsonb) [#config_check_ste_vec_modejsonb]
Validate ste\_vec index mode option.
Parameters [#parameters-19]
| Name | Type | Description |
| ----- | ------- | ----------- |
| `val` | `jsonb` | |
***
config_check_tables(jsonb) [#config_check_tablesjsonb]
Validate tables field presence.
Parameters [#parameters-20]
| Name | Type | Description |
| ----- | ------- | ----------- |
| `val` | `jsonb` | |
***
config_check_version(jsonb) [#config_check_versionjsonb]
Validate version field presence.
Parameters [#parameters-21]
| Name | Type | Description |
| ----- | ------- | ----------- |
| `val` | `jsonb` | |
***
config_match_default() [#config_match_default]
Generate default options for match index.
***
count_encrypted_with_active_config(TEXT, TEXT) [#count_encrypted_with_active_configtext-text]
Count rows encrypted with active configuration.
Parameters [#parameters-22]
| Name | Type | Description |
| ------------- | ------ | ----------- |
| `table_name` | `TEXT` | |
| `column_name` | `TEXT` | |
***
create_encrypted_columns() [#create_encrypted_columns]
Create encrypted columns for initial encryption.
For each plaintext column with pending configuration that lacks an encrypted target column, creates a new column '\{column\_name}\_encrypted' of type eql\_v2\_encrypted. This prepares the database schema for initial encryption.
Returns [#returns-8]
**Type:** `TABLE(table_name`
TABLE(table\_name text, column\_name text) Created encrypted columns
Note [#note]
Only creates columns that don't already exist
⚠️ Warning [#️-warning]
Executes dynamic DDL (ALTER TABLE ADD COLUMN) - modifies database schema
Variants [#variants-8]
* eql\_v2.rename\_encrypted\_columns
***
diff_config(JSONB, JSONB) [#diff_configjsonb-jsonb]
Compare two configurations and find differences.
Parameters [#parameters-23]
| Name | Type | Description |
| ---- | ------- | ----------- |
| `a` | `JSONB` | |
| `b` | `JSONB` | |
***
eql_v2_configuration() [#eql_v2_configuration]
Unique pending configuration constraint.
Unique encrypting configuration constraint.
Parameters [#parameters-24]
| Name | Type | Description |
| ------- | ---- | ----------- |
| `state` | | |
Note [#note-1]
Only one configuration can be 'encrypting' at once
***
has_bloom_filter() [#has_bloom_filter]
Check if JSONB payload contains Bloom filter index term.
Check if encrypted column value contains Bloom filter index term.
Tests whether the encrypted data payload includes a 'bf' field, indicating a Bloom filter is available for pattern-match queries.
Parameters [#parameters-25]
| Name | Type | Description |
| ----- | ---- | ----------- |
| `val` | | |
Returns [#returns-9]
**Type:** `boolean`
Boolean True if Bloom filter is present
Variants [#variants-9]
* `has_bloom_filter(jsonb)`
***
has_hmac_256(ste_vec_entry) [#has_hmac_256ste_vec_entry]
Check if a ste\_vec entry contains an HMAC-SHA256 index term.
Parameters [#parameters-26]
| Name | Type | Description |
| --------------- | --------------- | ----------- |
| `entry eql_v2.` | `ste_vec_entry` | |
Returns [#returns-10]
**Type:** `boolean`
Boolean True if hm field is present and non-null
***
has_hmac_256(jsonb) [#has_hmac_256jsonb]
Check if JSONB payload contains HMAC-SHA256 index term.
Check if encrypted column value contains HMAC-SHA256 index term.
Tests whether the encrypted data payload includes an 'hm' field, indicating an HMAC-SHA256 hash is available for exact-match queries.
Parameters [#parameters-27]
| Name | Type | Description |
| ----- | ------- | --------------------- |
| `val` | `jsonb` | encrypted EQL payload |
Returns [#returns-11]
**Type:** `boolean`
Boolean True if HMAC-SHA256 hash is present
Variants [#variants-10]
* [`has_hmac_256(jsonb)`](#has_hmac_256jsonb)
***
has_ore_block_u64_8_256() [#has_ore_block_u64_8_256]
Check if JSONB payload contains ORE block index term.
Check if encrypted column value contains ORE block index term.
Tests whether the encrypted data payload includes an 'ob' field, indicating an ORE block is available for range queries.
Parameters [#parameters-28]
| Name | Type | Description |
| ----- | ---- | ----------- |
| `val` | | |
Returns [#returns-12]
**Type:** `boolean`
Boolean True if ORE block is present
Variants [#variants-11]
* `has_ore_block_u64_8_256(jsonb)`
***
has_ore_cllw(ste_vec_entry) [#has_ore_cllwste_vec_entry]
Check if a ste\_vec entry contains a CLLW ORE index term.
Tests whether the entry includes an oc field. Inlinable.
Parameters [#parameters-29]
| Name | Type | Description |
| --------------- | --------------- | ----------- |
| `entry eql_v2.` | `ste_vec_entry` | |
Returns [#returns-13]
**Type:** `boolean`
Boolean True if oc field is present and non-null
Variants [#variants-12]
* eql\_v2.ore\_cllw
***
has_ore_cllw(jsonb) [#has_ore_cllwjsonb]
Check if a raw jsonb value contains a CLLW ORE index term.
Companion to eql\_v2.has\_ore\_cllw(ste\_vec\_entry) for raw jsonb inputs.
Parameters [#parameters-30]
| Name | Type | Description |
| ----- | ------- | ------------------------------------ |
| `val` | `jsonb` | An object that may carry an oc field |
Returns [#returns-14]
**Type:** `boolean`
Boolean True if oc field is present and non-null
***
hmac_256(eql_v2_encrypted) [#hmac_256eql_v2_encrypted]
Extract HMAC-SHA256 index term from encrypted column value.
Extracts the HMAC-SHA256 hash from an encrypted column value. Inlinable single-statement SQL — see the jsonb overload for the rationale.
Parameters [#parameters-31]
| Name | Type | Description |
| ----- | ------------------ | ------------ |
| `val` | `eql_v2_encrypted` | column value |
Returns [#returns-15]
**Type:** `eql_v2.hmac_256`
eql\_v2.hmac\_256 HMAC-SHA256 hash value, or NULL when hm is absent
Variants [#variants-13]
* `hmac_256(jsonb)`
***
ilike(eql_v2_encrypted, eql_v2_encrypted) [#ilikeeql_v2_encrypted-eql_v2_encrypted]
Case-insensitive pattern matching helper.
Parameters [#parameters-32]
| Name | Type | Description |
| ---- | ------------------ | ----------- |
| `a` | `eql_v2_encrypted` | |
| `b` | `eql_v2_encrypted` | |
***
is_ste_vec_array(jsonb) [#is_ste_vec_arrayjsonb]
Check if JSONB payload is marked as an STE vector array.
Check if encrypted column value is marked as an STE vector array.
Tests whether the encrypted data payload has the 'a' (array) flag set to true, indicating it represents an array for STE vector operations.
Parameters [#parameters-33]
| Name | Type | Description |
| ----- | ------- | --------------------- |
| `val` | `jsonb` | encrypted EQL payload |
Returns [#returns-16]
**Type:** `boolean`
Boolean True if value is marked as an STE vector array
Variants [#variants-14]
* [`is_ste_vec_array(jsonb)`](#is_ste_vec_arrayjsonb)
***
is_ste_vec_array(eql_v2_encrypted) [#is_ste_vec_arrayeql_v2_encrypted]
Check if encrypted column value is marked as an STE vector array.
Tests whether an encrypted column value has the array flag set by checking its underlying JSONB data field.
Tests whether the encrypted data payload has the 'a' (array) flag set to true, indicating it represents an array for STE vector operations.
Parameters [#parameters-34]
| Name | Type | Description |
| ----- | ------------------ | ------------ |
| `val` | `eql_v2_encrypted` | column value |
Returns [#returns-17]
**Type:** `BEGIN IF NOT eql_v2`
Boolean True if value is marked as an STE vector array
Variants [#variants-15]
* [`is_ste_vec_array(jsonb)`](#is_ste_vec_arrayjsonb)
***
is_ste_vec_value(jsonb) [#is_ste_vec_valuejsonb]
Check if JSONB payload is a single-element STE vector.
Check if encrypted column value is a single-element STE vector.
Tests whether the encrypted data payload contains an 'sv' field with exactly one element. Single-element STE vectors can be treated as regular encrypted values.
Parameters [#parameters-35]
| Name | Type | Description |
| ----- | ------- | --------------------- |
| `val` | `jsonb` | encrypted EQL payload |
Returns [#returns-18]
**Type:** `boolean`
Boolean True if value is a single-element STE vector
Variants [#variants-16]
* [`is_ste_vec_value(jsonb)`](#is_ste_vec_valuejsonb)
***
jsonb_array(jsonb) [#jsonb_arrayjsonb]
Extract deterministic fields as array for GIN indexing.
Extract deterministic fields as array from encrypted column.
Extracts only deterministic search term fields (s, hm, oc, op) from each STE vector element. Excludes non-deterministic ciphertext for correct containment comparison using PostgreSQL's native @> operator.
Field set: selector (s), HMAC equality (hm), ORE CLLW (oc, Standard-mode), OPE CLLW (op, Compat-mode). The pre-2.3 fields (b3 / ocf / ocv / opf / opv) are no longer emitted — see U-004 and U-006 in docs/upgrading/v2.3.md.
Parameters [#parameters-36]
| Name | Type | Description |
| ----- | ------- | -------------------------------- |
| `val` | `jsonb` | containing encrypted EQL payload |
Returns [#returns-19]
**Type:** `jsonb[]`
jsonb\[] Array of JSONB elements with only deterministic fields
Note [#note-2]
Use this for GIN indexes and containment queries
Variants [#variants-17]
* [`jsonb_array(jsonb)`](#jsonb_arrayjsonb)
***
jsonb_array_elements(jsonb) [#jsonb_array_elementsjsonb]
Extract elements from encrypted JSONB array.
Returns each element of an encrypted JSONB array as a separate row. Each element is returned as an eql\_v2\_encrypted value with metadata preserved from the parent array.
Parameters [#parameters-37]
| Name | Type | Description |
| ----- | ------- | ----------------------------------- |
| `val` | `jsonb` | JSONB payload representing an array |
Returns [#returns-20]
**Type:** `SETOF`
SETOF eql\_v2\_encrypted One row per array element
Note [#note-3]
Each element inherits metadata (version, ident) from parent
Exceptions [#exceptions-3]
* if value is not an array (missing 'a' flag)
Variants [#variants-18]
* eql\_v2.jsonb\_array\_elements\_text
***
jsonb_array_elements_text(jsonb) [#jsonb_array_elements_textjsonb]
Extract encrypted array elements as ciphertext.
Returns each element of an encrypted JSONB array as its raw ciphertext value (text representation). Unlike jsonb\_array\_elements, this returns only the ciphertext 'c' field without metadata.
Parameters [#parameters-38]
| Name | Type | Description |
| ----- | ------- | ----------------------------------- |
| `val` | `jsonb` | JSONB payload representing an array |
Returns [#returns-21]
**Type:** `SETOF`
SETOF text One ciphertext string per array element
Note [#note-4]
Returns ciphertext only, not full encrypted structure
Exceptions [#exceptions-4]
* if value is not an array (missing 'a' flag)
Variants [#variants-19]
* eql\_v2.jsonb\_array\_elements
***
jsonb_array_from_array_elements(jsonb) [#jsonb_array_from_array_elementsjsonb]
Extract full encrypted JSONB elements as array.
Extract full encrypted JSONB elements as array from encrypted column.
Extracts all JSONB elements from the STE vector including non-deterministic fields. Use jsonb\_array() instead for GIN indexing and containment queries.
Parameters [#parameters-39]
| Name | Type | Description |
| ----- | ------- | -------------------------------- |
| `val` | `jsonb` | containing encrypted EQL payload |
Returns [#returns-22]
**Type:** `jsonb[]`
jsonb\[] Array of full JSONB elements
Variants [#variants-20]
* [`jsonb_array_from_array_elements(jsonb)`](#jsonb_array_from_array_elementsjsonb)
***
jsonb_array_length(jsonb) [#jsonb_array_lengthjsonb]
Get length of encrypted JSONB array.
Returns the number of elements in an encrypted JSONB array by counting elements in the STE vector ('sv'). The encrypted value must have the array flag ('a') set to true.
Parameters [#parameters-40]
| Name | Type | Description |
| ----- | ------- | ----------------------------------- |
| `val` | `jsonb` | JSONB payload representing an array |
Returns [#returns-23]
**Type:** `integer`
integer Number of elements in the array
Note [#note-5]
Array flag 'a' must be present and set to true value
Exceptions [#exceptions-5]
* 'cannot get array length of a non-array' if 'a' flag is missing or not true
Variants [#variants-21]
* eql\_v2.jsonb\_array\_elements
***
jsonb_array_to_bytea_array(jsonb) [#jsonb_array_to_bytea_arrayjsonb]
Convert JSONB hex array to bytea array.
Parameters [#parameters-41]
| Name | Type | Description |
| ----- | ------- | ----------- |
| `val` | `jsonb` | |
***
jsonb_contained_by(eql_v2_encrypted, eql_v2_encrypted) [#jsonb_contained_byeql_v2_encrypted-eql_v2_encrypted]
GIN-indexable JSONB "is contained by" check.
GIN-indexable JSONB "is contained by" check (jsonb, encrypted)
GIN-indexable JSONB "is contained by" check (encrypted, jsonb)
Checks if all JSONB elements from 'a' are contained in 'b'. Uses jsonb\[] arrays internally for native PostgreSQL GIN index support.
Parameters [#parameters-42]
| Name | Type | Description |
| ---- | ------------------ | ----------------------------------------- |
| `a` | `eql_v2_encrypted` | Value to check (typically a table column) |
| `b` | `eql_v2_encrypted` | Container value |
Returns [#returns-24]
**Type:** `boolean`
Boolean True if all elements of a are contained in b
Variants [#variants-22]
* [`jsonb_contained_by(eql_v2_encrypted, eql_v2_encrypted)`](#jsonb_contained_byeql_v2_encrypted-eql_v2_encrypted)
***
jsonb_contains(eql_v2_encrypted, jsonb) [#jsonb_containseql_v2_encrypted-jsonb]
GIN-indexable JSONB containment check (encrypted, jsonb)
GIN-indexable JSONB containment check (jsonb, encrypted)
Checks if encrypted value 'a' contains all JSONB elements from jsonb value 'b'. Uses jsonb\[] arrays internally for native PostgreSQL GIN index support.
Parameters [#parameters-43]
| Name | Type | Description |
| ---- | ------------------ | ------------------------------------------ |
| `a` | `eql_v2_encrypted` | Container value (typically a table column) |
| `b` | `jsonb` | JSONB value to search for |
Returns [#returns-25]
**Type:** `boolean`
Boolean True if a contains all elements of b
Variants [#variants-23]
* `jsonb_contains(eql_v2_encrypted, eql_v2_encrypted)`
***
jsonb_path_exists(jsonb, text) [#jsonb_path_existsjsonb-text]
Check if selector path exists in encrypted JSONB.
Check existence with encrypted selector.
Tests whether any encrypted elements match the given selector path. More efficient than jsonb\_path\_query when only existence check is needed.
Parameters [#parameters-44]
| Name | Type | Description |
| ---------- | ------- | ------------------------------ |
| `val` | `jsonb` | Encrypted JSONB value to check |
| `selector` | `text` | Encrypted selector to test |
Returns [#returns-26]
**Type:** `boolean`
boolean True if path exists
Variants [#variants-24]
* [`jsonb_path_exists(jsonb, text)`](#jsonb_path_existsjsonb-text)
***
jsonb_path_query(eql_v2_encrypted, eql_v2_encrypted) [#jsonb_path_queryeql_v2_encrypted-eql_v2_encrypted]
Query encrypted JSONB with encrypted selector.
Overload that accepts encrypted selector and extracts its plaintext value before delegating to main jsonb\_path\_query implementation.
Parameters [#parameters-45]
| Name | Type | Description |
| ---------- | ------------------ | ----------------------------------- |
| `val` | `eql_v2_encrypted` | Encrypted JSONB value to query |
| `selector` | `eql_v2_encrypted` | Encrypted selector to match against |
Returns [#returns-27]
**Type:** `SETOF`
SETOF eql\_v2\_encrypted Matching encrypted elements
Variants [#variants-25]
* `jsonb_path_query(jsonb, text)`
***
jsonb_path_query_first(eql_v2_encrypted, eql_v2_encrypted) [#jsonb_path_query_firsteql_v2_encrypted-eql_v2_encrypted]
Get first element matching selector.
Returns only the first encrypted element matching the selector path, or NULL if no match found. More efficient than jsonb\_path\_query when only one result is needed.
Overload that accepts encrypted selector and extracts its value before querying for first match.
Parameters [#parameters-46]
| Name | Type | Description |
| ---------- | ------------------ | ------------------------------ |
| `val` | `eql_v2_encrypted` | Encrypted JSONB value to query |
| `selector` | `eql_v2_encrypted` | Encrypted selector to match |
Returns [#returns-28]
**Type:** `eql_v2_encrypted`
eql\_v2\_encrypted First matching element or NULL
Note [#note-6]
Uses LIMIT 1 internally for efficiency
Variants [#variants-26]
* `jsonb_path_query_first(jsonb, text)`
***
log(text, text) [#logtext-text]
Log message with context.
Overload of log function that includes context label for better log organization during testing.
Parameters [#parameters-47]
| Name | Type | Description |
| ----- | ------ | -------------------------------------------- |
| `ctx` | `text` | Context label (e.g., test name, module name) |
| `s` | `text` | Message to log |
Note [#note-7]
Format: "\[LOG] \{ctx} \{message}"
Variants [#variants-27]
* [`log(text)`](#logtext)
***
log(text) [#logtext]
Log message for debugging.
Convenience function to emit log messages during testing and debugging. Uses RAISE NOTICE to output messages to PostgreSQL logs.
Parameters [#parameters-48]
| Name | Type | Description |
| ---- | ------ | ----------- |
| `s` | `text` | to log |
Note [#note-8]
Primarily used in tests and development
Variants [#variants-28]
* [`log(text, text)`](#logtext-text)
***
lt(eql_v2.ste_vec_entry, eql_v2.ste_vec_entry) [#lteql_v2ste_vec_entry-eql_v2ste_vec_entry]
Less-than backing function for eql\_v2.ste\_vec\_entry
Parameters [#parameters-49]
| Name | Type | Description |
| ---- | ---------------------- | ----------- |
| `a` | `eql_v2.ste_vec_entry` | |
| `b` | `eql_v2.ste_vec_entry` | |
***
neq(eql_v2.ste_vec_entry, eql_v2.ste_vec_entry) [#neqeql_v2ste_vec_entry-eql_v2ste_vec_entry]
Inequality backing function for eql\_v2.ste\_vec\_entry
Parameters [#parameters-50]
| Name | Type | Description |
| ---- | ---------------------- | ----------- |
| `a` | `eql_v2.ste_vec_entry` | |
| `b` | `eql_v2.ste_vec_entry` | |
***
ore_block_u64_8_256(jsonb) [#ore_block_u64_8_256jsonb]
Extract ORE block index term from JSONB payload.
Extract ORE block index term from encrypted column value.
Extracts the ORE block array from the 'ob' field of an encrypted data payload. Used internally for range query comparisons.
Parameters [#parameters-51]
| Name | Type | Description |
| ----- | ------- | --------------------- |
| `val` | `jsonb` | encrypted EQL payload |
Returns [#returns-29]
**Type:** `eql_v2.ore_block_u64_8_256`
eql\_v2.ore\_block\_u64\_8\_256 ORE block index term
Exceptions [#exceptions-6]
* if 'ob' field is missing when ore index is expected
Variants [#variants-29]
* [`ore_block_u64_8_256(jsonb)`](#ore_block_u64_8_256jsonb)
***
ore_block_u64_8_256_gt(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#ore_block_u64_8_256_gteql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256]
Greater than operator for ORE block types.
Parameters [#parameters-52]
| Name | Type | Description |
| ---- | ---------------------------- | ----------- |
| `a` | `eql_v2.ore_block_u64_8_256` | |
| `b` | `eql_v2.ore_block_u64_8_256` | |
***
ore_block_u64_8_256_gte(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#ore_block_u64_8_256_gteeql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256]
Greater than or equal operator for ORE block types.
Parameters [#parameters-53]
| Name | Type | Description |
| ---- | ---------------------------- | ----------- |
| `a` | `eql_v2.ore_block_u64_8_256` | |
| `b` | `eql_v2.ore_block_u64_8_256` | |
***
ore_block_u64_8_256_lt(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#ore_block_u64_8_256_lteql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256]
Less than operator for ORE block types.
Parameters [#parameters-54]
| Name | Type | Description |
| ---- | ---------------------------- | ----------- |
| `a` | `eql_v2.ore_block_u64_8_256` | |
| `b` | `eql_v2.ore_block_u64_8_256` | |
***
ore_block_u64_8_256_lte(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#ore_block_u64_8_256_lteeql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256]
Less than or equal operator for ORE block types.
Parameters [#parameters-55]
| Name | Type | Description |
| ---- | ---------------------------- | ----------- |
| `a` | `eql_v2.ore_block_u64_8_256` | |
| `b` | `eql_v2.ore_block_u64_8_256` | |
***
ore_block_u64_8_256_neq(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#ore_block_u64_8_256_neqeql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256]
Not equal operator for ORE block types.
Parameters [#parameters-56]
| Name | Type | Description |
| ---- | ---------------------------- | ----------- |
| `a` | `eql_v2.ore_block_u64_8_256` | |
| `b` | `eql_v2.ore_block_u64_8_256` | |
***
ore_cllw(jsonb) [#ore_cllwjsonb]
Extract CLLW ORE index term from raw jsonb (RHS parameter helper)
Companion overload for eql\_v2.ore\_cllw(eql\_v2.ste\_vec\_entry) that accepts a raw jsonb value. Intended for the right-hand side of comparisons where the caller binds a literal/parameter jsonb representing a single ste\_vec entry: ... \< eql\_v2.ore\_cllw($1. The (jsonb) form skips the domain CHECK constraint so it works for ad-hoc test inputs and for the GenericComparison case in eql\_v2.compare\_ore\_cllw\_term.
Returns SQL-level NULL when the input lacks oc, matching the (ste\_vec\_entry) overload's missing-oc semantics so a WHERE ore\_cllw(col) \< ore\_cllw($1 with a malformed query needle evaluates to no rows rather than indexing a NULL-bytes composite.
Parameters [#parameters-57]
| Name | Type | Description |
| ----- | ------- | ------------------------------ |
| `val` | `jsonb` | An object carrying an oc field |
Returns [#returns-30]
**Type:** `eql_v2.ore_cllw`
eql\_v2.ore\_cllw Composite carrying the CLLW ciphertext, or NULL when the oc field is absent.
***
ore_cllw_gt(eql_v2.ore_cllw, eql_v2.ore_cllw) [#ore_cllw_gteql_v2ore_cllw-eql_v2ore_cllw]
Greater-than operator backing function for eql\_v2.ore\_cllw
Parameters [#parameters-58]
| Name | Type | Description |
| ---- | ----------------- | ----------- |
| `a` | `eql_v2.ore_cllw` | |
| `b` | `eql_v2.ore_cllw` | |
***
ore_cllw_gte(eql_v2.ore_cllw, eql_v2.ore_cllw) [#ore_cllw_gteeql_v2ore_cllw-eql_v2ore_cllw]
Greater-than-or-equal operator backing function for eql\_v2.ore\_cllw
Parameters [#parameters-59]
| Name | Type | Description |
| ---- | ----------------- | ----------- |
| `a` | `eql_v2.ore_cllw` | |
| `b` | `eql_v2.ore_cllw` | |
***
ore_cllw_lt(eql_v2.ore_cllw, eql_v2.ore_cllw) [#ore_cllw_lteql_v2ore_cllw-eql_v2ore_cllw]
Less-than operator backing function for eql\_v2.ore\_cllw
Parameters [#parameters-60]
| Name | Type | Description |
| ---- | ----------------- | ----------- |
| `a` | `eql_v2.ore_cllw` | |
| `b` | `eql_v2.ore_cllw` | |
***
ore_cllw_lte(eql_v2.ore_cllw, eql_v2.ore_cllw) [#ore_cllw_lteeql_v2ore_cllw-eql_v2ore_cllw]
Less-than-or-equal operator backing function for eql\_v2.ore\_cllw
Parameters [#parameters-61]
| Name | Type | Description |
| ---- | ----------------- | ----------- |
| `a` | `eql_v2.ore_cllw` | |
| `b` | `eql_v2.ore_cllw` | |
***
ore_cllw_neq(eql_v2.ore_cllw, eql_v2.ore_cllw) [#ore_cllw_neqeql_v2ore_cllw-eql_v2ore_cllw]
Inequality operator backing function for eql\_v2.ore\_cllw
Parameters [#parameters-62]
| Name | Type | Description |
| ---- | ----------------- | ----------- |
| `a` | `eql_v2.ore_cllw` | |
| `b` | `eql_v2.ore_cllw` | |
***
ready_for_encryption() [#ready_for_encryption]
Check if database is ready for encryption.
Verifies that all columns with pending configuration have corresponding encrypted target columns created. Returns true if encryption can proceed.
Returns [#returns-31]
**Type:** `BOOLEAN`
boolean True if all pending columns have target encrypted columns
Note [#note-9]
Returns false if any pending column lacks encrypted column
Variants [#variants-30]
* eql\_v2.create\_encrypted\_columns
***
reload_config() [#reload_config]
Reload configuration from CipherStash Proxy.
Placeholder function for reloading configuration from the CipherStash Proxy. Currently returns NULL without side effects.
Returns [#returns-32]
**Type:** `void`
Void
Note [#note-10]
This function may be used for configuration synchronization in future versions
***
rename_encrypted_columns() [#rename_encrypted_columns]
Finalize initial encryption by renaming columns.
After initial encryption completes, renames columns to complete the transition:Plaintext column '\{column\_name}' → '\{column\_name}\_plaintext'Encrypted column '\{column\_name}\_encrypted' → '\{column\_name}'
This makes the encrypted column the primary column with the original name.
Returns [#returns-33]
**Type:** `TABLE(table_name`
TABLE(table\_name text, column\_name text, target\_column text) Renamed columns
Note [#note-11]
Only renames columns where target is '\{column\_name}\_encrypted'
⚠️ Warning [#️-warning-1]
Executes dynamic DDL (ALTER TABLE RENAME COLUMN) - modifies database schema
Variants [#variants-31]
* eql\_v2.create\_encrypted\_columns
***
select_pending_columns() [#select_pending_columns]
Get columns with pending configuration changes.
Compares 'pending' and 'active' configurations to identify columns that need encryption or re-encryption. Returns columns where configuration differs.
Returns [#returns-34]
**Type:** `TABLE(table_name`
TABLE(table\_name text, column\_name text) Columns needing encryption
Note [#note-12]
Treats missing active config as empty config
Exceptions [#exceptions-7]
* if no pending configuration exists
Variants [#variants-32]
* eql\_v2.select\_target\_columns
***
select_target_columns() [#select_target_columns]
Map pending columns to their encrypted target columns.
For each column with pending configuration, identifies the corresponding encrypted column. During initial encryption, target is '\{column\_name}\_encrypted'. Returns NULL for target\_column if encrypted column doesn't exist yet.
Returns [#returns-35]
**Type:** `TABLE(table_name`
TABLE(table\_name text, column\_name text, target\_column text) Column mappings
Note [#note-13]
The LEFT JOIN checks both original and '\_encrypted' suffix variations with type verification
Variants [#variants-33]
* eql\_v2.create\_encrypted\_columns
***
selector(ste_vec_entry) [#selectorste_vec_entry]
Extract selector value from a ste\_vec entry.
Direct overload on the domain type. The DOMAIN's CHECK constraint already guarantees s is present, so this is a simple field access.
Parameters [#parameters-63]
| Name | Type | Description |
| --------------- | --------------- | ----------- |
| `entry eql_v2.` | `ste_vec_entry` | |
Returns [#returns-36]
**Type:** `text`
Text The selector value
Variants [#variants-34]
* [`selector(jsonb)`](#selectorjsonb)
***
selector(jsonb) [#selectorjsonb]
Extract selector value from JSONB payload.
Extracts the selector ('s') field from an encrypted data payload. Selectors are used to match STE vector elements during containment queries.
Parameters [#parameters-64]
| Name | Type | Description |
| ----- | ------- | --------------------- |
| `val` | `jsonb` | encrypted EQL payload |
Returns [#returns-37]
**Type:** `text`
Text The selector value
Exceptions [#exceptions-8]
* if 's' field is missing
Variants [#variants-35]
* eql\_v2.ste\_vec\_contains
***
ste_vec(eql_v2_encrypted) [#ste_veceql_v2_encrypted]
Extract STE vector index from encrypted column value.
Extracts the STE vector from an encrypted column value by accessing its underlying JSONB data field. Used for containment query operations.
Parameters [#parameters-65]
| Name | Type | Description |
| ----- | ------------------ | ------------ |
| `val` | `eql_v2_encrypted` | column value |
Returns [#returns-38]
**Type:** `public.eql_v2_encrypted[]`
eql\_v2\_encrypted\[] Array of encrypted STE vector elements
Variants [#variants-36]
* `ste_vec(jsonb)`
***
ste_vec_contains(eql_v2_encrypted, eql_v2_encrypted) [#ste_vec_containseql_v2_encrypted-eql_v2_encrypted]
Check if encrypted value 'a' contains all elements of encrypted value 'b'.
Performs STE vector containment comparison between two encrypted values. Returns true if all elements in b's STE vector are found in a's STE vector. Used internally by the > containment operator for searchable encryption.
Parameters [#parameters-66]
| Name | Type | Description |
| ---- | ------------------ | ----------------------------------------- |
| `a` | `eql_v2_encrypted` | First encrypted value (container) |
| `b` | `eql_v2_encrypted` | Second encrypted value (elements to find) |
Returns [#returns-39]
**Type:** `boolean`
Boolean True if all elements of b are contained in a
Note [#note-14]
Each element of b must match both selector and value in a
Variants [#variants-37]
* eql\_v2."@>"
***
ste_vec_contains(public.eql_v2_encrypted, eql_v2_encrypted) [#ste_vec_containspubliceql_v2_encrypted-eql_v2_encrypted]
Check if STE vector array contains a specific encrypted element.
Tests whether any element in the STE vector array 'a' contains the encrypted value 'b'. Matching requires both the selector and encrypted value to be equal. Used internally by ste\_vec\_contains(encrypted, encrypted) for array containment checks.
Parameters [#parameters-67]
| Name | Type | Description |
| ---- | ------------------------- | --------------------- |
| `a` | `public.eql_v2_encrypted` | |
| `b` | `eql_v2_encrypted` | element to search for |
Returns [#returns-40]
**Type:** `boolean`
Boolean True if b is found in any element of a
Note [#note-15]
Compares both selector and encrypted value for match
Variants [#variants-38]
* [`ste_vec_contains(eql_v2_encrypted, eql_v2_encrypted)`](#ste_vec_containseql_v2_encrypted-eql_v2_encrypted-1)
***
ste_vec_contains(eql_v2_encrypted, eql_v2_encrypted) [#ste_vec_containseql_v2_encrypted-eql_v2_encrypted-1]
Check if encrypted value 'a' contains all elements of encrypted value 'b'.
Performs STE vector containment comparison between two encrypted values. Returns true if all elements in b's STE vector are found in a's STE vector. Used internally by the > containment operator for searchable encryption.
Parameters [#parameters-68]
| Name | Type | Description |
| ---- | ------------------ | ----------------------------------------- |
| `a` | `eql_v2_encrypted` | First encrypted value (container) |
| `b` | `eql_v2_encrypted` | Second encrypted value (elements to find) |
Returns [#returns-41]
**Type:** `REQUIRE b eql_v2_encrypted RETURNS boolean LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE AS $$ SELECT eql_v2`
Boolean True if all elements of b are contained in a
Note [#note-16]
Each element of b must match both selector and value in a
Variants [#variants-39]
* eql\_v2."@>"
***
to_jsonb(eql_v2_encrypted) [#to_jsonbeql_v2_encrypted]
Convert encrypted type to JSONB.
Extracts the underlying JSONB payload from an eql\_v2\_encrypted composite type. Useful for debugging or when raw encrypted payload access is needed.
Parameters [#parameters-69]
| Name | Type | Description |
| ----------- | ------------------ | ----------- |
| `e public.` | `eql_v2_encrypted` | |
Returns [#returns-42]
**Type:** `jsonb`
jsonb Raw JSONB encrypted payload
Note [#note-17]
Returns the raw encrypted structure including ciphertext and index terms
Variants [#variants-40]
* `to_encrypted(jsonb)`
***
to_ste_vec_value(jsonb) [#to_ste_vec_valuejsonb]
Convert single-element STE vector to regular encrypted value.
Convert single-element STE vector to regular encrypted value (encrypted type)
Extracts the single element from a single-element STE vector and returns it as a regular encrypted value, preserving metadata. If the input is not a single-element STE vector, returns it unchanged.
Parameters [#parameters-70]
| Name | Type | Description |
| ----- | ------- | --------------------- |
| `val` | `jsonb` | encrypted EQL payload |
Returns [#returns-43]
**Type:** `eql_v2_encrypted`
eql\_v2\_encrypted Regular encrypted value (unwrapped if single-element STE vector)
Variants [#variants-41]
* [`to_ste_vec_value(jsonb)`](#to_ste_vec_valuejsonb)
***
Private Functions [#private-functions-1]
_encrypted_check_c(jsonb) [#_encrypted_check_cjsonb]
Validate ciphertext field in encrypted payload.
Parameters [#parameters-71]
| Name | Type | Description |
| ----- | ------- | ----------- |
| `val` | `jsonb` | |
***
_encrypted_check_i_ct(jsonb) [#_encrypted_check_i_ctjsonb]
Validate table and column fields in ident.
Parameters [#parameters-72]
| Name | Type | Description |
| ----- | ------- | ----------- |
| `val` | `jsonb` | |
***
_encrypted_check_v(jsonb) [#_encrypted_check_vjsonb]
Validate version field in encrypted payload.
Parameters [#parameters-73]
| Name | Type | Description |
| ----- | ------- | ----------- |
| `val` | `jsonb` | |
***
_first_grouped_value() [#_first_grouped_value]
State transition function for grouped\_value aggregate.
Parameters [#parameters-74]
| Name | Type | Description |
| ------- | ---- | ----------- |
| `jsonb` | | |
| `jsonb` | | |
***
_selector(eql_v2_encrypted) [#_selectoreql_v2_encrypted]
Extract selector value from encrypted column value.
Parameters [#parameters-75]
| Name | Type | Description |
| ----- | ------------------ | ----------- |
| `val` | `eql_v2_encrypted` | |
***
# Securing AI and RAG pipelines
Retrieval-Augmented Generation (RAG) pipelines commonly store sensitive documents alongside vector embeddings. Without encryption, this data is exposed at rest and during retrieval, creating a significant attack surface. CipherStash lets you encrypt sensitive content while preserving the ability to search and retrieve it.
The problem [#the-problem]
RAG architectures typically store:
* **Document chunks**: the original text, often containing PII, financial data, or confidential business information
* **Metadata**: source references, user associations, access tags
* **Vector embeddings**: numeric representations used for similarity search
If any of this data is exfiltrated from the database, the plaintext content is immediately exposed. Encryption-at-rest does not help because the data is decrypted as soon as it is queried.
Encrypting RAG context data [#encrypting-rag-context-data]
Use the Encryption SDK to encrypt sensitive fields before storing them alongside your embeddings.
Define a schema for your documents [#define-a-schema-for-your-documents]
```typescript filename="schema.ts"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
export const documents = encryptedTable("documents", {
content: encryptedColumn("content")
.freeTextSearch(),
source: encryptedColumn("source")
.equality(),
userId: encryptedColumn("user_id")
.equality(),
})
```
Encrypt before storage [#encrypt-before-storage]
```typescript filename="ingest.ts"
import { Encryption } from "@cipherstash/stack"
import { documents } from "./schema"
const client = await Encryption({ schemas: [documents] })
async function ingestDocument(doc: { content: string; source: string; userId: string; embedding: number[] }) {
const encryptedContent = await client.encrypt(doc.content, {
column: documents.content,
table: documents,
})
const encryptedSource = await client.encrypt(doc.source, {
column: documents.source,
table: documents,
})
const encryptedUserId = await client.encrypt(doc.userId, {
column: documents.userId,
table: documents,
})
if (encryptedContent.failure || encryptedSource.failure || encryptedUserId.failure) {
throw new Error("Encryption failed")
}
// Store encrypted fields alongside the vector embedding
await db.query(
`INSERT INTO documents (content, source, user_id, embedding)
VALUES ($1::jsonb, $2::jsonb, $3::jsonb, $4)`,
[encryptedContent.data, encryptedSource.data, encryptedUserId.data, JSON.stringify(doc.embedding)]
)
}
```
Decrypt retrieved context [#decrypt-retrieved-context]
After vector similarity search retrieves relevant documents, decrypt the content before passing it to the LLM:
```typescript filename="retrieve.ts"
async function retrieveContext(queryEmbedding: number[], topK: number = 5) {
// Vector similarity search returns encrypted rows
const results = await db.query(
`SELECT content, source FROM documents
ORDER BY embedding <-> $1
LIMIT $2`,
[JSON.stringify(queryEmbedding), topK]
)
// Decrypt the content for each result
const decryptedDocs = await Promise.all(
results.rows.map(async (row) => {
const content = await client.decrypt(row.content)
const source = await client.decrypt(row.source)
return {
content: content.failure ? null : content.data,
source: source.failure ? null : source.data,
}
})
)
return decryptedDocs.filter((doc) => doc.content !== null)
}
```
Searchable encrypted retrieval [#searchable-encrypted-retrieval]
When you need to filter documents by metadata before or alongside vector search, use [searchable encryption](/stack/cipherstash/encryption/searchable-encryption) with EQL:
```sql
-- Find documents for a specific user using encrypted equality search
SELECT content, source, embedding
FROM documents
WHERE eql_v2.eq(user_id, $1)
ORDER BY embedding <-> $2
LIMIT 10;
```
This combines encrypted metadata filtering with vector similarity, without ever decrypting the metadata in the database.
Benefits for AI pipelines [#benefits-for-ai-pipelines]
* **Sensitive context stays encrypted**: document chunks containing PII or confidential data are never stored in plaintext
* **Compliance-ready**: encrypted storage meets GDPR, HIPAA, and SOC2 requirements for data protection
* **Selective decryption**: only decrypt what the LLM needs, reducing exposure surface
* **Audit trail**: track who retrieved which documents and when using [identity-aware encryption](/stack/cipherstash/encryption/identity)
# Regulatory compliance
CipherStash's searchable encryption provides the technical controls needed to satisfy regulatory requirements while maintaining full query capabilities. This page covers common compliance patterns using the Encryption SDK and EQL.
Encrypted uniqueness constraints [#encrypted-uniqueness-constraints]
Many compliance frameworks require that sensitive identifiers (email, SSN, tax ID) are unique across your dataset. With CipherStash, you can enforce uniqueness on encrypted data using HMAC-based indexes.
Create a unique index on encrypted data [#create-a-unique-index-on-encrypted-data]
When you define an `equality()` index on a column, CipherStash generates a deterministic HMAC that can be used for unique constraints:
```typescript filename="schema.ts"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
export const patients = encryptedTable("patients", {
ssn: encryptedColumn("ssn")
.equality(),
email: encryptedColumn("email")
.equality(),
})
```
In PostgreSQL with EQL, create a unique index on the HMAC component:
```sql
-- Create the table with encrypted columns
CREATE TABLE patients (
id SERIAL PRIMARY KEY,
ssn eql_v2_encrypted NOT NULL,
email eql_v2_encrypted NOT NULL
);
-- Add unique index on the HMAC (equality) index term
CREATE UNIQUE INDEX patients_ssn_unique
ON patients (eql_v2.hmac_256(ssn));
CREATE UNIQUE INDEX patients_email_unique
ON patients (eql_v2.hmac_256(email));
```
This ensures no two patients can have the same SSN or email, enforced at the database level, while the actual values remain encrypted.
GDPR compliance patterns [#gdpr-compliance-patterns]
Right to erasure (Article 17) [#right-to-erasure-article-17]
Encrypted data with CipherStash supports crypto-shredding: revoke the keyset or client key, and all data encrypted under that key becomes permanently unreadable.
Data minimization (Article 5) [#data-minimization-article-5]
Encrypt all personal data fields and use [Lock Contexts](/stack/cipherstash/encryption/identity) to restrict which application components can decrypt specific records:
```typescript filename="minimize-access.ts"
// Only the billing service can decrypt payment data
const result = await client
.withLockContext({ identityToken: billingServiceJWT })
.decrypt(encryptedPaymentData)
```
Data portability (Article 20) [#data-portability-article-20]
Encrypted values are stored as standard JSON objects ([CipherCells](/stack/reference/cipher-cell)). They can be exported, transferred between systems, and decrypted at the destination, provided the recipient has the appropriate key material.
HIPAA compliance patterns [#hipaa-compliance-patterns]
Access controls (§ 164.312(a)) [#access-controls--164312a]
Use identity-aware encryption to bind decryption to authenticated healthcare providers:
```typescript filename="hipaa-access.ts"
// Encrypt patient records with identity binding
const encrypted = await client
.withLockContext({ identityToken: providerJWT })
.encrypt(patientRecord.diagnosis, {
column: patients.diagnosis,
table: patients,
})
```
Audit controls (§ 164.312(b)) [#audit-controls--164312b]
Every encryption and decryption operation through ZeroKMS produces an audit event. Combine with [CipherStash Proxy audit features](/stack/cipherstash/proxy/audit) for comprehensive data access logging including statement fingerprints and record reconciliation.
Integrity controls (§ 164.312(c)) [#integrity-controls--164312c]
CipherStash's authenticated encryption (AES-256-GCM) detects any tampering with encrypted data during decryption. The operation fails if the ciphertext has been modified.
PCI-DSS compliance patterns [#pci-dss-compliance-patterns]
Requirement 3: Protect stored cardholder data [#requirement-3-protect-stored-cardholder-data]
Encrypt cardholder data at the application layer before it reaches the database:
```typescript filename="pci-store.ts"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
export const cards = encryptedTable("cards", {
cardNumber: encryptedColumn("card_number")
.equality(),
cardholderName: encryptedColumn("cardholder_name"),
})
```
Requirement 10: Track and monitor access [#requirement-10-track-and-monitor-access]
CipherStash's audit logging provides cryptographic proof of data access. Every decryption request is logged with:
* **Who**: the authenticated identity (via Lock Context)
* **What**: which encrypted column was accessed
* **When**: timestamp of the operation
Supported regulations [#supported-regulations]
| Regulation | Key requirements | CipherStash features |
| ------------- | ----------------------------------------------- | ---------------------------------------------------------- |
| **GDPR** | Encryption, data minimization, right to erasure | Searchable encryption, Lock Contexts, crypto-shredding |
| **HIPAA** | Access controls, audit trails, integrity | Identity-aware encryption, ZeroKMS audit logs, AES-256-GCM |
| **PCI-DSS** | Protect cardholder data, access monitoring | Application-layer encryption, audit logging |
| **SOC2** | Encryption at rest and in use, access controls | Encryption-in-use, role-based key access |
| **ISO 27001** | Information security management | End-to-end encryption, key management, audit trails |
| **CCPA** | Consumer data protection | Encrypted storage, access controls |
# Data residency
CipherStash's architecture provides strong data residency guarantees through regional key management, zero-knowledge encryption, and cryptographic key splitting. This guide covers deployment patterns for organizations with cross-border data requirements.
Regional ZeroKMS deployment [#regional-zerokms-deployment]
ZeroKMS is available in [multiple regions](/stack/cipherstash/kms/regions) globally:
* **Asia Pacific**: Sydney (ap-southeast-2)
* **Europe**: Frankfurt (eu-central-1), Ireland (eu-west-1)
* **US East**: N. Virginia (us-east-1)
* **US West**: Oregon (us-west-2)
By selecting a ZeroKMS region, you control where authority keys are managed. Combined with your application's deployment region, this gives you full control over where key material exists.
Dual-party key split for sovereignty [#dual-party-key-split-for-sovereignty]
CipherStash uses a dual-party key split architecture that provides a strong sovereignty guarantee:
1. **Authority key**: managed by ZeroKMS in your chosen region
2. **Client key**: managed by your application in your infrastructure
Neither key alone is sufficient to derive data keys. Both must cooperate to encrypt or decrypt data. This means:
* **ZeroKMS alone cannot access your data**: it only holds half of the key material
* **Your application alone cannot access data**: it needs ZeroKMS to derive data keys
* **Data keys are never transmitted**: they are derived locally in your infrastructure
Deployment patterns [#deployment-patterns]
Single-region deployment [#single-region-deployment]
The simplest pattern: deploy your application and ZeroKMS in the same region.
```
┌─────────────────────────────────┐
│ Region: eu-central-1 │
│ │
│ ┌───────────┐ ┌────────────┐ │
│ │ Your App │──│ ZeroKMS │ │
│ │ + Client │ │ + Authority│ │
│ │ Key │ │ Key │ │
│ └─────┬─────┘ └────────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ PostgreSQL │ │
│ │ (encrypted)│ │
│ └───────────┘ │
└─────────────────────────────────┘
```
All key material and data remain within the single region. This satisfies most data residency requirements including GDPR and regional data protection laws.
Multi-region with regional key isolation [#multi-region-with-regional-key-isolation]
For organizations operating across regions with different data residency requirements, deploy separate workspaces per region:
```
┌─────────────────────┐ ┌─────────────────────┐
│ Region: eu-central-1│ │ Region: ap-southeast-2│
│ │ │ │
│ App + Client Key │ │ App + Client Key │
│ ZeroKMS (EU) │ │ ZeroKMS (APAC) │
│ PostgreSQL (EU) │ │ PostgreSQL (APAC) │
└─────────────────────┘ └─────────────────────┘
```
Each region has its own:
* ZeroKMS workspace with independent authority keys
* Client keys that never leave the region
* Database with encrypted data
Data encrypted in one region cannot be decrypted in another, providing cryptographic enforcement of data residency boundaries.
Cross-border access with centralized control [#cross-border-access-with-centralized-control]
When you need to access encrypted data across regions (e.g., a global support team), use the Encryption SDK with region-specific client keys:
```typescript filename="regional-access.ts"
import { Encryption } from "@cipherstash/stack"
import { customers } from "./schema"
// Configure client for the EU workspace
const euClient = await Encryption({
schemas: [customers],
workspaceCrn: process.env.CS_EU_WORKSPACE_CRN,
clientId: process.env.CS_EU_CLIENT_ID,
clientKey: process.env.CS_EU_CLIENT_KEY,
accessKey: process.env.CS_EU_ACCESS_KEY,
})
// Decrypt EU customer data (requires EU credentials)
const result = await euClient.decrypt(encryptedEuRecord)
```
Access to each region's data requires that region's credentials. This provides an auditable, revocable access model. To revoke a team member's access to a region, delete their client credentials for that region's workspace.
Compliance alignment [#compliance-alignment]
| Requirement | How CipherStash addresses it |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| **Data must not leave the region** | Encryption and decryption happen locally; plaintext never leaves your infrastructure |
| **Key material must stay in-region** | ZeroKMS authority keys are region-bound; client keys deploy with your app |
| **Audit trail for cross-border access** | ZeroKMS logs all key derivation requests with identity context |
| **Ability to revoke access** | Delete client credentials or revoke [Lock Context](/stack/cipherstash/encryption/identity) identities |
| **Cryptographic enforcement** | Dual-party key split makes unauthorized access mathematically impossible |
Next steps [#next-steps]
* [Configure ZeroKMS regions](/stack/cipherstash/kms/regions) for your workspaces
* [Set up identity-aware encryption](/stack/cipherstash/encryption/identity) for access control
* [Review disaster recovery](/stack/cipherstash/kms/disaster-recovery) for multi-region key backup
# Overview
CipherStash vs data vaults [#cipherstash-vs-data-vaults]
Compare CipherStash's proof-based security architecture with traditional data vault solutions like Skyflow and VGS.
Security architecture [#security-architecture]
CipherStash: Proof-based security [#cipherstash-proof-based-security]
Access is cryptographically provable. You do not need to fully trust CipherStash because we never see the data.
CipherStash is actually multiple separate components: a cloud-based key service (ZeroKMS) and encryption services (either Proxy or the Encryption SDK) that always run in your infrastructure. Neither plaintext nor data keys are ever visible to the cloud service. Even the encrypted values always remain within your systems.
CipherStash ZeroKMS employs an encryption key hierarchy to provide true zero-trust capabilities. For a given keyset, ZeroKMS manages an "authority key" which is not sufficient to generate data encryption keys. The application (Proxy or Encryption SDK) manages an independent client key. Data keys can only be generated by the application and so are never visible to ZeroKMS.
Skyflow and VGS: Trust-based security [#skyflow-and-vgs-trust-based-security]
There is no way to prove if data was accessed by an unauthorized user. You must trust that the vendor does the right thing.
In contrast, Skyflow and VGS both process and store plaintext data. There is no strict trust boundary between your systems and the data vault which means you must trust that the company's processes, practices and personnel are appropriate.
Searchable encryption [#searchable-encryption]
CipherStash encryption is searchable and supports simple lookups, range queries, ordering, and free-text search. Data does not need to be decrypted to be queried and can remain encrypted in a broader range of systems. CipherStash searchable encryption is fast: even the slowest mechanism takes less than 1ms to filter records in a PostgreSQL table containing 120 million rows.
Skyflow supports only exact lookups. VGS does not support searchable encryption at all.
Protection beyond the vault [#protection-beyond-the-vault]
If any encrypted data unexpectedly leaves your PostgreSQL database it remains safe. This provides strong protection against accidental leaks as well as insider threats. Encrypted data can also be transferred between systems without ever needing to decrypt it.
A vault offers no or limited protection for data outside the vault. If original values are retrieved and stored then no protection is provided.
Example: Query customer records [#example-query-customer-records]
**Scenario**: You have customer data including SSN, date of birth, email address and home address. You want to retrieve all records where the customer was born in 1990.
| Vendor | How DOB is stored | Query capability | Avoids N+1 | Performance |
| --------------- | --------------------------------- | -------------------------------- | ---------- | ----------------------------------------------- |
| **CipherStash** | Encrypted per-value in your DB | Range queries via ORE | Yes | Efficient (runs in PostgreSQL) |
| **Skyflow** | Plaintext in vault, token outside | Exact match only; range limited | No | Extra 50–200 ms per vault query |
| **VGS** | Plaintext in vault, alias outside | Equality only (no range queries) | No | Requires fetching all and filtering client-side |
***
Data sovereignty [#data-sovereignty]
CipherStash provides comprehensive solutions for data sovereignty requirements. The architecture gives you complete control over where your data and key material reside.
Zero-knowledge architecture [#zero-knowledge-architecture]
* **Data keys are never transmitted across networks**: keys are derived locally in your infrastructure
* **Plaintext data never leaves your systems**: encryption and decryption occur entirely within your environment
* **Even encrypted values remain in your infrastructure**: CipherStash Proxy and Encryption SDK run in your systems
* **No single point of compromise**: cryptographic proofs ensure unauthorized access is mathematically impossible
Cryptographic control [#cryptographic-control]
CipherStash uses a **dual-party key split** architecture:
* ZeroKMS manages an "authority key" (required but not sufficient)
* Your application (Proxy or Encryption SDK) manages an independent "client key"
* Data keys can only be generated when both components cooperate
* Data keys are never visible to ZeroKMS or transmitted over networks
Regional deployment [#regional-deployment]
ZeroKMS is available in [multiple regions globally](/stack/cipherstash/kms/regions), including Asia Pacific (Sydney), Europe (Frankfurt, Ireland), US East, and US West.
Compliance alignment [#compliance-alignment]
CipherStash's data sovereignty architecture supports compliance with GDPR, regional data protection laws, government regulations, and industry-specific requirements for healthcare, financial services, and other regulated sectors.
***
What data should I protect? [#what-data-should-i-protect]
When improving your data security practices, it can be difficult to know what data is important enough to protect. Here are the common types of sensitive data you should encrypt.
Personally Identifiable Information (PII) [#personally-identifiable-information-pii]
Any data that could identify a specific individual: names, phone numbers, dates of birth, addresses, emails, IP addresses, social security numbers, license numbers, passport numbers.
**Applicable regulations**: GDPR, CCPA, HIPAA, APP (Australia), PCI-DSS, SOC2, ISO27001
Protected Health Information (PHI) [#protected-health-information-phi]
Any information about health status, provision of health care, or payment for health care that can be linked to an individual: medical record numbers, health insurance beneficiary numbers, biometric identifiers, medical conditions, medication histories, payment histories.
**Applicable regulations**: HIPAA, CMIA, GDPR, APP
Financial Information [#financial-information]
Data about money, accounts, and transactions: account numbers and balances, transaction data, TFNs/ITINs, saved payees.
**Applicable regulations**: GDPR, CCPA, PCI-DSS, CDR (Australia), SOC2, ISO27001
Authentication Information [#authentication-information]
Credentials used to gain access to accounts and services: usernames, passwords (plaintext and hashed), OAuth tokens, session cookies.
**Applicable regulations**: SOC2, ISO27001, PCI-DSS
# Provable access control
Traditional access control relies on application logic. If a bug or misconfiguration exposes data, there is no way to prove access was unauthorized. CipherStash provides **cryptographic proof-based access control** through Lock Contexts, where access to data is mathematically bound to authenticated identities.
How it works [#how-it-works]
CipherStash [Lock Contexts](/stack/cipherstash/encryption/identity) bind encryption and decryption operations to an authenticated identity (typically a JWT from your identity provider). The identity token becomes part of the key derivation process. Without a valid token, the data keys cannot be derived and the data cannot be decrypted.
This creates a provable access boundary:
* If data was decrypted, the identity token **must** have been present
* ZeroKMS logs the identity associated with every key derivation
* The audit trail is cryptographically verifiable; it cannot be falsified
Identity-aware encryption [#identity-aware-encryption]
Bind encryption to an identity [#bind-encryption-to-an-identity]
```typescript filename="identity-encrypt.ts"
import { Encryption } from "@cipherstash/stack"
import { patients } from "./schema"
const client = await Encryption({ schemas: [patients] })
// The JWT identifies the healthcare provider performing the action
async function encryptPatientRecord(
record: { diagnosis: string },
providerJWT: string
) {
const result = await client
.withLockContext({ identityToken: providerJWT })
.encrypt(record.diagnosis, {
column: patients.diagnosis,
table: patients,
})
if (result.failure) {
throw new Error(`Encryption failed: ${result.failure.message}`)
}
return result.data
}
```
Decrypt with identity verification [#decrypt-with-identity-verification]
```typescript filename="identity-decrypt.ts"
async function decryptPatientRecord(
encryptedDiagnosis: unknown,
providerJWT: string
) {
const result = await client
.withLockContext({ identityToken: providerJWT })
.decrypt(encryptedDiagnosis)
if (result.failure) {
// Decryption fails if the identity doesn't have access
throw new Error(`Access denied: ${result.failure.message}`)
}
return result.data
}
```
Audit logging [#audit-logging]
Every encryption and decryption operation through ZeroKMS produces an audit event containing:
| Field | Description |
| --------------- | ------------------------------------------------------------- |
| **Identity** | The authenticated user or service (from the Lock Context JWT) |
| **Operation** | Encrypt or decrypt |
| **Timestamp** | When the operation occurred |
| **Keyset** | Which keyset was used |
| **Application** | Which application performed the operation |
Combining with Proxy audit [#combining-with-proxy-audit]
When using [CipherStash Proxy](/stack/cipherstash/proxy), additional audit capabilities are available:
* **[Statement fingerprinting](/stack/cipherstash/proxy/audit)**: identify unique SQL query patterns accessing encrypted data
* **[SQL redaction](/stack/cipherstash/proxy/audit)**: strip sensitive values from logged queries
* **[Primary key injection](/stack/cipherstash/proxy/audit)**: track which specific records were accessed
* **[Record reconciliation](/stack/cipherstash/proxy/audit)**: map data access events to specific table records
Together, these provide an end-to-end audit trail: **who** accessed **what data**, **when**, and using **which query**.
Use cases [#use-cases]
Healthcare: HIPAA audit requirements [#healthcare-hipaa-audit-requirements]
HIPAA requires audit controls that record who accessed Protected Health Information (PHI). With Lock Contexts, every access to patient data is cryptographically tied to the authenticated provider:
```typescript filename="hipaa-audit.ts"
// Each access is provably tied to the authenticated provider
const diagnosis = await client
.withLockContext({ identityToken: drSmithJWT })
.decrypt(patient.encryptedDiagnosis)
// ZeroKMS audit log shows:
// - Identity: dr.smith@hospital.example (from JWT)
// - Operation: decrypt
// - Keyset: patients-production
// - Timestamp: 2025-01-15T09:30:00Z
```
Financial services: Segregation of duties [#financial-services-segregation-of-duties]
Ensure that only authorized roles can access specific data categories:
```typescript filename="segregation.ts"
// Compliance team can decrypt audit records
const auditData = await client
.withLockContext({ identityToken: complianceTeamJWT })
.decrypt(encryptedAuditRecord)
// Trading team uses a different Lock Context — cannot decrypt audit records
// The key derivation will fail because the identity doesn't match
```
Multi-tenant SaaS: Tenant isolation [#multi-tenant-saas-tenant-isolation]
Bind encryption to tenant identity to provide cryptographic tenant isolation:
```typescript filename="tenant-isolation.ts"
// Encrypt data with tenant-scoped identity
const encrypted = await client
.withLockContext({ identityToken: tenantAJWT })
.encrypt(sensitiveData, {
column: tenants.data,
table: tenants,
})
// Only Tenant A's identity can decrypt this data
// Tenant B's JWT will fail key derivation
```
Benefits over traditional access control [#benefits-over-traditional-access-control]
| Aspect | Traditional (application logic) | Provable (CipherStash Lock Contexts) |
| ------------------------ | ---------------------------------------- | -------------------------------------------------------- |
| **Enforcement** | Software checks that can be bypassed | Cryptographic: mathematically impossible to bypass |
| **Audit trail** | Application logs that can be modified | ZeroKMS audit logs that are cryptographically verifiable |
| **Proof of access** | Circumstantial (log entries) | Deterministic (key derivation requires identity) |
| **Blast radius of bugs** | Data exposed if access check is bypassed | Data remains encrypted even if application logic fails |
Next steps [#next-steps]
* [Set up identity-aware encryption](/stack/cipherstash/encryption/identity) with Lock Contexts
* [Configure audit logging](/stack/cipherstash/proxy/audit) with CipherStash Proxy
* [Review compliance patterns](/stack/reference/use-cases/compliance) for GDPR, HIPAA, and PCI-DSS
# @cipherstash/stack
**@cipherstash/stack**
***
@cipherstash/stack [#cipherstashstack]
Modules [#modules]
* [packages/stack/src/client](/stack/reference/stack/latest/packages/stack/src/client)
* [packages/stack/src/drizzle](/stack/reference/stack/latest/packages/stack/src/drizzle)
* [packages/stack/src/dynamodb](/stack/reference/stack/latest/packages/stack/src/dynamodb)
* [packages/stack/src/encryption](/stack/reference/stack/latest/packages/stack/src/encryption)
* [packages/stack/src/errors](/stack/reference/stack/latest/packages/stack/src/errors)
* [packages/stack/src/identity](/stack/reference/stack/latest/packages/stack/src/identity)
* [packages/stack/src/schema](/stack/reference/stack/latest/packages/stack/src/schema)
* [packages/stack/src/supabase](/stack/reference/stack/latest/packages/stack/src/supabase)
* [packages/stack/src/types-public](/stack/reference/stack/latest/packages/stack/src/types-public)
# packages/stack/src/client
[**@cipherstash/stack**](../../../..)
***
packages/stack/src/client [#packagesstacksrcclient]
References [#references]
encryptedTable [#encryptedtable]
Re-exports [encryptedTable](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedTable)
***
encryptedColumn [#encryptedcolumn]
Re-exports [encryptedColumn](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedColumn)
***
encryptedField [#encryptedfield]
Re-exports [encryptedField](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedField)
***
EncryptedColumn [#encryptedcolumn-1]
Re-exports [EncryptedColumn](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedColumn)
***
EncryptedTable [#encryptedtable-1]
Re-exports [EncryptedTable](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)
***
EncryptedTableColumn [#encryptedtablecolumn]
Re-exports [EncryptedTableColumn](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)
***
EncryptedField [#encryptedfield-1]
Re-exports [EncryptedField](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedField)
***
InferPlaintext [#inferplaintext]
Re-exports [InferPlaintext](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/InferPlaintext)
***
InferEncrypted [#inferencrypted]
Re-exports [InferEncrypted](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/InferEncrypted)
***
EncryptionClient [#encryptionclient]
Re-exports [EncryptionClient](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient)
# packages/stack/src/drizzle
[**@cipherstash/stack**](../../../..)
***
packages/stack/src/drizzle [#packagesstacksrcdrizzle]
Classes [#classes]
* [EncryptionOperatorError](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError)
* [EncryptionConfigError](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionConfigError)
Type Aliases [#type-aliases]
* [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig)
Functions [#functions]
* [encryptedType](/stack/reference/stack/latest/packages/stack/src/drizzle/functions/encryptedType)
* [createEncryptionOperators](/stack/reference/stack/latest/packages/stack/src/drizzle/functions/createEncryptionOperators)
* [extractEncryptionSchema](/stack/reference/stack/latest/packages/stack/src/drizzle/functions/extractEncryptionSchema)
References [#references]
CastAs [#castas]
Re-exports [CastAs](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/CastAs)
***
MatchIndexOpts [#matchindexopts]
Re-exports [MatchIndexOpts](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/MatchIndexOpts)
***
TokenFilter [#tokenfilter]
Re-exports [TokenFilter](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/TokenFilter)
# packages/stack/src/dynamodb
[**@cipherstash/stack**](../../../..)
***
packages/stack/src/dynamodb [#packagesstacksrcdynamodb]
Classes [#classes]
* [BulkDecryptModelsOperation](/stack/reference/stack/latest/packages/stack/src/dynamodb/classes/BulkDecryptModelsOperation)
* [BulkEncryptModelsOperation](/stack/reference/stack/latest/packages/stack/src/dynamodb/classes/BulkEncryptModelsOperation)
* [DecryptModelOperation](/stack/reference/stack/latest/packages/stack/src/dynamodb/classes/DecryptModelOperation)
* [EncryptModelOperation](/stack/reference/stack/latest/packages/stack/src/dynamodb/classes/EncryptModelOperation)
Interfaces [#interfaces]
* [EncryptedDynamoDBConfig](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBConfig)
* [EncryptedDynamoDBError](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBError)
* [EncryptedDynamoDBInstance](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBInstance)
Functions [#functions]
* [encryptedDynamoDB](/stack/reference/stack/latest/packages/stack/src/dynamodb/functions/encryptedDynamoDB)
# packages/stack/src/encryption
[**@cipherstash/stack**](../../../..)
***
packages/stack/src/encryption [#packagesstacksrcencryption]
Classes [#classes]
* [EncryptionClient](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient)
* [BatchEncryptQueryOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/BatchEncryptQueryOperation)
* [BulkDecryptModelsOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/BulkDecryptModelsOperation)
* [BulkDecryptOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/BulkDecryptOperation)
* [BulkEncryptModelsOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/BulkEncryptModelsOperation)
* [BulkEncryptOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/BulkEncryptOperation)
* [DecryptModelOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/DecryptModelOperation)
* [DecryptOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/DecryptOperation)
* [EncryptModelOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptModelOperation)
* [EncryptQueryOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptQueryOperation)
* [EncryptOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptOperation)
Functions [#functions]
* [noClientError](/stack/reference/stack/latest/packages/stack/src/encryption/functions/noClientError)
* [Encryption](/stack/reference/stack/latest/packages/stack/src/encryption/functions/Encryption)
# packages/stack/src/errors
[**@cipherstash/stack**](../../../..)
***
packages/stack/src/errors [#packagesstacksrcerrors]
Interfaces [#interfaces]
* [EncryptionError](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)
* [ClientInitError](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/ClientInitError)
* [EncryptionOperationError](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionOperationError)
* [DecryptionOperationError](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/DecryptionOperationError)
* [LockContextError](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/LockContextError)
* [CtsTokenError](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/CtsTokenError)
Type Aliases [#type-aliases]
* [StackError](/stack/reference/stack/latest/packages/stack/src/errors/type-aliases/StackError)
Variables [#variables]
* [EncryptionErrorTypes](/stack/reference/stack/latest/packages/stack/src/errors/variables/EncryptionErrorTypes)
Functions [#functions]
* [getErrorMessage](/stack/reference/stack/latest/packages/stack/src/errors/functions/getErrorMessage)
# packages/stack/src/identity
[**@cipherstash/stack**](../../../..)
***
packages/stack/src/identity [#packagesstacksrcidentity]
Classes [#classes]
* [LockContext](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
Type Aliases [#type-aliases]
* [CtsRegions](/stack/reference/stack/latest/packages/stack/src/identity/type-aliases/CtsRegions)
* [IdentifyOptions](/stack/reference/stack/latest/packages/stack/src/identity/type-aliases/IdentifyOptions)
* [CtsToken](/stack/reference/stack/latest/packages/stack/src/identity/type-aliases/CtsToken)
* [Context](/stack/reference/stack/latest/packages/stack/src/identity/type-aliases/Context)
* [LockContextOptions](/stack/reference/stack/latest/packages/stack/src/identity/type-aliases/LockContextOptions)
* [GetLockContextResponse](/stack/reference/stack/latest/packages/stack/src/identity/type-aliases/GetLockContextResponse)
# packages/stack/src/schema
[**@cipherstash/stack**](../../../..)
***
packages/stack/src/schema [#packagesstacksrcschema]
Classes [#classes]
* [EncryptedField](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedField)
* [EncryptedColumn](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedColumn)
* [EncryptedTable](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)
Type Aliases [#type-aliases]
* [CastAs](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/CastAs)
* [EqlCastAs](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EqlCastAs)
* [TokenFilter](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/TokenFilter)
* [MatchIndexOpts](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/MatchIndexOpts)
* [SteVecIndexOpts](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/SteVecIndexOpts)
* [UniqueIndexOpts](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/UniqueIndexOpts)
* [OreIndexOpts](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/OreIndexOpts)
* [ColumnSchema](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/ColumnSchema)
* [EncryptedTableColumn](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)
* [EncryptConfig](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptConfig)
* [InferPlaintext](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/InferPlaintext)
* [InferEncrypted](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/InferEncrypted)
Variables [#variables]
* [eqlCastAsEnum](/stack/reference/stack/latest/packages/stack/src/schema/variables/eqlCastAsEnum)
* [castAsEnum](/stack/reference/stack/latest/packages/stack/src/schema/variables/castAsEnum)
Functions [#functions]
* [toEqlCastAs](/stack/reference/stack/latest/packages/stack/src/schema/functions/toEqlCastAs)
* [encryptedTable](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedTable)
* [encryptedColumn](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedColumn)
* [encryptedField](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedField)
* [buildEncryptConfig](/stack/reference/stack/latest/packages/stack/src/schema/functions/buildEncryptConfig)
# packages/stack/src/supabase
[**@cipherstash/stack**](../../../..)
***
packages/stack/src/supabase [#packagesstacksrcsupabase]
Interfaces [#interfaces]
* [EncryptedSupabaseInstance](/stack/reference/stack/latest/packages/stack/src/supabase/interfaces/EncryptedSupabaseInstance)
* [SupabaseClientLike](/stack/reference/stack/latest/packages/stack/src/supabase/interfaces/SupabaseClientLike)
* [EncryptedQueryBuilder](/stack/reference/stack/latest/packages/stack/src/supabase/interfaces/EncryptedQueryBuilder)
Type Aliases [#type-aliases]
* [EncryptedSupabaseConfig](/stack/reference/stack/latest/packages/stack/src/supabase/type-aliases/EncryptedSupabaseConfig)
* [EncryptedSupabaseResponse](/stack/reference/stack/latest/packages/stack/src/supabase/type-aliases/EncryptedSupabaseResponse)
* [EncryptedSupabaseError](/stack/reference/stack/latest/packages/stack/src/supabase/type-aliases/EncryptedSupabaseError)
* [PendingOrCondition](/stack/reference/stack/latest/packages/stack/src/supabase/type-aliases/PendingOrCondition)
Functions [#functions]
* [encryptedSupabase](/stack/reference/stack/latest/packages/stack/src/supabase/functions/encryptedSupabase)
# packages/stack/src/types-public
[**@cipherstash/stack**](../../../..)
***
packages/stack/src/types-public [#packagesstacksrctypes-public]
Type Aliases [#type-aliases]
* [Client](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Client)
* [EncryptedValue](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedValue)
* [Encrypted](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Encrypted)
* [EncryptedQuery](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedQuery)
* [KeysetIdentifier](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/KeysetIdentifier)
* [ClientConfig](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/ClientConfig)
* [EncryptionClientConfig](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptionClientConfig)
* [EncryptOptions](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptOptions)
* [EncryptedReturnType](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedReturnType)
* [SearchTerm](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/SearchTerm)
* [EncryptedSearchTerm](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedSearchTerm)
* [EncryptedQueryResult](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedQueryResult)
* [EncryptedFields](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedFields)
* [OtherFields](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/OtherFields)
* [DecryptedFields](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/DecryptedFields)
* [Decrypted](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)
* [EncryptedFromSchema](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedFromSchema)
* [BulkEncryptPayload](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkEncryptPayload)
* [BulkEncryptedData](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkEncryptedData)
* [BulkDecryptPayload](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkDecryptPayload)
* [BulkDecryptedData](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkDecryptedData)
* [DecryptionResult](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/DecryptionResult)
* [QueryTypeName](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/QueryTypeName)
* [EncryptQueryOptions](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptQueryOptions)
* [ScalarQueryTerm](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/ScalarQueryTerm)
Variables [#variables]
* [queryTypes](/stack/reference/stack/latest/packages/stack/src/types-public/variables/queryTypes)
# createEncryptionOperators
[**@cipherstash/stack**](../../../../..)
***
Function: createEncryptionOperators() [#function-createencryptionoperators]
```ts
function createEncryptionOperators(encryptionClient): {
eq: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
ne: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
gt: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
gte: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
lt: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
lte: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
between: (left, min, max) => SQL<unknown> | Promise<SQL<unknown>>;
notBetween: (left, min, max) => SQL<unknown> | Promise<SQL<unknown>>;
like: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
ilike: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
notIlike: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
jsonbPathQueryFirst: (left, right) => Promise<SQL<unknown>>;
jsonbGet: (left, right) => Promise<SQL<unknown>>;
jsonbPathExists: (left, right) => Promise<SQL<unknown>>;
inArray: (left, right) => Promise<SQL<unknown>>;
notInArray: (left, right) => Promise<SQL<unknown>>;
asc: (column) => SQL;
desc: (column) => SQL;
and: (...conditions) => Promise<SQL<unknown>>;
or: (...conditions) => Promise<SQL<unknown>>;
exists: (subquery) => SQL;
notExists: (subquery) => SQL;
isNull: (value) => SQL;
isNotNull: (value) => SQL;
not: (condition) => SQL;
arrayContains: {
<T> (column, values): SQL;
<TColumn> (column, values): SQL;
<T> (column, values): SQL;
};
arrayContained: {
<T> (column, values): SQL;
<TColumn> (column, values): SQL;
<T> (column, values): SQL;
};
arrayOverlaps: {
<T> (column, values): SQL;
<TColumn> (column, values): SQL;
<T> (column, values): SQL;
};
};
```
Defined in: [packages/stack/src/drizzle/operators.ts:975](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/operators.ts#L975)
Creates a set of encryption-aware operators that automatically encrypt values
for encrypted columns before using them with Drizzle operators.
For equality and text search operators (eq, ne, like, ilike, inArray, etc.):
Values are encrypted and then passed to regular Drizzle operators, which use
PostgreSQL's built-in operators for eql\_v2\_encrypted types.
For order and range operators (gt, gte, lt, lte, between, notBetween):
Values are encrypted and then use eql\_v2.\* functions (eql\_v2.gt(), eql\_v2.gte(), etc.)
which are required for ORE (Order-Revealing Encryption) comparisons.
Parameters [#parameters]
encryptionClient [#encryptionclient]
[`EncryptionClient`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient)
The EncryptionClient instance
Returns [#returns]
An object with all Drizzle operators wrapped for encrypted columns
eq() [#eq]
```ts
eq: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
```
Equality operator - encrypts value for encrypted columns.
Requires either `equality` or `orderAndRange` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
Parameters [#parameters-1]
left [#left]
`SQLWrapper`
right [#right]
`unknown`
Returns [#returns-1]
`SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>>
Example [#example]
Select users with a specific email address.
```ts
const condition = await ops.eq(usersTable.email, 'user@example.com')
const results = await db.select().from(usersTable).where(condition)
```
ne() [#ne]
```ts
ne: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
```
Not equal operator - encrypts value for encrypted columns.
Requires either `equality` or `orderAndRange` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
Parameters [#parameters-2]
left [#left-1]
`SQLWrapper`
right [#right-1]
`unknown`
Returns [#returns-2]
`SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>>
Example [#example-1]
Select users whose email address is not a specific value.
```ts
const condition = await ops.ne(usersTable.email, 'user@example.com')
const results = await db.select().from(usersTable).where(condition)
```
gt() [#gt]
```ts
gt: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
```
Greater than operator for encrypted columns with ORE index.
Requires `orderAndRange` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
Parameters [#parameters-3]
left [#left-2]
`SQLWrapper`
right [#right-2]
`unknown`
Returns [#returns-3]
`SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>>
Example [#example-2]
Select users older than a specific age.
```ts
const condition = await ops.gt(usersTable.age, 30)
const results = await db.select().from(usersTable).where(condition)
```
gte() [#gte]
```ts
gte: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
```
Greater than or equal operator for encrypted columns with ORE index.
Requires `orderAndRange` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
Parameters [#parameters-4]
left [#left-3]
`SQLWrapper`
right [#right-3]
`unknown`
Returns [#returns-4]
`SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>>
Example [#example-3]
Select users older than or equal to a specific age.
```ts
const condition = await ops.gte(usersTable.age, 30)
const results = await db.select().from(usersTable).where(condition)
```
lt() [#lt]
```ts
lt: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
```
Less than operator for encrypted columns with ORE index.
Requires `orderAndRange` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
Parameters [#parameters-5]
left [#left-4]
`SQLWrapper`
right [#right-4]
`unknown`
Returns [#returns-5]
`SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>>
Example [#example-4]
Select users younger than a specific age.
```ts
const condition = await ops.lt(usersTable.age, 30)
const results = await db.select().from(usersTable).where(condition)
```
lte() [#lte]
```ts
lte: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
```
Less than or equal operator for encrypted columns with ORE index.
Requires `orderAndRange` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
Parameters [#parameters-6]
left [#left-5]
`SQLWrapper`
right [#right-5]
`unknown`
Returns [#returns-6]
`SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>>
Example [#example-5]
Select users younger than or equal to a specific age.
```ts
const condition = await ops.lte(usersTable.age, 30)
const results = await db.select().from(usersTable).where(condition)
```
between() [#between]
```ts
between: (left, min, max) => SQL<unknown> | Promise<SQL<unknown>>;
```
Between operator for encrypted columns with ORE index.
Requires `orderAndRange` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
Parameters [#parameters-7]
left [#left-6]
`SQLWrapper`
min [#min]
`unknown`
max [#max]
`unknown`
Returns [#returns-7]
`SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>>
Example [#example-6]
Select users within a specific age range.
```ts
const condition = await ops.between(usersTable.age, 20, 30)
const results = await db.select().from(usersTable).where(condition)
```
notBetween() [#notbetween]
```ts
notBetween: (left, min, max) => SQL<unknown> | Promise<SQL<unknown>>;
```
Not between operator for encrypted columns with ORE index.
Requires `orderAndRange` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
Parameters [#parameters-8]
left [#left-7]
`SQLWrapper`
min [#min-1]
`unknown`
max [#max-1]
`unknown`
Returns [#returns-8]
`SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>>
Example [#example-7]
Select users outside a specific age range.
```ts
const condition = await ops.notBetween(usersTable.age, 20, 30)
const results = await db.select().from(usersTable).where(condition)
```
like() [#like]
```ts
like: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
```
Like operator for encrypted columns with free text search.
Requires `freeTextSearch` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
> \[!IMPORTANT]
> Case sensitivity on encrypted columns depends on the [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
> Ensure that the column is configured for case-insensitive search if needed.
Parameters [#parameters-9]
left [#left-8]
`SQLWrapper`
right [#right-6]
`unknown`
Returns [#returns-9]
`SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>>
Example [#example-8]
Select users with email addresses matching a pattern.
```ts
const condition = await ops.like(usersTable.email, '%@example.com')
const results = await db.select().from(usersTable).where(condition)
```
ilike() [#ilike]
```ts
ilike: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
```
ILike operator for encrypted columns with free text search.
Requires `freeTextSearch` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
> \[!IMPORTANT]
> Case sensitivity on encrypted columns depends on the [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
> Ensure that the column is configured for case-insensitive search if needed.
Parameters [#parameters-10]
left [#left-9]
`SQLWrapper`
right [#right-7]
`unknown`
Returns [#returns-10]
`SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>>
Example [#example-9]
Select users with email addresses matching a pattern (case-insensitive).
```ts
const condition = await ops.ilike(usersTable.email, '%@example.com')
const results = await db.select().from(usersTable).where(condition)
```
notIlike() [#notilike]
```ts
notIlike: (left, right) => SQL<unknown> | Promise<SQL<unknown>>;
```
Parameters [#parameters-11]
left [#left-10]
`SQLWrapper`
right [#right-8]
`unknown`
Returns [#returns-11]
`SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>>
jsonbPathQueryFirst() [#jsonbpathqueryfirst]
```ts
jsonbPathQueryFirst: (left, right) => Promise<SQL<unknown>>;
```
JSONB path query first operator for encrypted columns with searchable JSON.
Requires `searchableJson` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
Encrypts the JSON path selector and calls `eql_v2.jsonb_path_query_first()`,
casting the parameter to `eql_v2_encrypted`.
Parameters [#parameters-12]
left [#left-11]
`SQLWrapper`
right [#right-9]
`unknown`
Returns [#returns-12]
`Promise`\<`SQL`\<`unknown`>>
Throws [#throws]
If the column does not have `searchableJson` enabled.
jsonbGet() [#jsonbget]
```ts
jsonbGet: (left, right) => Promise<SQL<unknown>>;
```
JSONB get operator for encrypted columns with searchable JSON.
Requires `searchableJson` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
Encrypts the JSON path selector and uses the `->` operator,
casting the parameter to `eql_v2_encrypted`.
Parameters [#parameters-13]
left [#left-12]
`SQLWrapper`
right [#right-10]
`unknown`
Returns [#returns-13]
`Promise`\<`SQL`\<`unknown`>>
Throws [#throws-1]
If the column does not have `searchableJson` enabled.
jsonbPathExists() [#jsonbpathexists]
```ts
jsonbPathExists: (left, right) => Promise<SQL<unknown>>;
```
JSONB path exists operator for encrypted columns with searchable JSON.
Requires `searchableJson` to be set on [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
Encrypts the JSON path selector and calls `eql_v2.jsonb_path_exists()`,
casting the parameter to `eql_v2_encrypted`.
Parameters [#parameters-14]
left [#left-13]
`SQLWrapper`
right [#right-11]
`unknown`
Returns [#returns-14]
`Promise`\<`SQL`\<`unknown`>>
Throws [#throws-2]
If the column does not have `searchableJson` enabled.
inArray() [#inarray]
```ts
inArray: (left, right) => Promise<SQL<unknown>>;
```
Parameters [#parameters-15]
left [#left-14]
`SQLWrapper`
right [#right-12]
`unknown`\[] | `SQLWrapper`
Returns [#returns-15]
`Promise`\<`SQL`\<`unknown`>>
notInArray() [#notinarray]
```ts
notInArray: (left, right) => Promise<SQL<unknown>>;
```
Parameters [#parameters-16]
left [#left-15]
`SQLWrapper`
right [#right-13]
`unknown`\[] | `SQLWrapper`
Returns [#returns-16]
`Promise`\<`SQL`\<`unknown`>>
asc() [#asc]
```ts
asc: (column) => SQL;
```
Parameters [#parameters-17]
column [#column]
`SQLWrapper`
Returns [#returns-17]
`SQL`
desc() [#desc]
```ts
desc: (column) => SQL;
```
Parameters [#parameters-18]
column [#column-1]
`SQLWrapper`
Returns [#returns-18]
`SQL`
and() [#and]
```ts
and: (...conditions) => Promise<SQL<unknown>>;
```
Parameters [#parameters-19]
conditions [#conditions]
...(
\| `SQLWrapper`
\| `SQL`\<`unknown`>
\| `Promise`\<`SQL`\<`unknown`>>
\| `undefined`)\[]
Returns [#returns-19]
`Promise`\<`SQL`\<`unknown`>>
or() [#or]
```ts
or: (...conditions) => Promise<SQL<unknown>>;
```
Parameters [#parameters-20]
conditions [#conditions-1]
...(
\| `SQLWrapper`
\| `SQL`\<`unknown`>
\| `Promise`\<`SQL`\<`unknown`>>
\| `undefined`)\[]
Returns [#returns-20]
`Promise`\<`SQL`\<`unknown`>>
exists() [#exists]
```ts
exists: (subquery) => SQL;
```
Test whether a subquery evaluates to have any rows.
Examples [#examples]
```ts
// Users whose `homeCity` column has a match in a cities
// table.
db
.select()
.from(users)
.where(
exists(db.select()
.from(cities)
.where(eq(users.homeCity, cities.id))),
);
```
Parameters [#parameters-21]
subquery [#subquery]
`SQLWrapper`
Returns [#returns-21]
`SQL`
See [#see]
notExists for the inverse of this test
notExists() [#notexists]
```ts
notExists: (subquery) => SQL;
```
Test whether a subquery doesn't include any result
rows.
Examples [#examples-1]
```ts
// Users whose `homeCity` column doesn't match
// a row in the cities table.
db
.select()
.from(users)
.where(
notExists(db.select()
.from(cities)
.where(eq(users.homeCity, cities.id))),
);
```
Parameters [#parameters-22]
subquery [#subquery-1]
`SQLWrapper`
Returns [#returns-22]
`SQL`
See [#see-1]
exists for the inverse of this test
isNull() [#isnull]
```ts
isNull: (value) => SQL;
```
Test whether an expression is NULL. By the SQL standard,
NULL is neither equal nor not equal to itself, so
it's recommended to use `isNull` and `notIsNull` for
comparisons to NULL.
Examples [#examples-2]
```ts
// Select cars that have no discontinuedAt date.
db.select().from(cars)
.where(isNull(cars.discontinuedAt))
```
Parameters [#parameters-23]
value [#value]
`SQLWrapper`
Returns [#returns-23]
`SQL`
See [#see-2]
isNotNull for the inverse of this test
isNotNull() [#isnotnull]
```ts
isNotNull: (value) => SQL;
```
Test whether an expression is not NULL. By the SQL standard,
NULL is neither equal nor not equal to itself, so
it's recommended to use `isNull` and `notIsNull` for
comparisons to NULL.
Examples [#examples-3]
```ts
// Select cars that have been discontinued.
db.select().from(cars)
.where(isNotNull(cars.discontinuedAt))
```
Parameters [#parameters-24]
value [#value-1]
`SQLWrapper`
Returns [#returns-24]
`SQL`
See [#see-3]
isNull for the inverse of this test
not() [#not]
```ts
not: (condition) => SQL;
```
Negate the meaning of an expression using the `not` keyword.
Examples [#examples-4]
```ts
// Select cars _not_ made by GM or Ford.
db.select().from(cars)
.where(not(inArray(cars.make, ['GM', 'Ford'])))
```
Parameters [#parameters-25]
condition [#condition]
`SQLWrapper`
Returns [#returns-25]
`SQL`
arrayContains() [#arraycontains]
```ts
arrayContains: {
<T> (column, values): SQL;
<TColumn> (column, values): SQL;
<T> (column, values): SQL;
};
```
Call Signature [#call-signature]
```ts
<T>(column, values): SQL;
```
Test that a column or expression contains all elements of
the list passed as the second argument.
Throws [#throws-3]
The argument passed in the second array can't be empty:
if an empty is provided, this method will throw.
Examples [#examples-5]
```ts
// Select posts where its tags contain "Typescript" and "ORM".
db.select().from(posts)
.where(arrayContains(posts.tags, ['Typescript', 'ORM']))
```
Type Parameters [#type-parameters]
T [#t]
`T`
Parameters [#parameters-26]
column [#column-2]
`Aliased`\<`T`>
values [#values]
`SQLWrapper` | `Placeholder`\<`string`, `any`> | `T`
Returns [#returns-26]
`SQL`
See [#see-4]
* arrayContained to find if an array contains all elements of a column or expression
* arrayOverlaps to find if a column or expression contains any elements of an array
Call Signature [#call-signature-1]
```ts
<TColumn>(column, values): SQL;
```
Test that a column or expression contains all elements of
the list passed as the second argument.
Throws [#throws-4]
The argument passed in the second array can't be empty:
if an empty is provided, this method will throw.
Examples [#examples-6]
```ts
// Select posts where its tags contain "Typescript" and "ORM".
db.select().from(posts)
.where(arrayContains(posts.tags, ['Typescript', 'ORM']))
```
Type Parameters [#type-parameters-1]
TColumn [#tcolumn]
`TColumn` *extends* `Column`\<`ColumnBaseConfig`\<`ColumnDataType`, `string`>, `object`, `object`>
Parameters [#parameters-27]
column [#column-3]
`TColumn`
values [#values-1]
`SQLWrapper` | `Placeholder`\<`string`, `any`> | `TColumn`\[`"_"`]\[`"data"`]
Returns [#returns-27]
`SQL`
See [#see-5]
* arrayContained to find if an array contains all elements of a column or expression
* arrayOverlaps to find if a column or expression contains any elements of an array
Call Signature [#call-signature-2]
```ts
<T>(column, values): SQL;
```
Test that a column or expression contains all elements of
the list passed as the second argument.
Throws [#throws-5]
The argument passed in the second array can't be empty:
if an empty is provided, this method will throw.
Examples [#examples-7]
```ts
// Select posts where its tags contain "Typescript" and "ORM".
db.select().from(posts)
.where(arrayContains(posts.tags, ['Typescript', 'ORM']))
```
Type Parameters [#type-parameters-2]
T [#t-1]
`T` *extends* `SQLWrapper`
Parameters [#parameters-28]
column [#column-4]
`Exclude`\<`T`,
\| `Column`\<`ColumnBaseConfig`\<`ColumnDataType`, `string`>, `object`, `object`>
\| `Aliased`\<`unknown`>>
values [#values-2]
`unknown`\[] | `SQLWrapper`
Returns [#returns-28]
`SQL`
See [#see-6]
* arrayContained to find if an array contains all elements of a column or expression
* arrayOverlaps to find if a column or expression contains any elements of an array
arrayContained() [#arraycontained]
```ts
arrayContained: {
<T> (column, values): SQL;
<TColumn> (column, values): SQL;
<T> (column, values): SQL;
};
```
Call Signature [#call-signature-3]
```ts
<T>(column, values): SQL;
```
Test that the list passed as the second argument contains
all elements of a column or expression.
Throws [#throws-6]
The argument passed in the second array can't be empty:
if an empty is provided, this method will throw.
Examples [#examples-8]
```ts
// Select posts where its tags contain "Typescript", "ORM" or both,
// but filtering posts that have additional tags.
db.select().from(posts)
.where(arrayContained(posts.tags, ['Typescript', 'ORM']))
```
Type Parameters [#type-parameters-3]
T [#t-2]
`T`
Parameters [#parameters-29]
column [#column-5]
`Aliased`\<`T`>
values [#values-3]
`SQLWrapper` | `Placeholder`\<`string`, `any`> | `T`
Returns [#returns-29]
`SQL`
See [#see-7]
* arrayContains to find if a column or expression contains all elements of an array
* arrayOverlaps to find if a column or expression contains any elements of an array
Call Signature [#call-signature-4]
```ts
<TColumn>(column, values): SQL;
```
Test that the list passed as the second argument contains
all elements of a column or expression.
Throws [#throws-7]
The argument passed in the second array can't be empty:
if an empty is provided, this method will throw.
Examples [#examples-9]
```ts
// Select posts where its tags contain "Typescript", "ORM" or both,
// but filtering posts that have additional tags.
db.select().from(posts)
.where(arrayContained(posts.tags, ['Typescript', 'ORM']))
```
Type Parameters [#type-parameters-4]
TColumn [#tcolumn-1]
`TColumn` *extends* `Column`\<`ColumnBaseConfig`\<`ColumnDataType`, `string`>, `object`, `object`>
Parameters [#parameters-30]
column [#column-6]
`TColumn`
values [#values-4]
`SQLWrapper` | `Placeholder`\<`string`, `any`> | `TColumn`\[`"_"`]\[`"data"`]
Returns [#returns-30]
`SQL`
See [#see-8]
* arrayContains to find if a column or expression contains all elements of an array
* arrayOverlaps to find if a column or expression contains any elements of an array
Call Signature [#call-signature-5]
```ts
<T>(column, values): SQL;
```
Test that the list passed as the second argument contains
all elements of a column or expression.
Throws [#throws-8]
The argument passed in the second array can't be empty:
if an empty is provided, this method will throw.
Examples [#examples-10]
```ts
// Select posts where its tags contain "Typescript", "ORM" or both,
// but filtering posts that have additional tags.
db.select().from(posts)
.where(arrayContained(posts.tags, ['Typescript', 'ORM']))
```
Type Parameters [#type-parameters-5]
T [#t-3]
`T` *extends* `SQLWrapper`
Parameters [#parameters-31]
column [#column-7]
`Exclude`\<`T`,
\| `Column`\<`ColumnBaseConfig`\<`ColumnDataType`, `string`>, `object`, `object`>
\| `Aliased`\<`unknown`>>
values [#values-5]
`unknown`\[] | `SQLWrapper`
Returns [#returns-31]
`SQL`
See [#see-9]
* arrayContains to find if a column or expression contains all elements of an array
* arrayOverlaps to find if a column or expression contains any elements of an array
arrayOverlaps() [#arrayoverlaps]
```ts
arrayOverlaps: {
<T> (column, values): SQL;
<TColumn> (column, values): SQL;
<T> (column, values): SQL;
};
```
Call Signature [#call-signature-6]
```ts
<T>(column, values): SQL;
```
Test that a column or expression contains any elements of
the list passed as the second argument.
Throws [#throws-9]
The argument passed in the second array can't be empty:
if an empty is provided, this method will throw.
Examples [#examples-11]
```ts
// Select posts where its tags contain "Typescript", "ORM" or both.
db.select().from(posts)
.where(arrayOverlaps(posts.tags, ['Typescript', 'ORM']))
```
Type Parameters [#type-parameters-6]
T [#t-4]
`T`
Parameters [#parameters-32]
column [#column-8]
`Aliased`\<`T`>
values [#values-6]
`SQLWrapper` | `Placeholder`\<`string`, `any`> | `T`
Returns [#returns-32]
`SQL`
See [#see-10]
* arrayContains to find if a column or expression contains all elements of an array
* arrayContained to find if an array contains all elements of a column or expression
Call Signature [#call-signature-7]
```ts
<TColumn>(column, values): SQL;
```
Test that a column or expression contains any elements of
the list passed as the second argument.
Throws [#throws-10]
The argument passed in the second array can't be empty:
if an empty is provided, this method will throw.
Examples [#examples-12]
```ts
// Select posts where its tags contain "Typescript", "ORM" or both.
db.select().from(posts)
.where(arrayOverlaps(posts.tags, ['Typescript', 'ORM']))
```
Type Parameters [#type-parameters-7]
TColumn [#tcolumn-2]
`TColumn` *extends* `Column`\<`ColumnBaseConfig`\<`ColumnDataType`, `string`>, `object`, `object`>
Parameters [#parameters-33]
column [#column-9]
`TColumn`
values [#values-7]
`SQLWrapper` | `Placeholder`\<`string`, `any`> | `TColumn`\[`"_"`]\[`"data"`]
Returns [#returns-33]
`SQL`
See [#see-11]
* arrayContains to find if a column or expression contains all elements of an array
* arrayContained to find if an array contains all elements of a column or expression
Call Signature [#call-signature-8]
```ts
<T>(column, values): SQL;
```
Test that a column or expression contains any elements of
the list passed as the second argument.
Throws [#throws-11]
The argument passed in the second array can't be empty:
if an empty is provided, this method will throw.
Examples [#examples-13]
```ts
// Select posts where its tags contain "Typescript", "ORM" or both.
db.select().from(posts)
.where(arrayOverlaps(posts.tags, ['Typescript', 'ORM']))
```
Type Parameters [#type-parameters-8]
T [#t-5]
`T` *extends* `SQLWrapper`
Parameters [#parameters-34]
column [#column-10]
`Exclude`\<`T`,
\| `Column`\<`ColumnBaseConfig`\<`ColumnDataType`, `string`>, `object`, `object`>
\| `Aliased`\<`unknown`>>
values [#values-8]
`unknown`\[] | `SQLWrapper`
Returns [#returns-34]
`SQL`
See [#see-12]
* arrayContains to find if a column or expression contains all elements of an array
* arrayContained to find if an array contains all elements of a column or expression
Example [#example-10]
```ts
// Initialize operators
const ops = createEncryptionOperators(encryptionClient)
// Equality search - automatically encrypts and uses PostgreSQL operators
const results = await db
.select()
.from(usersTable)
.where(await ops.eq(usersTable.email, 'user@example.com'))
// Range query - automatically encrypts and uses eql_v2.gte()
const olderUsers = await db
.select()
.from(usersTable)
.where(await ops.gte(usersTable.age, 25))
```
# encryptedType
[**@cipherstash/stack**](../../../../..)
***
Function: encryptedType() [#function-encryptedtype]
```ts
function encryptedType<TData>(name, config?): PgCustomColumnBuilder<{
name: string;
dataType: "custom";
columnType: "PgCustomColumn";
data: TData;
driverParam: string;
enumValues: undefined;
}>;
```
Defined in: [packages/stack/src/drizzle/index.ts:103](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/index.ts#L103)
Creates an encrypted column type for Drizzle ORM with configurable searchable encryption options.
When data is encrypted, the actual stored value is an [EQL v2](/stack/reference/eql) encrypted composite type which includes any searchable encryption indexes defined for the column.
Importantly, the original data type is not known until it is decrypted. Therefore, this function allows specifying
the original data type via the `dataType` option in the configuration.
This ensures that when data is decrypted, it can be correctly interpreted as the intended TypeScript type.
Type Parameters [#type-parameters]
TData [#tdata]
`TData`
The TypeScript type of the data stored in the column
Parameters [#parameters]
name [#name]
`string`
The column name in the database
config? [#config]
[`EncryptedColumnConfig`](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig)
Optional configuration for data type and searchable encryption indexes
Returns [#returns]
`PgCustomColumnBuilder`\<\{
`name`: `string`;
`dataType`: `"custom"`;
`columnType`: `"PgCustomColumn"`;
`data`: `TData`;
`driverParam`: `string`;
`enumValues`: `undefined`;
}>
A Drizzle column type that can be used in pgTable definitions
Searchable Encryption Options [#searchable-encryption-options]
* `dataType`: Specifies the original data type of the column (e.g., 'string', 'number', 'json'). Default is 'string'.
* `freeTextSearch`: Enables free text search index. Can be a boolean for default options, or an object for custom configuration.
* `equality`: Enables equality index. Can be a boolean for default options, or an array of token filters.
* `orderAndRange`: Enables order and range index for sorting and range queries.
* `searchableJson`: Enables searchable JSON index for JSONB path queries on encrypted JSON columns.
See [EncryptedColumnConfig](/stack/reference/stack/latest/packages/stack/src/drizzle/type-aliases/EncryptedColumnConfig).
Example [#example]
Defining a drizzle table schema for postgres table with encrypted columns.
```typescript
import { pgTable, integer, timestamp } from 'drizzle-orm/pg-core'
import { encryptedType } from '@cipherstash/stack/drizzle'
const users = pgTable('users', {
email: encryptedType('email', {
freeTextSearch: true,
equality: true,
orderAndRange: true,
}),
age: encryptedType('age', {
dataType: 'number',
equality: true,
orderAndRange: true,
}),
profile: encryptedType('profile', {
dataType: 'json',
}),
})
```
# extractEncryptionSchema
[**@cipherstash/stack**](../../../../..)
***
Function: extractEncryptionSchema() [#function-extractencryptionschema]
```ts
function extractEncryptionSchema<T>(table): EncryptedTable<DrizzleEncryptedSchema<T>> & DrizzleEncryptedSchema<T>;
```
Defined in: [packages/stack/src/drizzle/schema-extraction.ts:42](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/schema-extraction.ts#L42)
Extracts an encryption schema from a Drizzle table definition.
This function identifies columns created with `encryptedType` and
builds a corresponding `EncryptedTable` with `encryptedColumn` definitions.
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `PgTable`\<`any`>
Parameters [#parameters]
table [#table]
`T`
The Drizzle table definition
Returns [#returns]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<`DrizzleEncryptedSchema`\<`T`>> & `DrizzleEncryptedSchema`\<`T`>
A EncryptedTable that can be used with encryption client initialization
Example [#example]
```ts
const drizzleUsersTable = pgTable('users', {
email: encryptedType('email', { freeTextSearch: true, equality: true }),
age: encryptedType('age', { dataType: 'number', orderAndRange: true }),
})
const encryptionSchema = extractEncryptionSchema(drizzleUsersTable)
const client = await createEncryptionClient({ schemas: [encryptionSchema.build()] })
```
# EncryptionConfigError
[**@cipherstash/stack**](../../../../..)
***
Class: EncryptionConfigError [#class-encryptionconfigerror]
Defined in: [packages/stack/src/drizzle/operators.ts:95](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/operators.ts#L95)
Create Drizzle query operators (`eq`, `lt`, `gt`, etc.) that work with
encrypted columns. The returned operators encrypt query values before
passing them to Drizzle, enabling searchable encryption in standard
Drizzle queries.
Extends [#extends]
* [`EncryptionOperatorError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError)
Constructors [#constructors]
Constructor [#constructor]
```ts
new EncryptionConfigError(message, context?): EncryptionConfigError;
```
Defined in: [packages/stack/src/drizzle/operators.ts:96](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/operators.ts#L96)
Parameters [#parameters]
message [#message]
`string`
context? [#context]
tableName? [#tablename]
`string`
columnName? [#columnname]
`string`
operator? [#operator]
`string`
Returns [#returns]
`EncryptionConfigError`
Overrides [#overrides]
[`EncryptionOperatorError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError).[`constructor`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError#constructor)
Properties [#properties]
context? [#context-1]
```ts
readonly optional context: {
tableName?: string;
columnName?: string;
operator?: string;
};
```
Defined in: [packages/stack/src/drizzle/operators.ts:84](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/operators.ts#L84)
tableName? [#tablename-1]
```ts
optional tableName: string;
```
columnName? [#columnname-1]
```ts
optional columnName: string;
```
operator? [#operator-1]
```ts
optional operator: string;
```
Inherited from [#inherited-from]
[`EncryptionOperatorError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError).[`context`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError#context)
***
stackTraceLimit [#stacktracelimit]
```ts
static stackTraceLimit: number;
```
Defined in: node\_modules/.bun/@types+node\@22.19.19/node\_modules/@types/node/globals.d.ts:68
The `Error.stackTraceLimit` property specifies the number of stack frames
collected by a stack trace (whether generated by `new Error().stack` or
`Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes
will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will
not capture any frames.
Inherited from [#inherited-from-1]
[`EncryptionOperatorError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError).[`stackTraceLimit`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError#stacktracelimit)
***
cause? [#cause]
```ts
optional cause: unknown;
```
Defined in: ../node\_modules/typescript/lib/lib.es2022.error.d.ts:26
Inherited from [#inherited-from-2]
[`EncryptionOperatorError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError).[`cause`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError#cause)
***
name [#name]
```ts
name: string;
```
Defined in: ../node\_modules/typescript/lib/lib.es5.d.ts:1076
Inherited from [#inherited-from-3]
[`EncryptionOperatorError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError).[`name`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError#name)
***
message [#message-1]
```ts
message: string;
```
Defined in: ../node\_modules/typescript/lib/lib.es5.d.ts:1077
Inherited from [#inherited-from-4]
[`EncryptionOperatorError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError).[`message`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError#message)
***
stack? [#stack]
```ts
optional stack: string;
```
Defined in: ../node\_modules/typescript/lib/lib.es5.d.ts:1078
Inherited from [#inherited-from-5]
[`EncryptionOperatorError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError).[`stack`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError#stack)
Methods [#methods]
captureStackTrace() [#capturestacktrace]
```ts
static captureStackTrace(targetObject, constructorOpt?): void;
```
Defined in: node\_modules/.bun/@types+node\@22.19.19/node\_modules/@types/node/globals.d.ts:52
Creates a `.stack` property on `targetObject`, which when accessed returns
a string representing the location in the code at which
`Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with
`${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames
above `constructorOpt`, including `constructorOpt`, will be omitted from the
generated stack trace.
The `constructorOpt` argument is useful for hiding implementation
details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
Parameters [#parameters-1]
targetObject [#targetobject]
`object`
constructorOpt? [#constructoropt]
`Function`
Returns [#returns-1]
`void`
Inherited from [#inherited-from-6]
[`EncryptionOperatorError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError).[`captureStackTrace`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError#capturestacktrace)
***
prepareStackTrace() [#preparestacktrace]
```ts
static prepareStackTrace(err, stackTraces): any;
```
Defined in: node\_modules/.bun/@types+node\@22.19.19/node\_modules/@types/node/globals.d.ts:56
Parameters [#parameters-2]
err [#err]
`Error`
stackTraces [#stacktraces]
`CallSite`\[]
Returns [#returns-2]
`any`
See [#see]
[https://v8.dev/docs/stack-trace-api#customizing-stack-traces](https://v8.dev/docs/stack-trace-api#customizing-stack-traces)
Inherited from [#inherited-from-7]
[`EncryptionOperatorError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError).[`prepareStackTrace`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError#preparestacktrace)
***
isError() [#iserror]
```ts
static isError(error): error is Error;
```
Defined in: ../node\_modules/typescript/lib/lib.esnext.error.d.ts:23
Indicates whether the argument provided is a built-in Error instance or not.
Parameters [#parameters-3]
error [#error]
`unknown`
Returns [#returns-3]
`error is Error`
Inherited from [#inherited-from-8]
[`EncryptionOperatorError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError).[`isError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionOperatorError#iserror)
# EncryptionOperatorError
[**@cipherstash/stack**](../../../../..)
***
Class: EncryptionOperatorError [#class-encryptionoperatorerror]
Defined in: [packages/stack/src/drizzle/operators.ts:81](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/operators.ts#L81)
Custom error types for better debugging
Extends [#extends]
* `Error`
Extended by [#extended-by]
* [`EncryptionConfigError`](/stack/reference/stack/latest/packages/stack/src/drizzle/classes/EncryptionConfigError)
Constructors [#constructors]
Constructor [#constructor]
```ts
new EncryptionOperatorError(message, context?): EncryptionOperatorError;
```
Defined in: [packages/stack/src/drizzle/operators.ts:82](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/operators.ts#L82)
Parameters [#parameters]
message [#message]
`string`
context? [#context]
tableName? [#tablename]
`string`
columnName? [#columnname]
`string`
operator? [#operator]
`string`
Returns [#returns]
`EncryptionOperatorError`
Overrides [#overrides]
```ts
Error.constructor
```
Properties [#properties]
context? [#context-1]
```ts
readonly optional context: {
tableName?: string;
columnName?: string;
operator?: string;
};
```
Defined in: [packages/stack/src/drizzle/operators.ts:84](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/operators.ts#L84)
tableName? [#tablename-1]
```ts
optional tableName: string;
```
columnName? [#columnname-1]
```ts
optional columnName: string;
```
operator? [#operator-1]
```ts
optional operator: string;
```
***
stackTraceLimit [#stacktracelimit]
```ts
static stackTraceLimit: number;
```
Defined in: node\_modules/.bun/@types+node\@22.19.19/node\_modules/@types/node/globals.d.ts:68
The `Error.stackTraceLimit` property specifies the number of stack frames
collected by a stack trace (whether generated by `new Error().stack` or
`Error.captureStackTrace(obj)`).
The default value is `10` but may be set to any valid JavaScript number. Changes
will affect any stack trace captured *after* the value has been changed.
If set to a non-number value, or set to a negative number, stack traces will
not capture any frames.
Inherited from [#inherited-from]
```ts
Error.stackTraceLimit
```
***
cause? [#cause]
```ts
optional cause: unknown;
```
Defined in: ../node\_modules/typescript/lib/lib.es2022.error.d.ts:26
Inherited from [#inherited-from-1]
```ts
Error.cause
```
***
name [#name]
```ts
name: string;
```
Defined in: ../node\_modules/typescript/lib/lib.es5.d.ts:1076
Inherited from [#inherited-from-2]
```ts
Error.name
```
***
message [#message-1]
```ts
message: string;
```
Defined in: ../node\_modules/typescript/lib/lib.es5.d.ts:1077
Inherited from [#inherited-from-3]
```ts
Error.message
```
***
stack? [#stack]
```ts
optional stack: string;
```
Defined in: ../node\_modules/typescript/lib/lib.es5.d.ts:1078
Inherited from [#inherited-from-4]
```ts
Error.stack
```
Methods [#methods]
captureStackTrace() [#capturestacktrace]
```ts
static captureStackTrace(targetObject, constructorOpt?): void;
```
Defined in: node\_modules/.bun/@types+node\@22.19.19/node\_modules/@types/node/globals.d.ts:52
Creates a `.stack` property on `targetObject`, which when accessed returns
a string representing the location in the code at which
`Error.captureStackTrace()` was called.
```js
const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack; // Similar to `new Error().stack`
```
The first line of the trace will be prefixed with
`${myObject.name}: ${myObject.message}`.
The optional `constructorOpt` argument accepts a function. If given, all frames
above `constructorOpt`, including `constructorOpt`, will be omitted from the
generated stack trace.
The `constructorOpt` argument is useful for hiding implementation
details of error generation from the user. For instance:
```js
function a() {
b();
}
function b() {
c();
}
function c() {
// Create an error without stack trace to avoid calculating the stack trace twice.
const { stackTraceLimit } = Error;
Error.stackTraceLimit = 0;
const error = new Error();
Error.stackTraceLimit = stackTraceLimit;
// Capture the stack trace above function b
Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace
throw error;
}
a();
```
Parameters [#parameters-1]
targetObject [#targetobject]
`object`
constructorOpt? [#constructoropt]
`Function`
Returns [#returns-1]
`void`
Inherited from [#inherited-from-5]
```ts
Error.captureStackTrace
```
***
prepareStackTrace() [#preparestacktrace]
```ts
static prepareStackTrace(err, stackTraces): any;
```
Defined in: node\_modules/.bun/@types+node\@22.19.19/node\_modules/@types/node/globals.d.ts:56
Parameters [#parameters-2]
err [#err]
`Error`
stackTraces [#stacktraces]
`CallSite`\[]
Returns [#returns-2]
`any`
See [#see]
[https://v8.dev/docs/stack-trace-api#customizing-stack-traces](https://v8.dev/docs/stack-trace-api#customizing-stack-traces)
Inherited from [#inherited-from-6]
```ts
Error.prepareStackTrace
```
***
isError() [#iserror]
```ts
static isError(error): error is Error;
```
Defined in: ../node\_modules/typescript/lib/lib.esnext.error.d.ts:23
Indicates whether the argument provided is a built-in Error instance or not.
Parameters [#parameters-3]
error [#error]
`unknown`
Returns [#returns-3]
`error is Error`
Inherited from [#inherited-from-7]
```ts
Error.isError
```
# EncryptedColumnConfig
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptedColumnConfig [#type-alias-encryptedcolumnconfig]
```ts
type EncryptedColumnConfig = {
dataType?: CastAs;
freeTextSearch?: | boolean
| MatchIndexOpts;
equality?: | boolean
| TokenFilter[];
orderAndRange?: boolean;
searchableJson?: boolean;
};
```
Defined in: [packages/stack/src/drizzle/index.ts:23](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/index.ts#L23)
Configuration for encrypted column indexes and data types
Properties [#properties]
dataType? [#datatype]
```ts
optional dataType: CastAs;
```
Defined in: [packages/stack/src/drizzle/index.ts:27](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/index.ts#L27)
Data type for the column (default: 'string')
***
freeTextSearch? [#freetextsearch]
```ts
optional freeTextSearch:
| boolean
| MatchIndexOpts;
```
Defined in: [packages/stack/src/drizzle/index.ts:31](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/index.ts#L31)
Enable free text search. Can be a boolean for default options, or an object for custom configuration.
***
equality? [#equality]
```ts
optional equality:
| boolean
| TokenFilter[];
```
Defined in: [packages/stack/src/drizzle/index.ts:35](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/index.ts#L35)
Enable equality index. Can be a boolean for default options, or an array of token filters.
***
orderAndRange? [#orderandrange]
```ts
optional orderAndRange: boolean;
```
Defined in: [packages/stack/src/drizzle/index.ts:39](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/index.ts#L39)
Enable order and range index for sorting and range queries.
***
searchableJson? [#searchablejson]
```ts
optional searchableJson: boolean;
```
Defined in: [packages/stack/src/drizzle/index.ts:44](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/drizzle/index.ts#L44)
Enable searchable JSON index for JSONB path queries.
Requires dataType: 'json'.
# BulkDecryptModelsOperation
[**@cipherstash/stack**](../../../../..)
***
Class: BulkDecryptModelsOperation [#class-bulkdecryptmodelsoperationt]
Defined in: [packages/stack/src/dynamodb/operations/bulk-decrypt-models.ts:13](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/bulk-decrypt-models.ts#L13)
Extends [#extends]
* `DynamoDBOperation`\<[`Decrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)\<`T`>\[]>
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `Record`\<`string`, `unknown`>
Constructors [#constructors]
Constructor [#constructor]
```ts
new BulkDecryptModelsOperation<T>(
encryptionClient,
items,
table,
options?): BulkDecryptModelsOperation<T>;
```
Defined in: [packages/stack/src/dynamodb/operations/bulk-decrypt-models.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/bulk-decrypt-models.ts#L20)
Parameters [#parameters]
encryptionClient [#encryptionclient]
[`EncryptionClient`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient)
items [#items]
`Record`\<`string`, `unknown`>\[]
table [#table]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)>
options? [#options]
`DynamoDBOperationOptions`
Returns [#returns]
`BulkDecryptModelsOperation`\<`T`>
Overrides [#overrides]
```ts
DynamoDBOperation<Decrypted<T>[]>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/dynamodb/operations/base-operation.ts:32](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/base-operation.ts#L32)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
DynamoDBOperation.audit
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/dynamodb/operations/base-operation.ts:54](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/base-operation.ts#L54)
Make the operation thenable
Type Parameters [#type-parameters-1]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`Decrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)\<`T`>\[], [`EncryptedDynamoDBError`](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-2]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-1]
```ts
DynamoDBOperation.then
```
***
execute() [#execute]
```ts
execute(): Promise<Result<Decrypted<T>[], EncryptedDynamoDBError>>;
```
Defined in: [packages/stack/src/dynamodb/operations/bulk-decrypt-models.ts:32](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/bulk-decrypt-models.ts#L32)
Execute the operation and return a Result
Returns [#returns-3]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`Decrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)\<`T`>\[], [`EncryptedDynamoDBError`](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBError)>>
Overrides [#overrides-1]
```ts
DynamoDBOperation.execute
```
# BulkEncryptModelsOperation
[**@cipherstash/stack**](../../../../..)
***
Class: BulkEncryptModelsOperation [#class-bulkencryptmodelsoperationt]
Defined in: [packages/stack/src/dynamodb/operations/bulk-encrypt-models.ts:12](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/bulk-encrypt-models.ts#L12)
Extends [#extends]
* `DynamoDBOperation`\<`T`\[]>
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `Record`\<`string`, `unknown`>
Constructors [#constructors]
Constructor [#constructor]
```ts
new BulkEncryptModelsOperation<T>(
encryptionClient,
items,
table,
options?): BulkEncryptModelsOperation<T>;
```
Defined in: [packages/stack/src/dynamodb/operations/bulk-encrypt-models.ts:19](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/bulk-encrypt-models.ts#L19)
Parameters [#parameters]
encryptionClient [#encryptionclient]
[`EncryptionClient`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient)
items [#items]
`T`\[]
table [#table]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)>
options? [#options]
`DynamoDBOperationOptions`
Returns [#returns]
`BulkEncryptModelsOperation`\<`T`>
Overrides [#overrides]
```ts
DynamoDBOperation<T[]>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/dynamodb/operations/base-operation.ts:32](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/base-operation.ts#L32)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
DynamoDBOperation.audit
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/dynamodb/operations/base-operation.ts:54](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/base-operation.ts#L54)
Make the operation thenable
Type Parameters [#type-parameters-1]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<`T`\[], [`EncryptedDynamoDBError`](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-2]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-1]
```ts
DynamoDBOperation.then
```
***
execute() [#execute]
```ts
execute(): Promise<Result<T[], EncryptedDynamoDBError>>;
```
Defined in: [packages/stack/src/dynamodb/operations/bulk-encrypt-models.ts:31](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/bulk-encrypt-models.ts#L31)
Execute the operation and return a Result
Returns [#returns-3]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<`T`\[], [`EncryptedDynamoDBError`](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBError)>>
Overrides [#overrides-1]
```ts
DynamoDBOperation.execute
```
# DecryptModelOperation
[**@cipherstash/stack**](../../../../..)
***
Class: DecryptModelOperation [#class-decryptmodeloperationt]
Defined in: [packages/stack/src/dynamodb/operations/decrypt-model.ts:13](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/decrypt-model.ts#L13)
Extends [#extends]
* `DynamoDBOperation`\<[`Decrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)\<`T`>>
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `Record`\<`string`, `unknown`>
Constructors [#constructors]
Constructor [#constructor]
```ts
new DecryptModelOperation<T>(
encryptionClient,
item,
table,
options?): DecryptModelOperation<T>;
```
Defined in: [packages/stack/src/dynamodb/operations/decrypt-model.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/decrypt-model.ts#L20)
Parameters [#parameters]
encryptionClient [#encryptionclient]
[`EncryptionClient`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient)
item [#item]
`Record`\<`string`,
\| [`EncryptedValue`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedValue)
\| `unknown`>
table [#table]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)>
options? [#options]
`DynamoDBOperationOptions`
Returns [#returns]
`DecryptModelOperation`\<`T`>
Overrides [#overrides]
```ts
DynamoDBOperation<Decrypted<T>>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/dynamodb/operations/base-operation.ts:32](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/base-operation.ts#L32)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
DynamoDBOperation.audit
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/dynamodb/operations/base-operation.ts:54](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/base-operation.ts#L54)
Make the operation thenable
Type Parameters [#type-parameters-1]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`Decrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)\<`T`>, [`EncryptedDynamoDBError`](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-2]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-1]
```ts
DynamoDBOperation.then
```
***
execute() [#execute]
```ts
execute(): Promise<Result<Decrypted<T>, EncryptedDynamoDBError>>;
```
Defined in: [packages/stack/src/dynamodb/operations/decrypt-model.ts:32](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/decrypt-model.ts#L32)
Execute the operation and return a Result
Returns [#returns-3]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`Decrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)\<`T`>, [`EncryptedDynamoDBError`](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBError)>>
Overrides [#overrides-1]
```ts
DynamoDBOperation.execute
```
# EncryptModelOperation
[**@cipherstash/stack**](../../../../..)
***
Class: EncryptModelOperation [#class-encryptmodeloperationt]
Defined in: [packages/stack/src/dynamodb/operations/encrypt-model.ts:12](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/encrypt-model.ts#L12)
Extends [#extends]
* `DynamoDBOperation`\<`T`>
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `Record`\<`string`, `unknown`>
Constructors [#constructors]
Constructor [#constructor]
```ts
new EncryptModelOperation<T>(
encryptionClient,
item,
table,
options?): EncryptModelOperation<T>;
```
Defined in: [packages/stack/src/dynamodb/operations/encrypt-model.ts:19](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/encrypt-model.ts#L19)
Parameters [#parameters]
encryptionClient [#encryptionclient]
[`EncryptionClient`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient)
item [#item]
`T`
table [#table]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)>
options? [#options]
`DynamoDBOperationOptions`
Returns [#returns]
`EncryptModelOperation`\<`T`>
Overrides [#overrides]
```ts
DynamoDBOperation<T>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/dynamodb/operations/base-operation.ts:32](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/base-operation.ts#L32)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
DynamoDBOperation.audit
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/dynamodb/operations/base-operation.ts:54](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/base-operation.ts#L54)
Make the operation thenable
Type Parameters [#type-parameters-1]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<`T`, [`EncryptedDynamoDBError`](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-2]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-1]
```ts
DynamoDBOperation.then
```
***
execute() [#execute]
```ts
execute(): Promise<Result<T, EncryptedDynamoDBError>>;
```
Defined in: [packages/stack/src/dynamodb/operations/encrypt-model.ts:31](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/operations/encrypt-model.ts#L31)
Execute the operation and return a Result
Returns [#returns-3]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<`T`, [`EncryptedDynamoDBError`](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBError)>>
Overrides [#overrides-1]
```ts
DynamoDBOperation.execute
```
# encryptedDynamoDB
[**@cipherstash/stack**](../../../../..)
***
Function: encryptedDynamoDB() [#function-encrypteddynamodb]
```ts
function encryptedDynamoDB(config): EncryptedDynamoDBInstance;
```
Defined in: [packages/stack/src/dynamodb/index.ts:39](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/index.ts#L39)
Create an encrypted DynamoDB helper bound to an `EncryptionClient`.
Returns an object with `encryptModel`, `decryptModel`, `bulkEncryptModels`,
and `bulkDecryptModels` methods that transparently encrypt/decrypt DynamoDB
items according to the provided table schema.
Parameters [#parameters]
config [#config]
[`EncryptedDynamoDBConfig`](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBConfig)
Configuration containing the `encryptionClient` and optional
logging / error-handling callbacks.
Returns [#returns]
[`EncryptedDynamoDBInstance`](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBInstance)
An [EncryptedDynamoDBInstance](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBInstance) with encrypt/decrypt operations.
Example [#example]
```typescript
import { Encryption } from "@cipherstash/stack"
import { encryptedDynamoDB } from "@cipherstash/stack/dynamodb"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality(),
})
const client = await Encryption({ schemas: [users] })
const dynamo = encryptedDynamoDB({ encryptionClient: client })
const encrypted = await dynamo.encryptModel({ email: "a@b.com" }, users)
```
# EncryptedDynamoDBConfig
[**@cipherstash/stack**](../../../../..)
***
Interface: EncryptedDynamoDBConfig [#interface-encrypteddynamodbconfig]
Defined in: [packages/stack/src/dynamodb/types.ts:10](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/types.ts#L10)
Properties [#properties]
encryptionClient [#encryptionclient]
```ts
encryptionClient: EncryptionClient;
```
Defined in: [packages/stack/src/dynamodb/types.ts:11](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/types.ts#L11)
***
options? [#options]
```ts
optional options: {
logger?: {
error: (message, error) => void;
};
errorHandler?: (error) => void;
};
```
Defined in: [packages/stack/src/dynamodb/types.ts:12](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/types.ts#L12)
logger? [#logger]
```ts
optional logger: {
error: (message, error) => void;
};
```
logger.error() [#loggererror]
```ts
error: (message, error) => void;
```
Parameters [#parameters]
message [#message]
`string`
error [#error]
`Error`
Returns [#returns]
`void`
errorHandler()? [#errorhandler]
```ts
optional errorHandler: (error) => void;
```
Parameters [#parameters-1]
error [#error-1]
[`EncryptedDynamoDBError`](/stack/reference/stack/latest/packages/stack/src/dynamodb/interfaces/EncryptedDynamoDBError)
Returns [#returns-1]
`void`
# EncryptedDynamoDBError
[**@cipherstash/stack**](../../../../..)
***
Interface: EncryptedDynamoDBError [#interface-encrypteddynamodberror]
Defined in: [packages/stack/src/dynamodb/types.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/types.ts#L20)
Extends [#extends]
* `Error`
Properties [#properties]
code [#code]
```ts
code: ProtectErrorCode | "DYNAMODB_ENCRYPTION_ERROR";
```
Defined in: [packages/stack/src/dynamodb/types.ts:21](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/types.ts#L21)
***
details? [#details]
```ts
optional details: Record<string, unknown>;
```
Defined in: [packages/stack/src/dynamodb/types.ts:22](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/types.ts#L22)
***
cause? [#cause]
```ts
optional cause: unknown;
```
Defined in: ../node\_modules/typescript/lib/lib.es2022.error.d.ts:26
Inherited from [#inherited-from]
```ts
Error.cause
```
***
name [#name]
```ts
name: string;
```
Defined in: ../node\_modules/typescript/lib/lib.es5.d.ts:1076
Inherited from [#inherited-from-1]
```ts
Error.name
```
***
message [#message]
```ts
message: string;
```
Defined in: ../node\_modules/typescript/lib/lib.es5.d.ts:1077
Inherited from [#inherited-from-2]
```ts
Error.message
```
***
stack? [#stack]
```ts
optional stack: string;
```
Defined in: ../node\_modules/typescript/lib/lib.es5.d.ts:1078
Inherited from [#inherited-from-3]
```ts
Error.stack
```
# EncryptedDynamoDBInstance
[**@cipherstash/stack**](../../../../..)
***
Interface: EncryptedDynamoDBInstance [#interface-encrypteddynamodbinstance]
Defined in: [packages/stack/src/dynamodb/types.ts:25](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/types.ts#L25)
Methods [#methods]
encryptModel() [#encryptmodel]
```ts
encryptModel<T>(item, table): EncryptModelOperation<T>;
```
Defined in: [packages/stack/src/dynamodb/types.ts:26](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/types.ts#L26)
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `Record`\<`string`, `unknown`>
Parameters [#parameters]
item [#item]
`T`
table [#table]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)>
Returns [#returns]
[`EncryptModelOperation`](/stack/reference/stack/latest/packages/stack/src/dynamodb/classes/EncryptModelOperation)\<`T`>
***
bulkEncryptModels() [#bulkencryptmodels]
```ts
bulkEncryptModels<T>(items, table): BulkEncryptModelsOperation<T>;
```
Defined in: [packages/stack/src/dynamodb/types.ts:31](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/types.ts#L31)
Type Parameters [#type-parameters-1]
T [#t-1]
`T` *extends* `Record`\<`string`, `unknown`>
Parameters [#parameters-1]
items [#items]
`T`\[]
table [#table-1]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)>
Returns [#returns-1]
[`BulkEncryptModelsOperation`](/stack/reference/stack/latest/packages/stack/src/dynamodb/classes/BulkEncryptModelsOperation)\<`T`>
***
decryptModel() [#decryptmodel]
```ts
decryptModel<T>(item, table): DecryptModelOperation<T>;
```
Defined in: [packages/stack/src/dynamodb/types.ts:36](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/types.ts#L36)
Type Parameters [#type-parameters-2]
T [#t-2]
`T` *extends* `Record`\<`string`, `unknown`>
Parameters [#parameters-2]
item [#item-1]
`Record`\<`string`,
\| [`EncryptedValue`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedValue)
\| `unknown`>
table [#table-2]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)>
Returns [#returns-2]
[`DecryptModelOperation`](/stack/reference/stack/latest/packages/stack/src/dynamodb/classes/DecryptModelOperation)\<`T`>
***
bulkDecryptModels() [#bulkdecryptmodels]
```ts
bulkDecryptModels<T>(items, table): BulkDecryptModelsOperation<T>;
```
Defined in: [packages/stack/src/dynamodb/types.ts:41](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/dynamodb/types.ts#L41)
Type Parameters [#type-parameters-3]
T [#t-3]
`T` *extends* `Record`\<`string`, `unknown`>
Parameters [#parameters-3]
items [#items-1]
`Record`\<`string`, `unknown`>\[]
table [#table-3]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)>
Returns [#returns-3]
[`BulkDecryptModelsOperation`](/stack/reference/stack/latest/packages/stack/src/dynamodb/classes/BulkDecryptModelsOperation)\<`T`>
# BatchEncryptQueryOperation
[**@cipherstash/stack**](../../../../..)
***
Class: BatchEncryptQueryOperation [#class-batchencryptqueryoperation]
Defined in: [packages/stack/src/encryption/operations/batch-encrypt-query.ts:94](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/batch-encrypt-query.ts#L94)
Extends [#extends]
* `EncryptionOperation`\<[`EncryptedQueryResult`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedQueryResult)\[]>
Constructors [#constructors]
Constructor [#constructor]
```ts
new BatchEncryptQueryOperation(client, terms): BatchEncryptQueryOperation;
```
Defined in: [packages/stack/src/encryption/operations/batch-encrypt-query.ts:97](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/batch-encrypt-query.ts#L97)
Parameters [#parameters]
client [#client]
[`Client`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Client)
terms [#terms]
readonly [`ScalarQueryTerm`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/ScalarQueryTerm)\[]
Returns [#returns]
`BatchEncryptQueryOperation`
Overrides [#overrides]
```ts
EncryptionOperation<
EncryptedQueryResult[]
>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L20)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Configuration for ZeroKMS audit logging
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
EncryptionOperation.audit
```
***
getAuditData() [#getauditdata]
```ts
getAuditData(): AuditData;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:28](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L28)
Get the audit data for this operation.
Returns [#returns-2]
`AuditData`
Inherited from [#inherited-from-1]
```ts
EncryptionOperation.getAuditData
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:42](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L42)
Make the operation thenable
Type Parameters [#type-parameters]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`EncryptedQueryResult`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedQueryResult)\[], [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-3]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-2]
```ts
EncryptionOperation.then
```
***
withLockContext() [#withlockcontext]
```ts
withLockContext(lockContext): BatchEncryptQueryOperationWithLockContext;
```
Defined in: [packages/stack/src/encryption/operations/batch-encrypt-query.ts:104](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/batch-encrypt-query.ts#L104)
Parameters [#parameters-3]
lockContext [#lockcontext]
[`LockContext`](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
Returns [#returns-4]
`BatchEncryptQueryOperationWithLockContext`
***
execute() [#execute]
```ts
execute(): Promise<Result<EncryptedQueryResult[], EncryptionError>>;
```
Defined in: [packages/stack/src/encryption/operations/batch-encrypt-query.ts:115](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/batch-encrypt-query.ts#L115)
Execute the operation and return a Result
Returns [#returns-5]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`EncryptedQueryResult`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedQueryResult)\[], [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>>
Overrides [#overrides-1]
```ts
EncryptionOperation.execute
```
# BulkDecryptModelsOperation
[**@cipherstash/stack**](../../../../..)
***
Class: BulkDecryptModelsOperation [#class-bulkdecryptmodelsoperationt]
Defined in: [packages/stack/src/encryption/operations/bulk-decrypt-models.ts:14](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-decrypt-models.ts#L14)
Extends [#extends]
* `EncryptionOperation`\<[`Decrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)\<`T`>\[]>
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `Record`\<`string`, `unknown`>
Constructors [#constructors]
Constructor [#constructor]
```ts
new BulkDecryptModelsOperation<T>(client, models): BulkDecryptModelsOperation<T>;
```
Defined in: [packages/stack/src/encryption/operations/bulk-decrypt-models.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-decrypt-models.ts#L20)
Parameters [#parameters]
client [#client]
[`Client`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Client)
models [#models]
`T`\[]
Returns [#returns]
`BulkDecryptModelsOperation`\<`T`>
Overrides [#overrides]
```ts
EncryptionOperation<Decrypted<T>[]>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L20)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Configuration for ZeroKMS audit logging
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
EncryptionOperation.audit
```
***
getAuditData() [#getauditdata]
```ts
getAuditData(): AuditData;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:28](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L28)
Get the audit data for this operation.
Returns [#returns-2]
`AuditData`
Inherited from [#inherited-from-1]
```ts
EncryptionOperation.getAuditData
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:42](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L42)
Make the operation thenable
Type Parameters [#type-parameters-1]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`Decrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)\<`T`>\[], [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-3]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-2]
```ts
EncryptionOperation.then
```
***
withLockContext() [#withlockcontext]
```ts
withLockContext(lockContext): BulkDecryptModelsOperationWithLockContext<T>;
```
Defined in: [packages/stack/src/encryption/operations/bulk-decrypt-models.ts:26](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-decrypt-models.ts#L26)
Parameters [#parameters-3]
lockContext [#lockcontext]
[`LockContext`](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
Returns [#returns-4]
`BulkDecryptModelsOperationWithLockContext`\<`T`>
***
execute() [#execute]
```ts
execute(): Promise<Result<Decrypted<T>[], EncryptionError>>;
```
Defined in: [packages/stack/src/encryption/operations/bulk-decrypt-models.ts:32](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-decrypt-models.ts#L32)
Execute the operation and return a Result
Returns [#returns-5]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`Decrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)\<`T`>\[], [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>>
Overrides [#overrides-1]
```ts
EncryptionOperation.execute
```
***
getOperation() [#getoperation]
```ts
getOperation(): {
client: Client;
models: T[];
};
```
Defined in: [packages/stack/src/encryption/operations/bulk-decrypt-models.ts:63](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-decrypt-models.ts#L63)
Returns [#returns-6]
```ts
{
client: Client;
models: T[];
}
```
client [#client-1]
```ts
client: Client;
```
models [#models-1]
```ts
models: T[];
```
# BulkDecryptOperation
[**@cipherstash/stack**](../../../../..)
***
Class: BulkDecryptOperation [#class-bulkdecryptoperation]
Defined in: [packages/stack/src/encryption/operations/bulk-decrypt.ts:57](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-decrypt.ts#L57)
Extends [#extends]
* `EncryptionOperation`\<[`BulkDecryptedData`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkDecryptedData)>
Constructors [#constructors]
Constructor [#constructor]
```ts
new BulkDecryptOperation(client, encryptedPayloads): BulkDecryptOperation;
```
Defined in: [packages/stack/src/encryption/operations/bulk-decrypt.ts:61](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-decrypt.ts#L61)
Parameters [#parameters]
client [#client]
[`Client`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Client)
encryptedPayloads [#encryptedpayloads]
[`BulkDecryptPayload`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkDecryptPayload)
Returns [#returns]
`BulkDecryptOperation`
Overrides [#overrides]
```ts
EncryptionOperation<BulkDecryptedData>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L20)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Configuration for ZeroKMS audit logging
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
EncryptionOperation.audit
```
***
getAuditData() [#getauditdata]
```ts
getAuditData(): AuditData;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:28](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L28)
Get the audit data for this operation.
Returns [#returns-2]
`AuditData`
Inherited from [#inherited-from-1]
```ts
EncryptionOperation.getAuditData
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:42](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L42)
Make the operation thenable
Type Parameters [#type-parameters]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`BulkDecryptedData`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkDecryptedData), [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-3]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-2]
```ts
EncryptionOperation.then
```
***
withLockContext() [#withlockcontext]
```ts
withLockContext(lockContext): BulkDecryptOperationWithLockContext;
```
Defined in: [packages/stack/src/encryption/operations/bulk-decrypt.ts:67](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-decrypt.ts#L67)
Parameters [#parameters-3]
lockContext [#lockcontext]
[`LockContext`](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
Returns [#returns-4]
`BulkDecryptOperationWithLockContext`
***
execute() [#execute]
```ts
execute(): Promise<Result<BulkDecryptedData, EncryptionError>>;
```
Defined in: [packages/stack/src/encryption/operations/bulk-decrypt.ts:73](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-decrypt.ts#L73)
Execute the operation and return a Result
Returns [#returns-5]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`BulkDecryptedData`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkDecryptedData), [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>>
Overrides [#overrides-1]
```ts
EncryptionOperation.execute
```
***
getOperation() [#getoperation]
```ts
getOperation(): {
client: Client;
encryptedPayloads: BulkDecryptPayload;
};
```
Defined in: [packages/stack/src/encryption/operations/bulk-decrypt.ts:115](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-decrypt.ts#L115)
Returns [#returns-6]
```ts
{
client: Client;
encryptedPayloads: BulkDecryptPayload;
}
```
client [#client-1]
```ts
client: Client;
```
encryptedPayloads [#encryptedpayloads-1]
```ts
encryptedPayloads: BulkDecryptPayload;
```
# BulkEncryptModelsOperation
[**@cipherstash/stack**](../../../../..)
***
Class: BulkEncryptModelsOperation [#class-bulkencryptmodelsoperationt]
Defined in: [packages/stack/src/encryption/operations/bulk-encrypt-models.ts:15](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-encrypt-models.ts#L15)
Extends [#extends]
* `EncryptionOperation`\<`T`\[]>
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `Record`\<`string`, `unknown`>
Constructors [#constructors]
Constructor [#constructor]
```ts
new BulkEncryptModelsOperation<T>(
client,
models,
table): BulkEncryptModelsOperation<T>;
```
Defined in: [packages/stack/src/encryption/operations/bulk-encrypt-models.ts:22](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-encrypt-models.ts#L22)
Parameters [#parameters]
client [#client]
[`Client`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Client)
models [#models]
`Record`\<`string`, `unknown`>\[]
table [#table]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)>
Returns [#returns]
`BulkEncryptModelsOperation`\<`T`>
Overrides [#overrides]
```ts
EncryptionOperation<T[]>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L20)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Configuration for ZeroKMS audit logging
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
EncryptionOperation.audit
```
***
getAuditData() [#getauditdata]
```ts
getAuditData(): AuditData;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:28](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L28)
Get the audit data for this operation.
Returns [#returns-2]
`AuditData`
Inherited from [#inherited-from-1]
```ts
EncryptionOperation.getAuditData
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:42](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L42)
Make the operation thenable
Type Parameters [#type-parameters-1]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<`T`\[], [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-3]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-2]
```ts
EncryptionOperation.then
```
***
withLockContext() [#withlockcontext]
```ts
withLockContext(lockContext): BulkEncryptModelsOperationWithLockContext<T>;
```
Defined in: [packages/stack/src/encryption/operations/bulk-encrypt-models.ts:33](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-encrypt-models.ts#L33)
Parameters [#parameters-3]
lockContext [#lockcontext]
[`LockContext`](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
Returns [#returns-4]
`BulkEncryptModelsOperationWithLockContext`\<`T`>
***
execute() [#execute]
```ts
execute(): Promise<Result<T[], EncryptionError>>;
```
Defined in: [packages/stack/src/encryption/operations/bulk-encrypt-models.ts:39](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-encrypt-models.ts#L39)
Execute the operation and return a Result
Returns [#returns-5]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<`T`\[], [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>>
Overrides [#overrides-1]
```ts
EncryptionOperation.execute
```
***
getOperation() [#getoperation]
```ts
getOperation(): {
client: Client;
models: Record<string, unknown>[];
table: EncryptedTable<EncryptedTableColumn>;
};
```
Defined in: [packages/stack/src/encryption/operations/bulk-encrypt-models.ts:76](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-encrypt-models.ts#L76)
Returns [#returns-6]
```ts
{
client: Client;
models: Record<string, unknown>[];
table: EncryptedTable<EncryptedTableColumn>;
}
```
client [#client-1]
```ts
client: Client;
```
models [#models-1]
```ts
models: Record<string, unknown>[];
```
table [#table-1]
```ts
table: EncryptedTable<EncryptedTableColumn>;
```
# BulkEncryptOperation
[**@cipherstash/stack**](../../../../..)
***
Class: BulkEncryptOperation [#class-bulkencryptoperation]
Defined in: [packages/stack/src/encryption/operations/bulk-encrypt.ts:64](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-encrypt.ts#L64)
Extends [#extends]
* `EncryptionOperation`\<[`BulkEncryptedData`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkEncryptedData)>
Constructors [#constructors]
Constructor [#constructor]
```ts
new BulkEncryptOperation(
client,
plaintexts,
opts): BulkEncryptOperation;
```
Defined in: [packages/stack/src/encryption/operations/bulk-encrypt.ts:70](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-encrypt.ts#L70)
Parameters [#parameters]
client [#client]
[`Client`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Client)
plaintexts [#plaintexts]
[`BulkEncryptPayload`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkEncryptPayload)
opts [#opts]
[`EncryptOptions`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptOptions)
Returns [#returns]
`BulkEncryptOperation`
Overrides [#overrides]
```ts
EncryptionOperation<BulkEncryptedData>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L20)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Configuration for ZeroKMS audit logging
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
EncryptionOperation.audit
```
***
getAuditData() [#getauditdata]
```ts
getAuditData(): AuditData;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:28](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L28)
Get the audit data for this operation.
Returns [#returns-2]
`AuditData`
Inherited from [#inherited-from-1]
```ts
EncryptionOperation.getAuditData
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:42](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L42)
Make the operation thenable
Type Parameters [#type-parameters]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`BulkEncryptedData`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkEncryptedData), [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-3]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-2]
```ts
EncryptionOperation.then
```
***
withLockContext() [#withlockcontext]
```ts
withLockContext(lockContext): BulkEncryptOperationWithLockContext;
```
Defined in: [packages/stack/src/encryption/operations/bulk-encrypt.ts:82](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-encrypt.ts#L82)
Parameters [#parameters-3]
lockContext [#lockcontext]
[`LockContext`](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
Returns [#returns-4]
`BulkEncryptOperationWithLockContext`
***
execute() [#execute]
```ts
execute(): Promise<Result<BulkEncryptedData, EncryptionError>>;
```
Defined in: [packages/stack/src/encryption/operations/bulk-encrypt.ts:88](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-encrypt.ts#L88)
Execute the operation and return a Result
Returns [#returns-5]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`BulkEncryptedData`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkEncryptedData), [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>>
Overrides [#overrides-1]
```ts
EncryptionOperation.execute
```
***
getOperation() [#getoperation]
```ts
getOperation(): {
client: Client;
plaintexts: BulkEncryptPayload;
column: | EncryptedColumn
| EncryptedField;
table: EncryptedTable<EncryptedTableColumn>;
};
```
Defined in: [packages/stack/src/encryption/operations/bulk-encrypt.ts:139](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/bulk-encrypt.ts#L139)
Returns [#returns-6]
```ts
{
client: Client;
plaintexts: BulkEncryptPayload;
column: | EncryptedColumn
| EncryptedField;
table: EncryptedTable<EncryptedTableColumn>;
}
```
client [#client-1]
```ts
client: Client;
```
plaintexts [#plaintexts-1]
```ts
plaintexts: BulkEncryptPayload;
```
column [#column]
```ts
column:
| EncryptedColumn
| EncryptedField;
```
table [#table]
```ts
table: EncryptedTable<EncryptedTableColumn>;
```
# DecryptModelOperation
[**@cipherstash/stack**](../../../../..)
***
Class: DecryptModelOperation [#class-decryptmodeloperationt]
Defined in: [packages/stack/src/encryption/operations/decrypt-model.ts:14](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/decrypt-model.ts#L14)
Extends [#extends]
* `EncryptionOperation`\<[`Decrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)\<`T`>>
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `Record`\<`string`, `unknown`>
Constructors [#constructors]
Constructor [#constructor]
```ts
new DecryptModelOperation<T>(client, model): DecryptModelOperation<T>;
```
Defined in: [packages/stack/src/encryption/operations/decrypt-model.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/decrypt-model.ts#L20)
Parameters [#parameters]
client [#client]
[`Client`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Client)
model [#model]
`T`
Returns [#returns]
`DecryptModelOperation`\<`T`>
Overrides [#overrides]
```ts
EncryptionOperation<Decrypted<T>>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L20)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Configuration for ZeroKMS audit logging
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
EncryptionOperation.audit
```
***
getAuditData() [#getauditdata]
```ts
getAuditData(): AuditData;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:28](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L28)
Get the audit data for this operation.
Returns [#returns-2]
`AuditData`
Inherited from [#inherited-from-1]
```ts
EncryptionOperation.getAuditData
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:42](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L42)
Make the operation thenable
Type Parameters [#type-parameters-1]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`Decrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)\<`T`>, [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-3]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-2]
```ts
EncryptionOperation.then
```
***
withLockContext() [#withlockcontext]
```ts
withLockContext(lockContext): DecryptModelOperationWithLockContext<T>;
```
Defined in: [packages/stack/src/encryption/operations/decrypt-model.ts:26](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/decrypt-model.ts#L26)
Parameters [#parameters-3]
lockContext [#lockcontext]
[`LockContext`](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
Returns [#returns-4]
`DecryptModelOperationWithLockContext`\<`T`>
***
execute() [#execute]
```ts
execute(): Promise<Result<Decrypted<T>, EncryptionError>>;
```
Defined in: [packages/stack/src/encryption/operations/decrypt-model.ts:32](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/decrypt-model.ts#L32)
Execute the operation and return a Result
Returns [#returns-5]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`Decrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Decrypted)\<`T`>, [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>>
Overrides [#overrides-1]
```ts
EncryptionOperation.execute
```
***
getOperation() [#getoperation]
```ts
getOperation(): {
client: Client;
model: T;
};
```
Defined in: [packages/stack/src/encryption/operations/decrypt-model.ts:62](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/decrypt-model.ts#L62)
Returns [#returns-6]
```ts
{
client: Client;
model: T;
}
```
client [#client-1]
```ts
client: Client;
```
model [#model-1]
```ts
model: T;
```
# DecryptOperation
[**@cipherstash/stack**](../../../../..)
***
Class: DecryptOperation [#class-decryptoperation]
Defined in: [packages/stack/src/encryption/operations/decrypt.ts:18](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/decrypt.ts#L18)
Decrypts an encrypted payload using the provided client.
This is the type returned by the [decrypt](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient#decrypt) method of the [EncryptionClient](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient).
Extends [#extends]
* `EncryptionOperation`\<`JsPlaintext`>
Constructors [#constructors]
Constructor [#constructor]
```ts
new DecryptOperation(client, encryptedData): DecryptOperation;
```
Defined in: [packages/stack/src/encryption/operations/decrypt.ts:26](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/decrypt.ts#L26)
Parameters [#parameters]
client [#client]
[`Client`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Client)
encryptedData [#encrypteddata]
`Encrypted` | `null`
Returns [#returns]
`DecryptOperation`
Overrides [#overrides]
```ts
EncryptionOperation<JsPlaintext>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L20)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Configuration for ZeroKMS audit logging
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
EncryptionOperation.audit
```
***
getAuditData() [#getauditdata]
```ts
getAuditData(): AuditData;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:28](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L28)
Get the audit data for this operation.
Returns [#returns-2]
`AuditData`
Inherited from [#inherited-from-1]
```ts
EncryptionOperation.getAuditData
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:42](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L42)
Make the operation thenable
Type Parameters [#type-parameters]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<`JsPlaintext`, [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-3]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-2]
```ts
EncryptionOperation.then
```
***
withLockContext() [#withlockcontext]
```ts
withLockContext(lockContext): DecryptOperationWithLockContext;
```
Defined in: [packages/stack/src/encryption/operations/decrypt.ts:32](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/decrypt.ts#L32)
Parameters [#parameters-3]
lockContext [#lockcontext]
[`LockContext`](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
Returns [#returns-4]
`DecryptOperationWithLockContext`
***
execute() [#execute]
```ts
execute(): Promise<Result<JsPlaintext, EncryptionError>>;
```
Defined in: [packages/stack/src/encryption/operations/decrypt.ts:38](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/decrypt.ts#L38)
Execute the operation and return a Result
Returns [#returns-5]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<`JsPlaintext`, [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>>
Overrides [#overrides-1]
```ts
EncryptionOperation.execute
```
***
getOperation() [#getoperation]
```ts
getOperation(): {
client: Client;
encryptedData: Encrypted | null;
auditData?: Record<string, unknown>;
};
```
Defined in: [packages/stack/src/encryption/operations/decrypt.ts:76](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/decrypt.ts#L76)
Returns [#returns-6]
```ts
{
client: Client;
encryptedData: Encrypted | null;
auditData?: Record<string, unknown>;
}
```
client [#client-1]
```ts
client: Client;
```
encryptedData [#encrypteddata-1]
```ts
encryptedData: Encrypted | null;
```
auditData? [#auditdata]
```ts
optional auditData: Record<string, unknown>;
```
# EncryptModelOperation
[**@cipherstash/stack**](../../../../..)
***
Class: EncryptModelOperation [#class-encryptmodeloperationt]
Defined in: [packages/stack/src/encryption/operations/encrypt-model.ts:15](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt-model.ts#L15)
Extends [#extends]
* `EncryptionOperation`\<`T`>
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `Record`\<`string`, `unknown`>
Constructors [#constructors]
Constructor [#constructor]
```ts
new EncryptModelOperation<T>(
client,
model,
table): EncryptModelOperation<T>;
```
Defined in: [packages/stack/src/encryption/operations/encrypt-model.ts:22](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt-model.ts#L22)
Parameters [#parameters]
client [#client]
[`Client`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Client)
model [#model]
`Record`\<`string`, `unknown`>
table [#table]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)>
Returns [#returns]
`EncryptModelOperation`\<`T`>
Overrides [#overrides]
```ts
EncryptionOperation<T>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L20)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Configuration for ZeroKMS audit logging
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
EncryptionOperation.audit
```
***
getAuditData() [#getauditdata]
```ts
getAuditData(): AuditData;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:28](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L28)
Get the audit data for this operation.
Returns [#returns-2]
`AuditData`
Inherited from [#inherited-from-1]
```ts
EncryptionOperation.getAuditData
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:42](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L42)
Make the operation thenable
Type Parameters [#type-parameters-1]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<`T`, [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-3]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-2]
```ts
EncryptionOperation.then
```
***
withLockContext() [#withlockcontext]
```ts
withLockContext(lockContext): EncryptModelOperationWithLockContext<T>;
```
Defined in: [packages/stack/src/encryption/operations/encrypt-model.ts:33](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt-model.ts#L33)
Parameters [#parameters-3]
lockContext [#lockcontext]
[`LockContext`](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
Returns [#returns-4]
`EncryptModelOperationWithLockContext`\<`T`>
***
execute() [#execute]
```ts
execute(): Promise<Result<T, EncryptionError>>;
```
Defined in: [packages/stack/src/encryption/operations/encrypt-model.ts:39](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt-model.ts#L39)
Execute the operation and return a Result
Returns [#returns-5]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<`T`, [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>>
Overrides [#overrides-1]
```ts
EncryptionOperation.execute
```
***
getOperation() [#getoperation]
```ts
getOperation(): {
client: Client;
model: Record<string, unknown>;
table: EncryptedTable<EncryptedTableColumn>;
};
```
Defined in: [packages/stack/src/encryption/operations/encrypt-model.ts:75](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt-model.ts#L75)
Returns [#returns-6]
```ts
{
client: Client;
model: Record<string, unknown>;
table: EncryptedTable<EncryptedTableColumn>;
}
```
client [#client-1]
```ts
client: Client;
```
model [#model-1]
```ts
model: Record<string, unknown>;
```
table [#table-1]
```ts
table: EncryptedTable<EncryptedTableColumn>;
```
# EncryptOperation
[**@cipherstash/stack**](../../../../..)
***
Class: EncryptOperation [#class-encryptoperation]
Defined in: [packages/stack/src/encryption/operations/encrypt.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt.ts#L20)
Extends [#extends]
* `EncryptionOperation`\<[`Encrypted`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Encrypted)>
Constructors [#constructors]
Constructor [#constructor]
```ts
new EncryptOperation(
client,
plaintext,
opts): EncryptOperation;
```
Defined in: [packages/stack/src/encryption/operations/encrypt.ts:30](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt.ts#L30)
Parameters [#parameters]
client [#client]
[`Client`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Client)
plaintext [#plaintext]
`JsPlaintext` | `null`
opts [#opts]
[`EncryptOptions`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptOptions)
Returns [#returns]
`EncryptOperation`
Overrides [#overrides]
```ts
EncryptionOperation<Encrypted>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L20)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Configuration for ZeroKMS audit logging
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
EncryptionOperation.audit
```
***
getAuditData() [#getauditdata]
```ts
getAuditData(): AuditData;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:28](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L28)
Get the audit data for this operation.
Returns [#returns-2]
`AuditData`
Inherited from [#inherited-from-1]
```ts
EncryptionOperation.getAuditData
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:42](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L42)
Make the operation thenable
Type Parameters [#type-parameters]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<`Encrypted`, [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-3]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-2]
```ts
EncryptionOperation.then
```
***
withLockContext() [#withlockcontext]
```ts
withLockContext(lockContext): EncryptOperationWithLockContext;
```
Defined in: [packages/stack/src/encryption/operations/encrypt.ts:42](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt.ts#L42)
Parameters [#parameters-3]
lockContext [#lockcontext]
[`LockContext`](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
Returns [#returns-4]
`EncryptOperationWithLockContext`
***
execute() [#execute]
```ts
execute(): Promise<Result<Encrypted, EncryptionError>>;
```
Defined in: [packages/stack/src/encryption/operations/encrypt.ts:48](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt.ts#L48)
Execute the operation and return a Result
Returns [#returns-5]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<`Encrypted`, [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>>
Overrides [#overrides-1]
```ts
EncryptionOperation.execute
```
***
getOperation() [#getoperation]
```ts
getOperation(): {
client: Client;
plaintext: JsPlaintext | null;
column: | EncryptedColumn
| EncryptedField;
table: EncryptedTable<EncryptedTableColumn>;
};
```
Defined in: [packages/stack/src/encryption/operations/encrypt.ts:109](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt.ts#L109)
Returns [#returns-6]
```ts
{
client: Client;
plaintext: JsPlaintext | null;
column: | EncryptedColumn
| EncryptedField;
table: EncryptedTable<EncryptedTableColumn>;
}
```
client [#client-1]
```ts
client: Client;
```
plaintext [#plaintext-1]
```ts
plaintext: JsPlaintext | null;
```
column [#column]
```ts
column:
| EncryptedColumn
| EncryptedField;
```
table [#table]
```ts
table: EncryptedTable<EncryptedTableColumn>;
```
# EncryptQueryOperation
[**@cipherstash/stack**](../../../../..)
***
Class: EncryptQueryOperation [#class-encryptqueryoperation]
Defined in: [packages/stack/src/encryption/operations/encrypt-query.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt-query.ts#L20)
Extends [#extends]
* `EncryptionOperation`\<[`EncryptedQueryResult`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedQueryResult)>
Constructors [#constructors]
Constructor [#constructor]
```ts
new EncryptQueryOperation(
client,
plaintext,
opts): EncryptQueryOperation;
```
Defined in: [packages/stack/src/encryption/operations/encrypt-query.ts:21](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt-query.ts#L21)
Parameters [#parameters]
client [#client]
[`Client`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Client)
plaintext [#plaintext]
`JsPlaintext` | `null` | `undefined`
opts [#opts]
`QueryTermBase`
Returns [#returns]
`EncryptQueryOperation`
Overrides [#overrides]
```ts
EncryptionOperation<EncryptedQueryResult>.constructor
```
Methods [#methods]
audit() [#audit]
```ts
audit(config): this;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:20](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L20)
Attach audit metadata to this operation. Can be chained.
Parameters [#parameters-1]
config [#config]
`AuditConfig`
Configuration for ZeroKMS audit logging
Returns [#returns-1]
`this`
Inherited from [#inherited-from]
```ts
EncryptionOperation.audit
```
***
getAuditData() [#getauditdata]
```ts
getAuditData(): AuditData;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:28](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L28)
Get the audit data for this operation.
Returns [#returns-2]
`AuditData`
Inherited from [#inherited-from-1]
```ts
EncryptionOperation.getAuditData
```
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): Promise<TResult1 | TResult2>;
```
Defined in: [packages/stack/src/encryption/operations/base-operation.ts:42](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/base-operation.ts#L42)
Make the operation thenable
Type Parameters [#type-parameters]
TResult1 [#tresult1]
`TResult1` = [`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`EncryptedQueryResult`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedQueryResult), [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-2]
onfulfilled? [#onfulfilled]
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-3]
`Promise`\<`TResult1` | `TResult2`>
Inherited from [#inherited-from-2]
```ts
EncryptionOperation.then
```
***
withLockContext() [#withlockcontext]
```ts
withLockContext(lockContext): EncryptQueryOperationWithLockContext;
```
Defined in: [packages/stack/src/encryption/operations/encrypt-query.ts:29](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt-query.ts#L29)
Parameters [#parameters-3]
lockContext [#lockcontext]
[`LockContext`](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
Returns [#returns-4]
`EncryptQueryOperationWithLockContext`
***
execute() [#execute]
```ts
execute(): Promise<Result<EncryptedQueryResult, EncryptionError>>;
```
Defined in: [packages/stack/src/encryption/operations/encrypt-query.ts:41](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt-query.ts#L41)
Execute the operation and return a Result
Returns [#returns-5]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`EncryptedQueryResult`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedQueryResult), [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>>
Overrides [#overrides-1]
```ts
EncryptionOperation.execute
```
***
getOperation() [#getoperation]
```ts
getOperation(): {
client: Client;
plaintext: JsPlaintext | null | undefined;
column: EncryptedColumn;
table: EncryptedTable<EncryptedTableColumn>;
queryType?: QueryTypeName;
returnType?: EncryptedReturnType;
};
```
Defined in: [packages/stack/src/encryption/operations/encrypt-query.ts:109](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/operations/encrypt-query.ts#L109)
Returns [#returns-6]
```ts
{
client: Client;
plaintext: JsPlaintext | null | undefined;
column: EncryptedColumn;
table: EncryptedTable<EncryptedTableColumn>;
queryType?: QueryTypeName;
returnType?: EncryptedReturnType;
}
```
client [#client-1]
```ts
client: Client;
```
plaintext [#plaintext-1]
```ts
plaintext: JsPlaintext | null | undefined;
```
column [#column]
```ts
column: EncryptedColumn;
```
table [#table]
```ts
table: EncryptedTable<EncryptedTableColumn>;
```
queryType? [#querytype]
```ts
optional queryType: QueryTypeName;
```
returnType? [#returntype]
```ts
optional returnType: EncryptedReturnType;
```
# EncryptionClient
[**@cipherstash/stack**](../../../../..)
***
Class: EncryptionClient [#class-encryptionclient]
Defined in: [packages/stack/src/encryption/index.ts:71](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L71)
The EncryptionClient is the main entry point for interacting with the CipherStash Encryption library.
It provides methods for encrypting and decrypting individual values, as well as models (objects) and bulk operations.
The client must be initialized using the [Encryption](/stack/reference/stack/latest/packages/stack/src/encryption/functions/Encryption) function before it can be used.
Constructors [#constructors]
Constructor [#constructor]
```ts
new EncryptionClient(): EncryptionClient;
```
Returns [#returns]
`EncryptionClient`
Methods [#methods]
encrypt() [#encrypt]
```ts
encrypt(plaintext, opts): EncryptOperation;
```
Defined in: [packages/stack/src/encryption/index.ts:202](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L202)
Encrypt a value - returns a promise which resolves to an encrypted value.
Parameters [#parameters]
plaintext [#plaintext]
`JsPlaintext`
The plaintext value to be encrypted.
opts [#opts]
[`EncryptOptions`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptOptions)
Options specifying the column (or nested field) and table for encryption. See [EncryptOptions](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptOptions).
Returns [#returns-1]
[`EncryptOperation`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptOperation)
An EncryptOperation that can be awaited or chained with additional methods.
Examples [#examples]
The following example demonstrates how to encrypt a value using the Encryption client.
It includes defining an encryption schema with [encryptedTable](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedTable) and [encryptedColumn](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedColumn),
initializing the client with [Encryption](/stack/reference/stack/latest/packages/stack/src/encryption/functions/Encryption), and performing the encryption.
`encrypt` returns an [EncryptOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptOperation) which can be awaited to get a [Result](https://www.npmjs.com/package/@byteslice/result)
which can either be the encrypted value or an [EncryptionError](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError).
```typescript
// Define encryption schema
import { Encryption } from "@cipherstash/stack"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const userSchema = encryptedTable("users", {
email: encryptedColumn("email"),
});
// Initialize Encryption client
const client = await Encryption({ schemas: [userSchema] })
// Encrypt a value
const encryptedResult = await client.encrypt(
"person@example.com",
{ column: userSchema.email, table: userSchema }
)
// Handle encryption result
if (encryptedResult.failure) {
throw new Error(`Encryption failed: ${encryptedResult.failure.message}`);
}
console.log("Encrypted data:", encryptedResult.data);
```
When encrypting data, a [LockContext](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext) can be provided to tie the encryption to a specific user or session.
This ensures that the same lock context is required for decryption.
The following example demonstrates how to create a lock context using a user's JWT token
and use it during encryption.
```typescript
// Define encryption schema and initialize client as above
// Create a lock for the user's `sub` claim from their JWT
const lc = new LockContext();
const lockContext = await lc.identify(userJwt);
if (lockContext.failure) {
// Handle the failure
}
// Encrypt a value with the lock context
// Decryption will then require the same lock context
const encryptedResult = await client.encrypt(
"person@example.com",
{ column: userSchema.email, table: userSchema }
)
.withLockContext(lockContext)
```
See [#see]
* [EncryptOptions](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptOptions)
* [Result](https://www.npmjs.com/package/@byteslice/result)
* [encryptedTable](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedTable)
* [encryptedColumn](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedColumn)
* [encryptedField](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedField)
* [LockContext](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
* [EncryptOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptOperation)
***
encryptQuery() [#encryptquery]
Call Signature [#call-signature]
```ts
encryptQuery(plaintext, opts): EncryptQueryOperation;
```
Defined in: [packages/stack/src/encryption/index.ts:259](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L259)
Encrypt a query value - returns a promise which resolves to an encrypted query value.
Parameters [#parameters-1]
plaintext [#plaintext-1]
`JsPlaintext`
The plaintext value to be encrypted for querying.
opts [#opts-1]
`QueryTermBase`
Options specifying the column, table, and optional queryType for encryption.
Returns [#returns-2]
[`EncryptQueryOperation`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptQueryOperation)
An EncryptQueryOperation that can be awaited or chained with additional methods.
Examples [#examples-1]
The following example demonstrates how to encrypt a query value using the Encryption client.
```typescript
// Define encryption schema
import { Encryption } from "@cipherstash/stack"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const userSchema = encryptedTable("users", {
email: encryptedColumn("email").equality(),
});
// Initialize Encryption client
const client = await Encryption({ schemas: [userSchema] })
// Encrypt a query value
const encryptedResult = await client.encryptQuery(
"person@example.com",
{ column: userSchema.email, table: userSchema, queryType: 'equality' }
)
// Handle encryption result
if (encryptedResult.failure) {
throw new Error(`Encryption failed: ${encryptedResult.failure.message}`);
}
console.log("Encrypted query:", encryptedResult.data);
```
The queryType can be auto-inferred from the column's configured indexes:
```typescript
// When queryType is omitted, it will be inferred from the column's indexes
const encryptedResult = await client.encryptQuery(
"person@example.com",
{ column: userSchema.email, table: userSchema }
)
```
See [#see-1]
[EncryptQueryOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptQueryOperation)
**JSONB columns (searchableJson):**
When `queryType` is omitted on a `searchableJson()` column, the query operation is inferred:
* String plaintext → `steVecSelector` (JSONPath queries like `'$.user.email'`)
* Object/Array plaintext → `steVecTerm` (containment queries like `{ role: 'admin' }`)
Call Signature [#call-signature-1]
```ts
encryptQuery(terms): BatchEncryptQueryOperation;
```
Defined in: [packages/stack/src/encryption/index.ts:268](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L268)
Encrypt multiple values for use in queries (batch operation).
Parameters [#parameters-2]
terms [#terms]
readonly [`ScalarQueryTerm`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/ScalarQueryTerm)\[]
Array of query terms to encrypt
Returns [#returns-3]
[`BatchEncryptQueryOperation`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/BatchEncryptQueryOperation)
***
decrypt() [#decrypt]
```ts
decrypt(encryptedData): DecryptOperation;
```
Defined in: [packages/stack/src/encryption/index.ts:344](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L344)
Decryption - returns a promise which resolves to a decrypted value.
Parameters [#parameters-3]
encryptedData [#encrypteddata]
`Encrypted`
The encrypted data to be decrypted.
Returns [#returns-4]
[`DecryptOperation`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/DecryptOperation)
A DecryptOperation that can be awaited or chained with additional methods.
Examples [#examples-2]
The following example demonstrates how to decrypt a value that was previously encrypted using the [encrypt](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient#encrypt) method.
It includes encrypting a value first, then decrypting it, and handling the result.
```typescript
const encryptedData = await client.encrypt(
"person@example.com",
{ column: "email", table: "users" }
)
const decryptResult = await client.decrypt(encryptedData)
if (decryptResult.failure) {
throw new Error(`Decryption failed: ${decryptResult.failure.message}`);
}
console.log("Decrypted data:", decryptResult.data);
```
Provide a lock context when decrypting:
```typescript
await client.decrypt(encryptedData)
.withLockContext(lockContext)
```
Remarks [#remarks]
The public input type rejects null, but at runtime `decrypt` will
short-circuit and return null when given a null ciphertext
(defense in depth for legacy / manually-NULLed DB rows reached via
casts or dynamic field walking). The narrow return type holds for
any caller that respects the input contract.
See [#see-2]
* [LockContext](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
* [DecryptOperation](/stack/reference/stack/latest/packages/stack/src/encryption/classes/DecryptOperation)
***
encryptModel() [#encryptmodel]
```ts
encryptModel<T, S>(input, table): EncryptModelOperation<EncryptedFromSchema<T, S>>;
```
Defined in: [packages/stack/src/encryption/index.ts:394](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L394)
Encrypt a model (object) based on the table schema.
Only fields whose keys match columns defined in the table schema are encrypted.
All other fields are passed through unchanged. Returns a thenable operation
that supports `.withLockContext()` for identity-aware encryption.
The return type is **schema-aware**: fields matching the table schema are
typed as `Encrypted`, while other fields retain their original types. For
best results, let TypeScript infer the type parameters from the arguments
rather than providing an explicit type argument.
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `Record`\<`string`, `unknown`>
S [#s]
`S` *extends* [`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn) = [`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)
Parameters [#parameters-4]
input [#input]
`T`
The model object with plaintext values to encrypt.
table [#table]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<`S`>
The table schema defining which fields to encrypt.
Returns [#returns-5]
[`EncryptModelOperation`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptModelOperation)\<[`EncryptedFromSchema`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedFromSchema)\<`T`, `S`>>
An `EncryptModelOperation` that can be awaited to get a `Result`
containing the model with schema-defined fields typed as `Encrypted`,
or an `EncryptionError`.
Example [#example]
```typescript
import { Encryption } from "@cipherstash/stack"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
type User = { id: string; email: string; createdAt: Date }
const usersSchema = encryptedTable("users", {
email: encryptedColumn("email").equality(),
})
const client = await Encryption({ schemas: [usersSchema] })
// Let TypeScript infer the return type from the schema.
// result.data.email is typed as `Encrypted`, result.data.id stays `string`.
const result = await client.encryptModel(
{ id: "user_123", email: "alice@example.com", createdAt: new Date() },
usersSchema,
)
if (result.failure) {
console.error(result.failure.message)
} else {
console.log(result.data.id) // string
console.log(result.data.email) // Encrypted
}
```
***
decryptModel() [#decryptmodel]
```ts
decryptModel<T>(input): DecryptModelOperation<T>;
```
Defined in: [packages/stack/src/encryption/index.ts:436](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L436)
Decrypt a model (object) whose fields contain encrypted values.
Identifies encrypted fields automatically and decrypts them, returning the
model with plaintext values. Returns a thenable operation that supports
`.withLockContext()` for identity-aware decryption.
Type Parameters [#type-parameters-1]
T [#t-1]
`T` *extends* `Record`\<`string`, `unknown`>
Parameters [#parameters-5]
input [#input-1]
`T`
The model object with encrypted field values.
Returns [#returns-6]
[`DecryptModelOperation`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/DecryptModelOperation)\<`T`>
A `DecryptModelOperation<T>` that can be awaited to get a `Result`
containing the model with decrypted plaintext fields, or an `EncryptionError`.
Example [#example-1]
```typescript
// Decrypt a previously encrypted model
const decrypted = await client.decryptModel<User>(encryptedUser)
if (decrypted.failure) {
console.error(decrypted.failure.message)
} else {
console.log(decrypted.data.email) // "alice@example.com"
}
// With a lock context
const decrypted = await client
.decryptModel<User>(encryptedUser)
.withLockContext(lockContext)
```
***
bulkEncryptModels() [#bulkencryptmodels]
```ts
bulkEncryptModels<T, S>(input, table): BulkEncryptModelsOperation<EncryptedFromSchema<T, S>>;
```
Defined in: [packages/stack/src/encryption/index.ts:487](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L487)
Encrypt multiple models (objects) in a single bulk operation.
Performs a single call to ZeroKMS regardless of the number of models,
while still using a unique key for each encrypted value. Only fields
matching the table schema are encrypted; other fields pass through unchanged.
The return type is **schema-aware**: fields matching the table schema are
typed as `Encrypted`, while other fields retain their original types. For
best results, let TypeScript infer the type parameters from the arguments.
Type Parameters [#type-parameters-2]
T [#t-2]
`T` *extends* `Record`\<`string`, `unknown`>
S [#s-1]
`S` *extends* [`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn) = [`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)
Parameters [#parameters-6]
input [#input-2]
`T`\[]
An array of model objects with plaintext values to encrypt.
table [#table-1]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<`S`>
The table schema defining which fields to encrypt.
Returns [#returns-7]
[`BulkEncryptModelsOperation`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/BulkEncryptModelsOperation)\<[`EncryptedFromSchema`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedFromSchema)\<`T`, `S`>>
A `BulkEncryptModelsOperation` that can be awaited to get a `Result`
containing an array of models with schema-defined fields typed as `Encrypted`,
or an `EncryptionError`.
Example [#example-2]
```typescript
import { Encryption } from "@cipherstash/stack"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
type User = { id: string; email: string }
const usersSchema = encryptedTable("users", {
email: encryptedColumn("email"),
})
const client = await Encryption({ schemas: [usersSchema] })
// Let TypeScript infer the return type from the schema.
// Each item's email is typed as `Encrypted`, id stays `string`.
const result = await client.bulkEncryptModels(
[
{ id: "1", email: "alice@example.com" },
{ id: "2", email: "bob@example.com" },
],
usersSchema,
)
if (!result.failure) {
console.log(result.data) // array of models with encrypted email fields
}
```
***
bulkDecryptModels() [#bulkdecryptmodels]
```ts
bulkDecryptModels<T>(input): BulkDecryptModelsOperation<T>;
```
Defined in: [packages/stack/src/encryption/index.ts:529](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L529)
Decrypt multiple models (objects) in a single bulk operation.
Performs a single call to ZeroKMS regardless of the number of models,
restoring all encrypted fields to their original plaintext values.
Type Parameters [#type-parameters-3]
T [#t-3]
`T` *extends* `Record`\<`string`, `unknown`>
Parameters [#parameters-7]
input [#input-3]
`T`\[]
An array of model objects with encrypted field values.
Returns [#returns-8]
[`BulkDecryptModelsOperation`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/BulkDecryptModelsOperation)\<`T`>
A `BulkDecryptModelsOperation<T>` that can be awaited to get a `Result`
containing an array of models with decrypted plaintext fields, or an `EncryptionError`.
Example [#example-3]
```typescript
const encryptedUsers = encryptedResult.data // from bulkEncryptModels
const result = await client.bulkDecryptModels<User>(encryptedUsers)
if (!result.failure) {
for (const user of result.data) {
console.log(user.email) // plaintext email
}
}
// With a lock context
const result = await client
.bulkDecryptModels<User>(encryptedUsers)
.withLockContext(lockContext)
```
***
bulkEncrypt() [#bulkencrypt]
```ts
bulkEncrypt(plaintexts, opts): BulkEncryptOperation;
```
Defined in: [packages/stack/src/encryption/index.ts:571](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L571)
Encrypt multiple plaintext values in a single bulk operation.
Each value is encrypted with its own unique key via a single call to ZeroKMS.
Values can include optional `id` fields for correlating results back to
your application data.
Parameters [#parameters-8]
plaintexts [#plaintexts]
[`BulkEncryptPayload`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkEncryptPayload)
An array of objects with `plaintext` (and optional `id`) fields.
opts [#opts-2]
[`EncryptOptions`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptOptions)
Options specifying the target column (or nested [encryptedField](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedField)) and table. See [EncryptOptions](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptOptions).
Returns [#returns-9]
[`BulkEncryptOperation`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/BulkEncryptOperation)
A `BulkEncryptOperation` that can be awaited to get a `Result`
containing an array of `{ id?, data: Encrypted }` objects, or an `EncryptionError`.
Example [#example-4]
```typescript
import { Encryption } from "@cipherstash/stack"
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email"),
})
const client = await Encryption({ schemas: [users] })
const result = await client.bulkEncrypt(
[
{ id: "u1", plaintext: "alice@example.com" },
{ id: "u2", plaintext: "bob@example.com" },
],
{ column: users.email, table: users },
)
if (!result.failure) {
// result.data = [{ id: "u1", data: Encrypted }, { id: "u2", data: Encrypted }, ...]
console.log(result.data)
}
```
***
bulkDecrypt() [#bulkdecrypt]
```ts
bulkDecrypt(encryptedPayloads): BulkDecryptOperation;
```
Defined in: [packages/stack/src/encryption/index.ts:608](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L608)
Decrypt multiple encrypted values in a single bulk operation.
Performs a single call to ZeroKMS to decrypt all values. The result uses
a multi-status pattern: each item in the returned array has either a `data`
field (success) or an `error` field (failure), allowing graceful handling
of partial failures.
Parameters [#parameters-9]
encryptedPayloads [#encryptedpayloads]
[`BulkDecryptPayload`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/BulkDecryptPayload)
An array of objects with `data` (encrypted payload) and optional `id` fields.
Returns [#returns-10]
[`BulkDecryptOperation`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/BulkDecryptOperation)
A `BulkDecryptOperation` that can be awaited to get a `Result`
containing an array of `{ id?, data: plaintext }` or `{ id?, error: string }` objects,
or an `EncryptionError` if the entire operation fails.
Example [#example-5]
```typescript
const encrypted = await client.bulkEncrypt(plaintexts, { column: users.email, table: users })
const result = await client.bulkDecrypt(encrypted.data)
if (!result.failure) {
for (const item of result.data) {
if ("data" in item) {
console.log(`${item.id}: ${item.data}`)
} else {
console.error(`${item.id} failed: ${item.error}`)
}
}
}
```
***
getEncryptConfig() [#getencryptconfig]
```ts
getEncryptConfig():
| {
v: number;
tables: Record<string, Record<string, {
cast_as: "string" | "number" | "bigint" | "boolean" | "text" | "date" | "json";
indexes: {
ore?: {
};
unique?: {
token_filters?: {
kind: ...;
}[];
};
match?: {
tokenizer?: | {
kind: "standard";
}
| {
kind: "ngram";
token_length: number;
};
token_filters?: {
kind: ...;
}[];
k?: number;
m?: number;
include_original?: boolean;
};
ste_vec?: {
prefix: string;
array_index_mode?: | "all"
| "none"
| {
item?: ... | ... | ...;
wildcard?: ... | ... | ...;
position?: ... | ... | ...;
};
};
};
}>>;
}
| undefined;
```
Defined in: [packages/stack/src/encryption/index.ts:617](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L617)
Get the encrypt config object.
Returns [#returns-11]
\| \{
`v`: `number`;
`tables`: `Record`\<`string`, `Record`\<`string`, \{
`cast_as`: `"string"` | `"number"` | `"bigint"` | `"boolean"` | `"text"` | `"date"` | `"json"`;
`indexes`: \{
`ore?`: \{
};
`unique?`: \{
`token_filters?`: \{
`kind`: ...;
}\[];
};
`match?`: \{
`tokenizer?`: | \{
`kind`: `"standard"`;
}
\| \{
`kind`: `"ngram"`;
`token_length`: `number`;
};
`token_filters?`: \{
`kind`: ...;
}\[];
`k?`: `number`;
`m?`: `number`;
`include_original?`: `boolean`;
};
`ste_vec?`: \{
`prefix`: `string`;
`array_index_mode?`: | `"all"`
\| `"none"`
\| \{
`item?`: ... | ... | ...;
`wildcard?`: ... | ... | ...;
`position?`: ... | ... | ...;
};
};
};
}>>;
}
\| `undefined`
The encrypt config object.
# Encryption
[**@cipherstash/stack**](../../../../..)
***
Function: Encryption() [#function-encryption]
```ts
function Encryption(config): Promise<EncryptionClient>;
```
Defined in: [packages/stack/src/encryption/index.ts:652](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L652)
Creates and initializes an Encryption client for encrypting and decrypting data with CipherStash.
Provide at least one schema (from [encryptedTable](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedTable)) so the client knows which tables and
columns to use. Credentials are read from the optional `config` or from the environment
(`CS_WORKSPACE_CRN`, `CS_CLIENT_ID`, `CS_CLIENT_KEY`, `CS_CLIENT_ACCESS_KEY`).
Parameters [#parameters]
config [#config]
[`EncryptionClientConfig`](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptionClientConfig)
Initialization options. Must include `schemas`; optionally include `config` for
workspace/keys. Logging is configured via the `STASH_STACK_LOG` environment variable
(`debug | info | error`, default: `error`).
Returns [#returns]
`Promise`\<[`EncryptionClient`](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient)>
A Promise that resolves to an initialized [EncryptionClient](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient) ready for
[EncryptionClient.encrypt](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient#encrypt), [EncryptionClient.decrypt](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient#decrypt), and related operations.
Throws [#throws]
Throws if `schemas` is empty, or if a keyset `id` is supplied but is not a valid UUID.
Also throws if the client fails to initialize (e.g. invalid credentials or config).
Example [#example]
```typescript
import { Encryption, encryptedTable, encryptedColumn } from "@cipherstash/stack"
const users = encryptedTable("users", {
email: encryptedColumn("email"),
})
const client = await Encryption({ schemas: [users] })
const result = await client.encrypt("alice@example.com", { column: users.email, table: users })
```
See [#see]
* [EncryptionClientConfig](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptionClientConfig) for full config options.
* [EncryptionClient](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient) for available methods after initialization.
# noClientError
[**@cipherstash/stack**](../../../../..)
***
Function: noClientError() [#function-noclienterror]
```ts
function noClientError(): Error;
```
Defined in: [packages/stack/src/encryption/index.ts:61](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/encryption/index.ts#L61)
Returns [#returns]
`Error`
# getErrorMessage
[**@cipherstash/stack**](../../../../..)
***
Function: getErrorMessage() [#function-geterrormessage]
```ts
function getErrorMessage(error): string;
```
Defined in: [packages/stack/src/errors/index.ts:113](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L113)
Safely extract an error message from an unknown thrown value.
Unlike `(error as Error).message`, this handles non-Error values gracefully.
Parameters [#parameters]
error [#error]
`unknown`
Returns [#returns]
`string`
# ClientInitError
[**@cipherstash/stack**](../../../../..)
***
Interface: ClientInitError [#interface-clientiniterror]
Defined in: [packages/stack/src/errors/index.ts:43](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L43)
Properties [#properties]
type [#type]
```ts
type: string;
```
Defined in: [packages/stack/src/errors/index.ts:44](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L44)
***
message [#message]
```ts
message: string;
```
Defined in: [packages/stack/src/errors/index.ts:45](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L45)
# CtsTokenError
[**@cipherstash/stack**](../../../../..)
***
Interface: CtsTokenError [#interface-ctstokenerror]
Defined in: [packages/stack/src/errors/index.ts:65](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L65)
Properties [#properties]
type [#type]
```ts
type: string;
```
Defined in: [packages/stack/src/errors/index.ts:66](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L66)
***
message [#message]
```ts
message: string;
```
Defined in: [packages/stack/src/errors/index.ts:67](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L67)
# DecryptionOperationError
[**@cipherstash/stack**](../../../../..)
***
Interface: DecryptionOperationError [#interface-decryptionoperationerror]
Defined in: [packages/stack/src/errors/index.ts:54](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L54)
Properties [#properties]
type [#type]
```ts
type: string;
```
Defined in: [packages/stack/src/errors/index.ts:55](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L55)
***
message [#message]
```ts
message: string;
```
Defined in: [packages/stack/src/errors/index.ts:56](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L56)
***
code? [#code]
```ts
optional code: ProtectErrorCode;
```
Defined in: [packages/stack/src/errors/index.ts:57](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L57)
# EncryptionError
[**@cipherstash/stack**](../../../../..)
***
Interface: EncryptionError [#interface-encryptionerror]
Defined in: [packages/stack/src/errors/index.ts:33](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L33)
Base error interface returned by all encryption operations.
Every operation that can fail returns `Result<T, EncryptionError>`.
Use the `type` field to narrow to a specific error kind, or use
[StackError](/stack/reference/stack/latest/packages/stack/src/errors/type-aliases/StackError) for an exhaustive discriminated union.
Example [#example]
```typescript
const result = await client.encrypt(value, opts)
if (result.failure) {
switch (result.failure.type) {
case EncryptionErrorTypes.EncryptionError:
console.error('Encryption failed:', result.failure.message)
break
case EncryptionErrorTypes.LockContextError:
console.error('Lock context issue:', result.failure.message)
break
}
}
```
Properties [#properties]
type [#type]
```ts
type: string;
```
Defined in: [packages/stack/src/errors/index.ts:34](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L34)
***
message [#message]
```ts
message: string;
```
Defined in: [packages/stack/src/errors/index.ts:35](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L35)
***
code? [#code]
```ts
optional code: ProtectErrorCode;
```
Defined in: [packages/stack/src/errors/index.ts:36](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L36)
# EncryptionOperationError
[**@cipherstash/stack**](../../../../..)
***
Interface: EncryptionOperationError [#interface-encryptionoperationerror]
Defined in: [packages/stack/src/errors/index.ts:48](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L48)
Properties [#properties]
type [#type]
```ts
type: string;
```
Defined in: [packages/stack/src/errors/index.ts:49](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L49)
***
message [#message]
```ts
message: string;
```
Defined in: [packages/stack/src/errors/index.ts:50](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L50)
***
code? [#code]
```ts
optional code: ProtectErrorCode;
```
Defined in: [packages/stack/src/errors/index.ts:51](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L51)
# LockContextError
[**@cipherstash/stack**](../../../../..)
***
Interface: LockContextError [#interface-lockcontexterror]
Defined in: [packages/stack/src/errors/index.ts:60](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L60)
Properties [#properties]
type [#type]
```ts
type: string;
```
Defined in: [packages/stack/src/errors/index.ts:61](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L61)
***
message [#message]
```ts
message: string;
```
Defined in: [packages/stack/src/errors/index.ts:62](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L62)
# StackError
[**@cipherstash/stack**](../../../../..)
***
Type Alias: StackError [#type-alias-stackerror]
```ts
type StackError =
| ClientInitError
| EncryptionOperationError
| DecryptionOperationError
| LockContextError
| CtsTokenError;
```
Defined in: [packages/stack/src/errors/index.ts:98](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L98)
Discriminated union of all specific error types.
Use `StackError` when you need exhaustive error handling via `switch` on the `type` field.
Example [#example]
```typescript
function handleError(error: StackError) {
switch (error.type) {
case 'ClientInitError':
// re-initialize client
break
case 'EncryptionError':
case 'DecryptionError':
// log and retry
break
case 'LockContextError':
// re-authenticate
break
case 'CtsTokenError':
// refresh token
break
default:
error satisfies never
}
}
```
# LockContext
[**@cipherstash/stack**](../../../../..)
***
Class: LockContext [#class-lockcontext]
Defined in: [packages/stack/src/identity/index.ts:53](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L53)
Manages CipherStash lock contexts for row-level access control.
A `LockContext` ties encryption/decryption operations to an authenticated
user identity via CTS (CipherStash Token Service). Call [identify](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext#identify)
with a user's JWT to obtain a CTS token, then pass the `LockContext`
to `.withLockContext()` on any encrypt/decrypt operation.
Example [#example]
```typescript
import { LockContext } from "@cipherstash/stack/identity"
const lc = new LockContext()
const identified = await lc.identify(userJwt)
if (identified.failure) throw new Error(identified.failure.message)
const result = await client
.encrypt(value, { column: users.email, table: users })
.withLockContext(identified.data)
```
Constructors [#constructors]
Constructor [#constructor]
```ts
new LockContext(__namedParameters?): LockContext;
```
Defined in: [packages/stack/src/identity/index.ts:58](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L58)
Parameters [#parameters]
__namedParameters? [#__namedparameters]
[`LockContextOptions`](/stack/reference/stack/latest/packages/stack/src/identity/type-aliases/LockContextOptions) = `{}`
Returns [#returns]
`LockContext`
Methods [#methods]
identify() [#identify]
```ts
identify(jwtToken): Promise<Result<LockContext, EncryptionError>>;
```
Defined in: [packages/stack/src/identity/index.ts:94](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L94)
Exchange a user's JWT for a CTS token and bind it to this lock context.
Parameters [#parameters-1]
jwtToken [#jwttoken]
`string`
A valid OIDC / JWT token for the current user.
Returns [#returns-1]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<`LockContext`, [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>>
A `Result` containing this `LockContext` (now authenticated) or an error.
Example [#example-1]
```typescript
const lc = new LockContext()
const result = await lc.identify(userJwt)
if (result.failure) {
console.error("Auth failed:", result.failure.message)
}
```
***
getLockContext() [#getlockcontext]
```ts
getLockContext(): Promise<Result<GetLockContextResponse, EncryptionError>>;
```
Defined in: [packages/stack/src/identity/index.ts:156](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L156)
Retrieve the current CTS token and context for use with encryption operations.
Must be called after [identify](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext#identify). Returns the token/context pair that
`.withLockContext()` expects.
Returns [#returns-2]
`Promise`\<[`Result`](https://www.npmjs.com/package/@byteslice/result)\<[`GetLockContextResponse`](/stack/reference/stack/latest/packages/stack/src/identity/type-aliases/GetLockContextResponse), [`EncryptionError`](/stack/reference/stack/latest/packages/stack/src/errors/interfaces/EncryptionError)>>
A `Result` containing the CTS token and identity context, or an error
if [identify](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext#identify) has not been called.
# EncryptionErrorTypes
[**@cipherstash/stack**](../../../../..)
***
Variable: EncryptionErrorTypes [#variable-encryptionerrortypes]
```ts
const EncryptionErrorTypes: {
ClientInitError: string;
EncryptionError: string;
DecryptionError: string;
LockContextError: string;
CtsTokenError: string;
};
```
Defined in: [packages/stack/src/errors/index.ts:3](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/errors/index.ts#L3)
Type Declaration [#type-declaration]
ClientInitError [#clientiniterror]
```ts
ClientInitError: string = 'ClientInitError';
```
EncryptionError [#encryptionerror]
```ts
EncryptionError: string = 'EncryptionError';
```
DecryptionError [#decryptionerror]
```ts
DecryptionError: string = 'DecryptionError';
```
LockContextError [#lockcontexterror]
```ts
LockContextError: string = 'LockContextError';
```
CtsTokenError [#ctstokenerror]
```ts
CtsTokenError: string = 'CtsTokenError';
```
# Context
[**@cipherstash/stack**](../../../../..)
***
Type Alias: Context [#type-alias-context]
```ts
type Context = {
identityClaim: string[];
};
```
Defined in: [packages/stack/src/identity/index.ts:17](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L17)
Properties [#properties]
identityClaim [#identityclaim]
```ts
identityClaim: string[];
```
Defined in: [packages/stack/src/identity/index.ts:18](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L18)
# CtsRegions
[**@cipherstash/stack**](../../../../..)
***
Type Alias: CtsRegions [#type-alias-ctsregions]
```ts
type CtsRegions = "ap-southeast-2";
```
Defined in: [packages/stack/src/identity/index.ts:6](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L6)
# CtsToken
[**@cipherstash/stack**](../../../../..)
***
Type Alias: CtsToken [#type-alias-ctstoken]
```ts
type CtsToken = {
accessToken: string;
expiry: number;
};
```
Defined in: [packages/stack/src/identity/index.ts:12](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L12)
Properties [#properties]
accessToken [#accesstoken]
```ts
accessToken: string;
```
Defined in: [packages/stack/src/identity/index.ts:13](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L13)
***
expiry [#expiry]
```ts
expiry: number;
```
Defined in: [packages/stack/src/identity/index.ts:14](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L14)
# GetLockContextResponse
[**@cipherstash/stack**](../../../../..)
***
Type Alias: GetLockContextResponse [#type-alias-getlockcontextresponse]
```ts
type GetLockContextResponse = {
ctsToken: CtsToken;
context: Context;
};
```
Defined in: [packages/stack/src/identity/index.ts:26](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L26)
Properties [#properties]
ctsToken [#ctstoken]
```ts
ctsToken: CtsToken;
```
Defined in: [packages/stack/src/identity/index.ts:27](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L27)
***
context [#context]
```ts
context: Context;
```
Defined in: [packages/stack/src/identity/index.ts:28](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L28)
# IdentifyOptions
[**@cipherstash/stack**](../../../../..)
***
Type Alias: IdentifyOptions [#type-alias-identifyoptions]
```ts
type IdentifyOptions = {
fetchFromCts?: boolean;
};
```
Defined in: [packages/stack/src/identity/index.ts:8](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L8)
Properties [#properties]
fetchFromCts? [#fetchfromcts]
```ts
optional fetchFromCts: boolean;
```
Defined in: [packages/stack/src/identity/index.ts:9](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L9)
# LockContextOptions
[**@cipherstash/stack**](../../../../..)
***
Type Alias: LockContextOptions [#type-alias-lockcontextoptions]
```ts
type LockContextOptions = {
context?: Context;
ctsToken?: CtsToken;
};
```
Defined in: [packages/stack/src/identity/index.ts:21](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L21)
Properties [#properties]
context? [#context]
```ts
optional context: Context;
```
Defined in: [packages/stack/src/identity/index.ts:22](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L22)
***
ctsToken? [#ctstoken]
```ts
optional ctsToken: CtsToken;
```
Defined in: [packages/stack/src/identity/index.ts:23](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/identity/index.ts#L23)
# EncryptedColumn
[**@cipherstash/stack**](../../../../..)
***
Class: EncryptedColumn [#class-encryptedcolumn]
Defined in: [packages/stack/src/schema/index.ts:240](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L240)
Constructors [#constructors]
Constructor [#constructor]
```ts
new EncryptedColumn(columnName): EncryptedColumn;
```
Defined in: [packages/stack/src/schema/index.ts:250](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L250)
Parameters [#parameters]
columnName [#columnname]
`string`
Returns [#returns]
`EncryptedColumn`
Methods [#methods]
dataType() [#datatype]
```ts
dataType(castAs): EncryptedColumn;
```
Defined in: [packages/stack/src/schema/index.ts:272](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L272)
Set or override the plaintext data type for this column.
By default all columns are treated as `'string'`. Use this method to specify
a different type so the encryption layer knows how to encode the plaintext
before encrypting.
Parameters [#parameters-1]
castAs [#castas]
The plaintext data type: `'string'`, `'number'`, `'boolean'`, `'date'`, `'bigint'`, or `'json'`.
`"string"` | `"number"` | `"bigint"` | `"boolean"` | `"text"` | `"date"` | `"json"`
Returns [#returns-1]
`EncryptedColumn`
This `EncryptedColumn` instance for method chaining.
Example [#example]
```typescript
import { encryptedColumn } from "@cipherstash/stack/schema"
const dateOfBirth = encryptedColumn("date_of_birth").dataType("date")
```
***
orderAndRange() [#orderandrange]
```ts
orderAndRange(): EncryptedColumn;
```
Defined in: [packages/stack/src/schema/index.ts:294](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L294)
Enable Order-Revealing Encryption (ORE) indexing on this column.
ORE allows sorting, comparison, and range queries on encrypted data.
Use with `encryptQuery` and `queryType: 'orderAndRange'`.
Returns [#returns-2]
`EncryptedColumn`
This `EncryptedColumn` instance for method chaining.
Example [#example-1]
```typescript
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").orderAndRange(),
})
```
***
equality() [#equality]
```ts
equality(tokenFilters?): EncryptedColumn;
```
Defined in: [packages/stack/src/schema/index.ts:318](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L318)
Enable an exact-match (unique) index on this column.
Allows equality queries on encrypted data. Use with `encryptQuery`
and `queryType: 'equality'`.
Parameters [#parameters-2]
tokenFilters? [#tokenfilters]
\{
`kind`: `"downcase"`;
}\[]
Optional array of token filters (e.g. `[{ kind: 'downcase' }]`).
When omitted, no token filters are applied.
Returns [#returns-3]
`EncryptedColumn`
This `EncryptedColumn` instance for method chaining.
Example [#example-2]
```typescript
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality(),
})
```
***
freeTextSearch() [#freetextsearch]
```ts
freeTextSearch(opts?): EncryptedColumn;
```
Defined in: [packages/stack/src/schema/index.ts:353](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L353)
Enable a full-text / fuzzy search (match) index on this column.
Uses n-gram tokenization by default for substring and fuzzy matching.
Use with `encryptQuery` and `queryType: 'freeTextSearch'`.
Parameters [#parameters-3]
opts? [#opts]
Optional match index configuration. Defaults to 3-character ngram
tokenization with a downcase filter, `k=6`, `m=2048`, and `include_original=true`.
tokenizer? [#tokenizer]
\| \{
`kind`: `"standard"`;
}
\| \{
`kind`: `"ngram"`;
`token_length`: `number`;
} = `tokenizerSchema`
token_filters? [#token_filters]
\{
`kind`: `"downcase"`;
}\[] = `...`
k? [#k]
`number` = `...`
m? [#m]
`number` = `...`
include_original? [#include_original]
`boolean` = `...`
Returns [#returns-4]
`EncryptedColumn`
This `EncryptedColumn` instance for method chaining.
Example [#example-3]
```typescript
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").freeTextSearch(),
})
// With custom options
const posts = encryptedTable("posts", {
body: encryptedColumn("body").freeTextSearch({
tokenizer: { kind: "ngram", token_length: 4 },
k: 8,
m: 4096,
}),
})
```
***
searchableJson() [#searchablejson]
```ts
searchableJson(): EncryptedColumn;
```
Defined in: [packages/stack/src/schema/index.ts:391](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L391)
Configure this column for searchable encrypted JSON (STE-Vec).
Enables encrypted JSONPath selector queries (e.g. `'$.user.email'`) and
containment queries (e.g. `{ role: 'admin' }`). Automatically sets the
data type to `'json'`.
When used with `encryptQuery`, the query operation is auto-inferred from
the plaintext type: strings become selector queries, objects/arrays become
containment queries.
Returns [#returns-5]
`EncryptedColumn`
This `EncryptedColumn` instance for method chaining.
Example [#example-4]
```typescript
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const documents = encryptedTable("documents", {
metadata: encryptedColumn("metadata").searchableJson(),
})
```
***
build() [#build]
```ts
build(): {
cast_as: "string" | "number" | "bigint" | "boolean" | "text" | "date" | "json";
indexes: {
ore?: {
};
unique?: {
token_filters?: {
kind: "downcase";
}[];
};
match?: Required<{
tokenizer?: | {
kind: "standard";
}
| {
kind: "ngram";
token_length: number;
};
token_filters?: {
kind: "downcase";
}[];
k?: number;
m?: number;
include_original?: boolean;
}>;
ste_vec?: {
prefix: string;
array_index_mode?: | "all"
| "none"
| {
item?: boolean;
wildcard?: boolean;
position?: boolean;
};
};
};
};
```
Defined in: [packages/stack/src/schema/index.ts:397](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L397)
Returns [#returns-6]
```ts
{
cast_as: "string" | "number" | "bigint" | "boolean" | "text" | "date" | "json";
indexes: {
ore?: {
};
unique?: {
token_filters?: {
kind: "downcase";
}[];
};
match?: Required<{
tokenizer?: | {
kind: "standard";
}
| {
kind: "ngram";
token_length: number;
};
token_filters?: {
kind: "downcase";
}[];
k?: number;
m?: number;
include_original?: boolean;
}>;
ste_vec?: {
prefix: string;
array_index_mode?: | "all"
| "none"
| {
item?: boolean;
wildcard?: boolean;
position?: boolean;
};
};
};
}
```
cast_as [#cast_as]
```ts
cast_as: "string" | "number" | "bigint" | "boolean" | "text" | "date" | "json";
```
indexes [#indexes]
```ts
indexes: {
ore?: {
};
unique?: {
token_filters?: {
kind: "downcase";
}[];
};
match?: Required<{
tokenizer?: | {
kind: "standard";
}
| {
kind: "ngram";
token_length: number;
};
token_filters?: {
kind: "downcase";
}[];
k?: number;
m?: number;
include_original?: boolean;
}>;
ste_vec?: {
prefix: string;
array_index_mode?: | "all"
| "none"
| {
item?: boolean;
wildcard?: boolean;
position?: boolean;
};
};
};
```
indexes.ore? [#indexesore]
```ts
optional ore: {
};
```
indexes.unique? [#indexesunique]
```ts
optional unique: {
token_filters?: {
kind: "downcase";
}[];
};
```
indexes.unique.token_filters? [#indexesuniquetoken_filters]
```ts
optional token_filters: {
kind: "downcase";
}[];
```
indexes.match? [#indexesmatch]
```ts
optional match: Required<{
tokenizer?: | {
kind: "standard";
}
| {
kind: "ngram";
token_length: number;
};
token_filters?: {
kind: "downcase";
}[];
k?: number;
m?: number;
include_original?: boolean;
}>;
```
indexes.ste_vec? [#indexesste_vec]
```ts
optional ste_vec: {
prefix: string;
array_index_mode?: | "all"
| "none"
| {
item?: boolean;
wildcard?: boolean;
position?: boolean;
};
};
```
indexes.ste_vec.prefix [#indexesste_vecprefix]
```ts
prefix: string;
```
indexes.ste_vec.array_index_mode? [#indexesste_vecarray_index_mode]
```ts
optional array_index_mode:
| "all"
| "none"
| {
item?: boolean;
wildcard?: boolean;
position?: boolean;
};
```
***
getName() [#getname]
```ts
getName(): string;
```
Defined in: [packages/stack/src/schema/index.ts:404](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L404)
Returns [#returns-7]
`string`
# EncryptedField
[**@cipherstash/stack**](../../../../..)
***
Class: EncryptedField [#class-encryptedfield]
Defined in: [packages/stack/src/schema/index.ts:197](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L197)
Builder for a nested encrypted field (encrypted but not searchable).
Create with [encryptedField](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedField). Use inside nested objects in [encryptedTable](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedTable);
supports `.dataType()` for plaintext type. No index methods (equality, orderAndRange, etc.).
Constructors [#constructors]
Constructor [#constructor]
```ts
new EncryptedField(valueName): EncryptedField;
```
Defined in: [packages/stack/src/schema/index.ts:201](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L201)
Parameters [#parameters]
valueName [#valuename]
`string`
Returns [#returns]
`EncryptedField`
Methods [#methods]
dataType() [#datatype]
```ts
dataType(castAs): EncryptedField;
```
Defined in: [packages/stack/src/schema/index.ts:223](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L223)
Set or override the plaintext data type for this field.
By default all values are treated as `'string'`. Use this method to specify
a different type so the encryption layer knows how to encode the plaintext
before encrypting.
Parameters [#parameters-1]
castAs [#castas]
The plaintext data type: `'string'`, `'number'`, `'boolean'`, `'date'`, `'text'`, `'bigint'`, or `'json'`.
`"string"` | `"number"` | `"bigint"` | `"boolean"` | `"text"` | `"date"` | `"json"`
Returns [#returns-1]
`EncryptedField`
This `EncryptedField` instance for method chaining.
Example [#example]
```typescript
import { encryptedField } from "@cipherstash/stack/schema"
const age = encryptedField("age").dataType("number")
```
***
build() [#build]
```ts
build(): {
cast_as: "string" | "number" | "bigint" | "boolean" | "text" | "date" | "json";
indexes: {
};
};
```
Defined in: [packages/stack/src/schema/index.ts:228](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L228)
Returns [#returns-2]
```ts
{
cast_as: "string" | "number" | "bigint" | "boolean" | "text" | "date" | "json";
indexes: {
};
}
```
cast_as [#cast_as]
```ts
cast_as: "string" | "number" | "bigint" | "boolean" | "text" | "date" | "json";
```
indexes [#indexes]
```ts
indexes: {
} = {};
```
***
getName() [#getname]
```ts
getName(): string;
```
Defined in: [packages/stack/src/schema/index.ts:235](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L235)
Returns [#returns-3]
`string`
# EncryptedTable
[**@cipherstash/stack**](../../../../..)
***
Class: EncryptedTable [#class-encryptedtablet]
Defined in: [packages/stack/src/schema/index.ts:414](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L414)
Type Parameters [#type-parameters]
T [#t]
`T` *extends* [`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)
Constructors [#constructors]
Constructor [#constructor]
```ts
new EncryptedTable<T>(tableName, columnBuilders): EncryptedTable<T>;
```
Defined in: [packages/stack/src/schema/index.ts:418](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L418)
Parameters [#parameters]
tableName [#tablename]
`string`
columnBuilders [#columnbuilders]
`T`
Returns [#returns]
`EncryptedTable`\<`T`>
Properties [#properties]
tableName [#tablename-1]
```ts
readonly tableName: string;
```
Defined in: [packages/stack/src/schema/index.ts:419](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L419)
***
columnBuilders [#columnbuilders-1]
```ts
readonly columnBuilders: T;
```
Defined in: [packages/stack/src/schema/index.ts:420](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L420)
Methods [#methods]
build() [#build]
```ts
build(): TableDefinition;
```
Defined in: [packages/stack/src/schema/index.ts:442](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L442)
Compile this table schema into a `TableDefinition` used internally by the encryption client.
Iterates over all column builders, calls `.build()` on each, and assembles
the final `{ tableName, columns }` structure. For `searchableJson()` columns,
the STE-Vec prefix is automatically set to `"<tableName>/<columnName>"`.
Returns [#returns-1]
`TableDefinition`
A `TableDefinition` containing the table name and built column configs.
Example [#example]
```typescript
const users = encryptedTable("users", {
email: encryptedColumn("email").equality(),
})
const definition = users.build()
// { tableName: "users", columns: { email: { cast_as: "string", indexes: { unique: ... } } } }
```
# buildEncryptConfig
[**@cipherstash/stack**](../../../../..)
***
Function: buildEncryptConfig() [#function-buildencryptconfig]
```ts
function buildEncryptConfig(...protectTables): {
v: number;
tables: Record<string, Record<string, {
cast_as: "string" | "number" | "bigint" | "boolean" | "text" | "date" | "json";
indexes: {
ore?: {
};
unique?: {
token_filters?: {
kind: "downcase";
}[];
};
match?: {
tokenizer?: | {
kind: "standard";
}
| {
kind: "ngram";
token_length: number;
};
token_filters?: {
kind: "downcase";
}[];
k?: number;
m?: number;
include_original?: boolean;
};
ste_vec?: {
prefix: string;
array_index_mode?: | "all"
| "none"
| {
item?: boolean;
wildcard?: boolean;
position?: boolean;
};
};
};
}>>;
};
```
Defined in: [packages/stack/src/schema/index.ts:680](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L680)
Build an encrypt config from a list of encrypted tables.
Parameters [#parameters]
protectTables [#protecttables]
...[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)>\[]
The list of encrypted tables to build the config from.
Returns [#returns]
```ts
{
v: number;
tables: Record<string, Record<string, {
cast_as: "string" | "number" | "bigint" | "boolean" | "text" | "date" | "json";
indexes: {
ore?: {
};
unique?: {
token_filters?: {
kind: "downcase";
}[];
};
match?: {
tokenizer?: | {
kind: "standard";
}
| {
kind: "ngram";
token_length: number;
};
token_filters?: {
kind: "downcase";
}[];
k?: number;
m?: number;
include_original?: boolean;
};
ste_vec?: {
prefix: string;
array_index_mode?: | "all"
| "none"
| {
item?: boolean;
wildcard?: boolean;
position?: boolean;
};
};
};
}>>;
}
```
An encrypt config object.
v [#v]
```ts
v: number;
```
tables [#tables]
```ts
tables: Record<string, Record<string, {
cast_as: "string" | "number" | "bigint" | "boolean" | "text" | "date" | "json";
indexes: {
ore?: {
};
unique?: {
token_filters?: {
kind: "downcase";
}[];
};
match?: {
tokenizer?: | {
kind: "standard";
}
| {
kind: "ngram";
token_length: number;
};
token_filters?: {
kind: "downcase";
}[];
k?: number;
m?: number;
include_original?: boolean;
};
ste_vec?: {
prefix: string;
array_index_mode?: | "all"
| "none"
| {
item?: boolean;
wildcard?: boolean;
position?: boolean;
};
};
};
}>> = tablesSchema;
```
Example [#example]
```typescript
import { buildEncryptConfig } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality(),
})
const orders = encryptedTable("orders", {
amount: encryptedColumn("amount").dataType("number"),
})
const config = buildEncryptConfig(users, orders)
console.log(config)
```
# encryptedColumn
[**@cipherstash/stack**](../../../../..)
***
Function: encryptedColumn() [#function-encryptedcolumn]
```ts
function encryptedColumn(columnName): EncryptedColumn;
```
Defined in: [packages/stack/src/schema/index.ts:628](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L628)
Define an encrypted column within a table schema.
Creates a `EncryptedColumn` builder for the given column name. Chain index
methods (`.equality()`, `.freeTextSearch()`, `.orderAndRange()`,
`.searchableJson()`) and/or `.dataType()` to configure searchable encryption
and the plaintext data type.
Parameters [#parameters]
columnName [#columnname]
`string`
The name of the database column to encrypt.
Returns [#returns]
[`EncryptedColumn`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedColumn)
A new `EncryptedColumn` builder.
Example [#example]
```typescript
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch().orderAndRange(),
})
```
# encryptedField
[**@cipherstash/stack**](../../../../..)
***
Function: encryptedField() [#function-encryptedfield]
```ts
function encryptedField(valueName): EncryptedField;
```
Defined in: [packages/stack/src/schema/index.ts:654](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L654)
Define an encrypted field for use in nested or structured schemas.
`encryptedField` is similar to [encryptedColumn](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedColumn) but creates an [EncryptedField](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedField)
for nested fields that are encrypted but not searchable (no indexes). Use `.dataType()`
to specify the plaintext type.
Parameters [#parameters]
valueName [#valuename]
`string`
The name of the value field.
Returns [#returns]
[`EncryptedField`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedField)
A new `EncryptedField` builder.
Example [#example]
```typescript
import { encryptedTable, encryptedField } from "@cipherstash/stack/schema"
const orders = encryptedTable("orders", {
details: {
amount: encryptedField("amount").dataType("number"),
currency: encryptedField("currency"),
},
})
```
# encryptedTable
[**@cipherstash/stack**](../../../../..)
***
Function: encryptedTable() [#function-encryptedtable]
```ts
function encryptedTable<T>(tableName, columns): EncryptedTable<T> & T;
```
Defined in: [packages/stack/src/schema/index.ts:592](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L592)
Define an encrypted table schema.
Creates a `EncryptedTable` that maps a database table name to a set of encrypted
column definitions. Pass the resulting object to `Encryption({ schemas: [...] })`
when initializing the client.
The returned object is also a proxy that exposes each column builder directly,
so you can reference columns as `users.email` when calling `encrypt`, `decrypt`,
and `encryptQuery`.
Type Parameters [#type-parameters]
T [#t]
`T` *extends* [`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)
Parameters [#parameters]
tableName [#tablename]
`string`
The name of the database table this schema represents.
columns [#columns]
`T`
An object whose keys are logical column names and values are
[EncryptedColumn](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedColumn) from [encryptedColumn](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedColumn), or nested objects whose
leaves are [EncryptedField](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedField) from [encryptedField](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedField).
Returns [#returns]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<`T`> & `T`
A `EncryptedTable<T> & T` that can be used as both a schema definition
and a column accessor.
Example [#example]
```typescript
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
const users = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch(),
address: encryptedColumn("address"),
})
// Use as schema
const client = await Encryption({ schemas: [users] })
// Use as column accessor
await client.encrypt("hello@example.com", { column: users.email, table: users })
```
# toEqlCastAs
[**@cipherstash/stack**](../../../../..)
***
Function: toEqlCastAs() [#function-toeqlcastas]
```ts
function toEqlCastAs(value):
| "boolean"
| "text"
| "int"
| "small_int"
| "big_int"
| "real"
| "double"
| "date"
| "jsonb";
```
Defined in: [packages/stack/src/schema/index.ts:57](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L57)
Map SDK-facing data types to EQL `cast_as` values.
The SDK accepts developer-friendly types like `'string'` and `'number'`,
but EQL expects PostgreSQL-aligned types like `'text'` and `'double'`.
Parameters [#parameters]
value [#value]
`"string"` | `"number"` | `"bigint"` | `"boolean"` | `"text"` | `"date"` | `"json"`
Returns [#returns]
\| `"boolean"`
\| `"text"`
\| `"int"`
\| `"small_int"`
\| `"big_int"`
\| `"real"`
\| `"double"`
\| `"date"`
\| `"jsonb"`
# CastAs
[**@cipherstash/stack**](../../../../..)
***
Type Alias: CastAs [#type-alias-castas]
```ts
type CastAs = z.infer<typeof castAsEnum>;
```
Defined in: [packages/stack/src/schema/index.ts:158](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L158)
Type-safe alias for [castAsEnum](/stack/reference/stack/latest/packages/stack/src/schema/variables/castAsEnum) used to specify the *unencrypted* data type of a column or value.
This is important because once encrypted, all data is stored as binary blobs.
See [#see]
[castAsEnum](/stack/reference/stack/latest/packages/stack/src/schema/variables/castAsEnum) for possible values.
# ColumnSchema
[**@cipherstash/stack**](../../../../..)
***
Type Alias: ColumnSchema [#type-alias-columnschema]
```ts
type ColumnSchema = z.infer<typeof columnSchema>;
```
Defined in: [packages/stack/src/schema/index.ts:165](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L165)
# EncryptConfig
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptConfig [#type-alias-encryptconfig]
```ts
type EncryptConfig = z.infer<typeof encryptConfigSchema>;
```
Defined in: [packages/stack/src/schema/index.ts:186](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L186)
# EncryptedTableColumn
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptedTableColumn [#type-alias-encryptedtablecolumn]
```ts
type EncryptedTableColumn = {
[key: string]:
| EncryptedColumn
| {
[key: string]:
| EncryptedField
| {
[key: string]:
| EncryptedField
| {
[key: string]: EncryptedField;
};
};
};
};
```
Defined in: [packages/stack/src/schema/index.ts:171](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L171)
Shape of table columns: either top-level [EncryptedColumn](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedColumn) or nested
objects whose leaves are [EncryptedField](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedField). Used with [encryptedTable](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedTable).
Index Signature [#index-signature]
```ts
[key: string]:
| EncryptedColumn
| {
[key: string]:
| EncryptedField
| {
[key: string]:
| EncryptedField
| {
[key: string]: EncryptedField;
};
};
}
```
# EqlCastAs
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EqlCastAs [#type-alias-eqlcastas]
```ts
type EqlCastAs = z.infer<typeof eqlCastAsEnum>;
```
Defined in: [packages/stack/src/schema/index.ts:159](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L159)
# InferEncrypted
[**@cipherstash/stack**](../../../../..)
***
Type Alias: InferEncrypted [#type-alias-inferencryptedt]
```ts
type InferEncrypted<T> = T extends EncryptedTable<infer C> ? { [K in keyof C as C[K] extends EncryptedColumn | EncryptedField ? K : never]: Encrypted } : never;
```
Defined in: [packages/stack/src/schema/index.ts:545](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L545)
Infer the encrypted type from a EncryptedTable schema.
Type Parameters [#type-parameters]
T [#t]
`T` *extends* [`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<`any`>
Example [#example]
```typescript
const users = encryptedTable("users", {
email: encryptedColumn("email").equality(),
})
type UserEncrypted = InferEncrypted<typeof users>
// => { email: Encrypted }
```
# InferPlaintext
[**@cipherstash/stack**](../../../../..)
***
Type Alias: InferPlaintext [#type-alias-inferplaintextt]
```ts
type InferPlaintext<T> = T extends EncryptedTable<infer C> ? { [K in keyof C as C[K] extends EncryptedColumn | EncryptedField ? K : never]: string } : never;
```
Defined in: [packages/stack/src/schema/index.ts:523](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L523)
Infer the plaintext (decrypted) type from a EncryptedTable schema.
Type Parameters [#type-parameters]
T [#t]
`T` *extends* [`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<`any`>
Example [#example]
```typescript
const users = encryptedTable("users", {
email: encryptedColumn("email").equality(),
name: encryptedColumn("name"),
})
type UserPlaintext = InferPlaintext<typeof users>
// => { email: string; name: string }
```
# MatchIndexOpts
[**@cipherstash/stack**](../../../../..)
***
Type Alias: MatchIndexOpts [#type-alias-matchindexopts]
```ts
type MatchIndexOpts = z.infer<typeof matchIndexOptsSchema>;
```
Defined in: [packages/stack/src/schema/index.ts:161](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L161)
# OreIndexOpts
[**@cipherstash/stack**](../../../../..)
***
Type Alias: OreIndexOpts [#type-alias-oreindexopts]
```ts
type OreIndexOpts = z.infer<typeof oreIndexOptsSchema>;
```
Defined in: [packages/stack/src/schema/index.ts:164](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L164)
# SteVecIndexOpts
[**@cipherstash/stack**](../../../../..)
***
Type Alias: SteVecIndexOpts [#type-alias-stevecindexopts]
```ts
type SteVecIndexOpts = z.infer<typeof steVecIndexOptsSchema>;
```
Defined in: [packages/stack/src/schema/index.ts:162](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L162)
# TokenFilter
[**@cipherstash/stack**](../../../../..)
***
Type Alias: TokenFilter [#type-alias-tokenfilter]
```ts
type TokenFilter = z.infer<typeof tokenFilterSchema>;
```
Defined in: [packages/stack/src/schema/index.ts:160](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L160)
# UniqueIndexOpts
[**@cipherstash/stack**](../../../../..)
***
Type Alias: UniqueIndexOpts [#type-alias-uniqueindexopts]
```ts
type UniqueIndexOpts = z.infer<typeof uniqueIndexOptsSchema>;
```
Defined in: [packages/stack/src/schema/index.ts:163](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L163)
# castAsEnum
[**@cipherstash/stack**](../../../../..)
***
Variable: castAsEnum [#variable-castasenum]
```ts
const castAsEnum: ZodDefault<ZodEnum<["bigint", "boolean", "date", "number", "string", "json", "text"]>>;
```
Defined in: [packages/stack/src/schema/index.ts:47](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L47)
SDK-facing data types — developer-friendly aliases accepted by `dataType()`.
# eqlCastAsEnum
[**@cipherstash/stack**](../../../../..)
***
Variable: eqlCastAsEnum [#variable-eqlcastasenum]
```ts
const eqlCastAsEnum: ZodDefault<ZodEnum<["text", "int", "small_int", "big_int", "real", "double", "boolean", "date", "jsonb"]>>;
```
Defined in: [packages/stack/src/schema/index.ts:30](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/schema/index.ts#L30)
EQL cast types — the PostgreSQL-aligned types that EQL actually accepts.
These are stored in the `cast_as` field of the EncryptConfig.
# encryptedSupabase
[**@cipherstash/stack**](../../../../..)
***
Function: encryptedSupabase() [#function-encryptedsupabase]
```ts
function encryptedSupabase(config): EncryptedSupabaseInstance;
```
Defined in: [packages/stack/src/supabase/index.ts:41](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/index.ts#L41)
Create an encrypted Supabase wrapper that transparently handles encryption
and decryption for queries on encrypted columns.
Parameters [#parameters]
config [#config]
[`EncryptedSupabaseConfig`](/stack/reference/stack/latest/packages/stack/src/supabase/type-aliases/EncryptedSupabaseConfig)
Configuration containing the encryption client and Supabase client.
Returns [#returns]
[`EncryptedSupabaseInstance`](/stack/reference/stack/latest/packages/stack/src/supabase/interfaces/EncryptedSupabaseInstance)
An object with a `from()` method that mirrors `supabase.from()` but
auto-encrypts mutations, adds `::jsonb` casts, encrypts filter values, and
decrypts results.
Example [#example]
```typescript
import { Encryption } from '@cipherstash/stack'
import { encryptedSupabase } from '@cipherstash/stack/supabase'
import { encryptedTable, encryptedColumn } from '@cipherstash/stack/schema'
const users = encryptedTable('users', {
name: encryptedColumn('name').freeTextSearch().equality(),
email: encryptedColumn('email').freeTextSearch().equality(),
})
const client = await Encryption({ schemas: [users] })
const eSupabase = encryptedSupabase({ encryptionClient: client, supabaseClient: supabase })
// INSERT - auto-encrypts, auto-converts to PG composite
await eSupabase.from('users', users)
.insert({ name: 'John', email: 'john@example.com', age: 30 })
// SELECT with filter - auto-casts ::jsonb, auto-encrypts search term, auto-decrypts
const { data } = await eSupabase.from('users', users)
.select('id, email, name')
.eq('email', 'john@example.com')
```
# EncryptedQueryBuilder
[**@cipherstash/stack**](../../../../..)
***
Interface: EncryptedQueryBuilder [#interface-encryptedquerybuildert]
Defined in: [packages/stack/src/supabase/types.ts:232](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L232)
Extends [#extends]
* `PromiseLike`\<[`EncryptedSupabaseResponse`](/stack/reference/stack/latest/packages/stack/src/supabase/type-aliases/EncryptedSupabaseResponse)\<`T`\[]>>
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `Record`\<`string`, `unknown`> = `Record`\<`string`, `unknown`>
Methods [#methods]
select() [#select]
```ts
select(columns, options?): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:235](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L235)
Parameters [#parameters]
columns [#columns]
`string`
options? [#options]
head? [#head]
`boolean`
count? [#count]
`"exact"` | `"planned"` | `"estimated"`
Returns [#returns]
`EncryptedQueryBuilder`\<`T`>
***
insert() [#insert]
```ts
insert(data, options?): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:239](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L239)
Parameters [#parameters-1]
data [#data]
`Partial`\<`T`> | `Partial`\<`T`>\[]
options? [#options-1]
count? [#count-1]
`"exact"` | `"planned"` | `"estimated"`
defaultToNull? [#defaulttonull]
`boolean`
onConflict? [#onconflict]
`string`
Returns [#returns-1]
`EncryptedQueryBuilder`\<`T`>
***
update() [#update]
```ts
update(data, options?): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:247](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L247)
Parameters [#parameters-2]
data [#data-1]
`Partial`\<`T`>
options? [#options-2]
count? [#count-2]
`"exact"` | `"planned"` | `"estimated"`
Returns [#returns-2]
`EncryptedQueryBuilder`\<`T`>
***
upsert() [#upsert]
```ts
upsert(data, options?): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:251](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L251)
Parameters [#parameters-3]
data [#data-2]
`Partial`\<`T`> | `Partial`\<`T`>\[]
options? [#options-3]
count? [#count-3]
`"exact"` | `"planned"` | `"estimated"`
onConflict? [#onconflict-1]
`string`
ignoreDuplicates? [#ignoreduplicates]
`boolean`
defaultToNull? [#defaulttonull-1]
`boolean`
Returns [#returns-3]
`EncryptedQueryBuilder`\<`T`>
***
delete() [#delete]
```ts
delete(options?): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:260](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L260)
Parameters [#parameters-4]
options? [#options-4]
count? [#count-4]
`"exact"` | `"planned"` | `"estimated"`
Returns [#returns-4]
`EncryptedQueryBuilder`\<`T`>
***
eq() [#eq]
```ts
eq<K>(column, value): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:263](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L263)
Type Parameters [#type-parameters-1]
K [#k]
`K` *extends* `string`
Parameters [#parameters-5]
column [#column]
`K`
value [#value]
`T`\[`K`]
Returns [#returns-5]
`EncryptedQueryBuilder`\<`T`>
***
neq() [#neq]
```ts
neq<K>(column, value): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:264](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L264)
Type Parameters [#type-parameters-2]
K [#k-1]
`K` *extends* `string`
Parameters [#parameters-6]
column [#column-1]
`K`
value [#value-1]
`T`\[`K`]
Returns [#returns-6]
`EncryptedQueryBuilder`\<`T`>
***
gt() [#gt]
```ts
gt<K>(column, value): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:268](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L268)
Type Parameters [#type-parameters-3]
K [#k-2]
`K` *extends* `string`
Parameters [#parameters-7]
column [#column-2]
`K`
value [#value-2]
`T`\[`K`]
Returns [#returns-7]
`EncryptedQueryBuilder`\<`T`>
***
gte() [#gte]
```ts
gte<K>(column, value): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:269](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L269)
Type Parameters [#type-parameters-4]
K [#k-3]
`K` *extends* `string`
Parameters [#parameters-8]
column [#column-3]
`K`
value [#value-3]
`T`\[`K`]
Returns [#returns-8]
`EncryptedQueryBuilder`\<`T`>
***
lt() [#lt]
```ts
lt<K>(column, value): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:273](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L273)
Type Parameters [#type-parameters-5]
K [#k-4]
`K` *extends* `string`
Parameters [#parameters-9]
column [#column-4]
`K`
value [#value-4]
`T`\[`K`]
Returns [#returns-9]
`EncryptedQueryBuilder`\<`T`>
***
lte() [#lte]
```ts
lte<K>(column, value): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:274](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L274)
Type Parameters [#type-parameters-6]
K [#k-5]
`K` *extends* `string`
Parameters [#parameters-10]
column [#column-5]
`K`
value [#value-5]
`T`\[`K`]
Returns [#returns-10]
`EncryptedQueryBuilder`\<`T`>
***
like() [#like]
```ts
like<K>(column, pattern): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:278](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L278)
Type Parameters [#type-parameters-7]
K [#k-6]
`K` *extends* `string`
Parameters [#parameters-11]
column [#column-6]
`K`
pattern [#pattern]
`string`
Returns [#returns-11]
`EncryptedQueryBuilder`\<`T`>
***
ilike() [#ilike]
```ts
ilike<K>(column, pattern): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:282](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L282)
Type Parameters [#type-parameters-8]
K [#k-7]
`K` *extends* `string`
Parameters [#parameters-12]
column [#column-7]
`K`
pattern [#pattern-1]
`string`
Returns [#returns-12]
`EncryptedQueryBuilder`\<`T`>
***
is() [#is]
```ts
is<K>(column, value): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:286](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L286)
Type Parameters [#type-parameters-9]
K [#k-8]
`K` *extends* `string`
Parameters [#parameters-13]
column [#column-8]
`K`
value [#value-6]
`boolean` | `null`
Returns [#returns-13]
`EncryptedQueryBuilder`\<`T`>
***
in() [#in]
```ts
in<K>(column, values): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:290](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L290)
Type Parameters [#type-parameters-10]
K [#k-9]
`K` *extends* `string`
Parameters [#parameters-14]
column [#column-9]
`K`
values [#values]
`T`\[`K`]\[]
Returns [#returns-14]
`EncryptedQueryBuilder`\<`T`>
***
filter() [#filter]
```ts
filter<K>(
column,
operator,
value): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:294](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L294)
Type Parameters [#type-parameters-11]
K [#k-10]
`K` *extends* `string`
Parameters [#parameters-15]
column [#column-10]
`K`
operator [#operator]
`string`
value [#value-7]
`T`\[`K`]
Returns [#returns-15]
`EncryptedQueryBuilder`\<`T`>
***
not() [#not]
```ts
not<K>(
column,
operator,
value): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:299](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L299)
Type Parameters [#type-parameters-12]
K [#k-11]
`K` *extends* `string`
Parameters [#parameters-16]
column [#column-11]
`K`
operator [#operator-1]
`string`
value [#value-8]
`T`\[`K`]
Returns [#returns-16]
`EncryptedQueryBuilder`\<`T`>
***
or() [#or]
Call Signature [#call-signature]
```ts
or(filters, options?): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:304](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L304)
Parameters [#parameters-17]
filters [#filters]
`string`
options? [#options-5]
referencedTable? [#referencedtable]
`string`
foreignTable? [#foreigntable]
`string`
Returns [#returns-17]
`EncryptedQueryBuilder`\<`T`>
Call Signature [#call-signature-1]
```ts
or(conditions, options?): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:308](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L308)
Parameters [#parameters-18]
conditions [#conditions]
[`PendingOrCondition`](/stack/reference/stack/latest/packages/stack/src/supabase/type-aliases/PendingOrCondition)\[]
options? [#options-6]
referencedTable? [#referencedtable-1]
`string`
foreignTable? [#foreigntable-1]
`string`
Returns [#returns-18]
`EncryptedQueryBuilder`\<`T`>
***
match() [#match]
```ts
match(query): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:312](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L312)
Parameters [#parameters-19]
query [#query]
`Partial`\<`T`>
Returns [#returns-19]
`EncryptedQueryBuilder`\<`T`>
***
order() [#order]
```ts
order<K>(column, options?): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:313](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L313)
Type Parameters [#type-parameters-13]
K [#k-12]
`K` *extends* `string`
Parameters [#parameters-20]
column [#column-12]
`K`
options? [#options-7]
ascending? [#ascending]
`boolean`
nullsFirst? [#nullsfirst]
`boolean`
referencedTable? [#referencedtable-2]
`string`
foreignTable? [#foreigntable-2]
`string`
Returns [#returns-20]
`EncryptedQueryBuilder`\<`T`>
***
limit() [#limit]
```ts
limit(count, options?): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:322](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L322)
Parameters [#parameters-21]
count [#count-5]
`number`
options? [#options-8]
referencedTable? [#referencedtable-3]
`string`
foreignTable? [#foreigntable-3]
`string`
Returns [#returns-21]
`EncryptedQueryBuilder`\<`T`>
***
range() [#range]
```ts
range(
from,
to,
options?): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:326](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L326)
Parameters [#parameters-22]
from [#from]
`number`
to [#to]
`number`
options? [#options-9]
referencedTable? [#referencedtable-4]
`string`
foreignTable? [#foreigntable-4]
`string`
Returns [#returns-22]
`EncryptedQueryBuilder`\<`T`>
***
single() [#single]
```ts
single(): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:331](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L331)
Returns [#returns-23]
`EncryptedQueryBuilder`\<`T`>
***
maybeSingle() [#maybesingle]
```ts
maybeSingle(): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:332](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L332)
Returns [#returns-24]
`EncryptedQueryBuilder`\<`T`>
***
csv() [#csv]
```ts
csv(): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:333](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L333)
Returns [#returns-25]
`EncryptedQueryBuilder`\<`T`>
***
abortSignal() [#abortsignal]
```ts
abortSignal(signal): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:334](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L334)
Parameters [#parameters-23]
signal [#signal]
`AbortSignal`
Returns [#returns-26]
`EncryptedQueryBuilder`\<`T`>
***
throwOnError() [#throwonerror]
```ts
throwOnError(): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:335](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L335)
Returns [#returns-27]
`EncryptedQueryBuilder`\<`T`>
***
returns() [#returns-28]
```ts
returns<U>(): EncryptedQueryBuilder<U>;
```
Defined in: [packages/stack/src/supabase/types.ts:336](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L336)
Type Parameters [#type-parameters-14]
U [#u]
`U` *extends* `Record`\<`string`, `unknown`>
Returns [#returns-29]
`EncryptedQueryBuilder`\<`U`>
***
withLockContext() [#withlockcontext]
```ts
withLockContext(lockContext): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:337](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L337)
Parameters [#parameters-24]
lockContext [#lockcontext]
[`LockContext`](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext)
Returns [#returns-30]
`EncryptedQueryBuilder`\<`T`>
***
audit() [#audit]
```ts
audit(config): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:338](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L338)
Parameters [#parameters-25]
config [#config]
`AuditConfig`
Returns [#returns-31]
`EncryptedQueryBuilder`\<`T`>
***
then() [#then]
```ts
then<TResult1, TResult2>(onfulfilled?, onrejected?): PromiseLike<TResult1 | TResult2>;
```
Defined in: ../node\_modules/typescript/lib/lib.es5.d.ts:1544
Attaches callbacks for the resolution and/or rejection of the Promise.
Type Parameters [#type-parameters-15]
TResult1 [#tresult1]
`TResult1` = [`EncryptedSupabaseResponse`](/stack/reference/stack/latest/packages/stack/src/supabase/type-aliases/EncryptedSupabaseResponse)\<`T`\[]>
TResult2 [#tresult2]
`TResult2` = `never`
Parameters [#parameters-26]
onfulfilled? [#onfulfilled]
The callback to execute when the Promise is resolved.
(`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null`
onrejected? [#onrejected]
The callback to execute when the Promise is rejected.
(`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null`
Returns [#returns-32]
`PromiseLike`\<`TResult1` | `TResult2`>
A Promise for the completion of which ever callback is executed.
Inherited from [#inherited-from]
```ts
PromiseLike.then
```
# EncryptedSupabaseInstance
[**@cipherstash/stack**](../../../../..)
***
Interface: EncryptedSupabaseInstance [#interface-encryptedsupabaseinstance]
Defined in: [packages/stack/src/supabase/types.ts:16](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L16)
Methods [#methods]
from() [#from]
```ts
from<T>(tableName, schema): EncryptedQueryBuilder<T>;
```
Defined in: [packages/stack/src/supabase/types.ts:17](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L17)
Type Parameters [#type-parameters]
T [#t]
`T` *extends* `Record`\<`string`, `unknown`> = `Record`\<`string`, `unknown`>
Parameters [#parameters]
tableName [#tablename]
`string`
schema [#schema]
[`EncryptedTable`](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)>
Returns [#returns]
[`EncryptedQueryBuilder`](/stack/reference/stack/latest/packages/stack/src/supabase/interfaces/EncryptedQueryBuilder)\<`T`>
# SupabaseClientLike
[**@cipherstash/stack**](../../../../..)
***
Interface: SupabaseClientLike [#interface-supabaseclientlike]
Defined in: [packages/stack/src/supabase/types.ts:208](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L208)
Methods [#methods]
from() [#from]
```ts
from(table): any;
```
Defined in: [packages/stack/src/supabase/types.ts:209](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L209)
Parameters [#parameters]
table [#table]
`string`
Returns [#returns]
`any`
# EncryptedSupabaseConfig
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptedSupabaseConfig [#type-alias-encryptedsupabaseconfig]
```ts
type EncryptedSupabaseConfig = {
encryptionClient: EncryptionClient;
supabaseClient: SupabaseClientLike;
};
```
Defined in: [packages/stack/src/supabase/types.ts:11](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L11)
Properties [#properties]
encryptionClient [#encryptionclient]
```ts
encryptionClient: EncryptionClient;
```
Defined in: [packages/stack/src/supabase/types.ts:12](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L12)
***
supabaseClient [#supabaseclient]
```ts
supabaseClient: SupabaseClientLike;
```
Defined in: [packages/stack/src/supabase/types.ts:13](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L13)
# EncryptedSupabaseError
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptedSupabaseError [#type-alias-encryptedsupabaseerror]
```ts
type EncryptedSupabaseError = {
message: string;
details?: string;
hint?: string;
code?: string;
encryptionError?: EncryptionError;
};
```
Defined in: [packages/stack/src/supabase/types.ts:35](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L35)
Properties [#properties]
message [#message]
```ts
message: string;
```
Defined in: [packages/stack/src/supabase/types.ts:36](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L36)
***
details? [#details]
```ts
optional details: string;
```
Defined in: [packages/stack/src/supabase/types.ts:37](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L37)
***
hint? [#hint]
```ts
optional hint: string;
```
Defined in: [packages/stack/src/supabase/types.ts:38](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L38)
***
code? [#code]
```ts
optional code: string;
```
Defined in: [packages/stack/src/supabase/types.ts:39](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L39)
***
encryptionError? [#encryptionerror]
```ts
optional encryptionError: EncryptionError;
```
Defined in: [packages/stack/src/supabase/types.ts:40](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L40)
# EncryptedSupabaseResponse
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptedSupabaseResponse [#type-alias-encryptedsupabaseresponset]
```ts
type EncryptedSupabaseResponse<T> = {
data: T | null;
error: | EncryptedSupabaseError
| null;
count: number | null;
status: number;
statusText: string;
};
```
Defined in: [packages/stack/src/supabase/types.ts:27](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L27)
Type Parameters [#type-parameters]
T [#t]
`T`
Properties [#properties]
data [#data]
```ts
data: T | null;
```
Defined in: [packages/stack/src/supabase/types.ts:28](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L28)
***
error [#error]
```ts
error:
| EncryptedSupabaseError
| null;
```
Defined in: [packages/stack/src/supabase/types.ts:29](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L29)
***
count [#count]
```ts
count: number | null;
```
Defined in: [packages/stack/src/supabase/types.ts:30](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L30)
***
status [#status]
```ts
status: number;
```
Defined in: [packages/stack/src/supabase/types.ts:31](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L31)
***
statusText [#statustext]
```ts
statusText: string;
```
Defined in: [packages/stack/src/supabase/types.ts:32](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L32)
# PendingOrCondition
[**@cipherstash/stack**](../../../../..)
***
Type Alias: PendingOrCondition [#type-alias-pendingorcondition]
```ts
type PendingOrCondition = {
column: string;
op: FilterOp;
value: unknown;
};
```
Defined in: [packages/stack/src/supabase/types.ts:69](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L69)
Properties [#properties]
column [#column]
```ts
column: string;
```
Defined in: [packages/stack/src/supabase/types.ts:70](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L70)
***
op [#op]
```ts
op: FilterOp;
```
Defined in: [packages/stack/src/supabase/types.ts:71](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L71)
***
value [#value]
```ts
value: unknown;
```
Defined in: [packages/stack/src/supabase/types.ts:72](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/supabase/types.ts#L72)
# queryTypes
[**@cipherstash/stack**](../../../../..)
***
Variable: queryTypes [#variable-querytypes]
```ts
const queryTypes: {
orderAndRange: "orderAndRange";
freeTextSearch: "freeTextSearch";
equality: "equality";
steVecSelector: "steVecSelector";
steVecTerm: "steVecTerm";
searchableJson: "searchableJson";
};
```
Defined in: [packages/stack/src/types.ts:253](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L253)
Type Declaration [#type-declaration]
orderAndRange [#orderandrange]
```ts
readonly orderAndRange: "orderAndRange" = 'orderAndRange';
```
freeTextSearch [#freetextsearch]
```ts
readonly freeTextSearch: "freeTextSearch" = 'freeTextSearch';
```
equality [#equality]
```ts
readonly equality: "equality" = 'equality';
```
steVecSelector [#stevecselector]
```ts
readonly steVecSelector: "steVecSelector" = 'steVecSelector';
```
steVecTerm [#stevecterm]
```ts
readonly steVecTerm: "steVecTerm" = 'steVecTerm';
```
searchableJson [#searchablejson]
```ts
readonly searchableJson: "searchableJson" = 'searchableJson';
```
# BulkDecryptPayload
[**@cipherstash/stack**](../../../../..)
***
Type Alias: BulkDecryptPayload [#type-alias-bulkdecryptpayload]
```ts
type BulkDecryptPayload = {
id?: string;
data: | Encrypted
| null;
}[];
```
Defined in: [packages/stack/src/types.ts:215](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L215)
Type Declaration [#type-declaration]
id? [#id]
```ts
optional id: string;
```
data [#data]
```ts
data:
| Encrypted
| null;
```
# BulkDecryptedData
[**@cipherstash/stack**](../../../../..)
***
Type Alias: BulkDecryptedData [#type-alias-bulkdecrypteddata]
```ts
type BulkDecryptedData = DecryptionResult<JsPlaintext | null>[];
```
Defined in: [packages/stack/src/types.ts:216](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L216)
# BulkEncryptPayload
[**@cipherstash/stack**](../../../../..)
***
Type Alias: BulkEncryptPayload [#type-alias-bulkencryptpayload]
```ts
type BulkEncryptPayload = {
id?: string;
plaintext: JsPlaintext | null;
}[];
```
Defined in: [packages/stack/src/types.ts:209](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L209)
Type Declaration [#type-declaration]
id? [#id]
```ts
optional id: string;
```
plaintext [#plaintext]
```ts
plaintext: JsPlaintext | null;
```
# BulkEncryptedData
[**@cipherstash/stack**](../../../../..)
***
Type Alias: BulkEncryptedData [#type-alias-bulkencrypteddata]
```ts
type BulkEncryptedData = {
id?: string;
data: | Encrypted
| null;
}[];
```
Defined in: [packages/stack/src/types.ts:214](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L214)
Type Declaration [#type-declaration]
id? [#id]
```ts
optional id: string;
```
data [#data]
```ts
data:
| Encrypted
| null;
```
# Client
[**@cipherstash/stack**](../../../../..)
***
Type Alias: Client [#type-alias-client]
```ts
type Client = Awaited<ReturnType<typeof newClient>> | undefined;
```
Defined in: [packages/stack/src/types.ts:32](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L32)
Public type re-exports for `@cipherstash/stack/types`.
This module exposes only the public types from the internal types module.
Internal helpers (`queryTypeToFfi`, `queryTypeToQueryOp`, `FfiIndexTypeName`,
`QueryTermBase`) are excluded.
# ClientConfig
[**@cipherstash/stack**](../../../../..)
***
Type Alias: ClientConfig [#type-alias-clientconfig]
```ts
type ClientConfig = {
workspaceCrn?: string;
accessKey?: string;
clientId?: string;
clientKey?: string;
keyset?: KeysetIdentifier;
};
```
Defined in: [packages/stack/src/types.ts:57](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L57)
Properties [#properties]
workspaceCrn? [#workspacecrn]
```ts
optional workspaceCrn: string;
```
Defined in: [packages/stack/src/types.ts:63](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L63)
The CipherStash workspace CRN (Cloud Resource Name).
Format: `crn:<region>.aws:<workspace-id>`.
Can also be set via the `CS_WORKSPACE_CRN` environment variable.
***
accessKey? [#accesskey]
```ts
optional accessKey: string;
```
Defined in: [packages/stack/src/types.ts:70](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L70)
The API access key used for authenticating with the CipherStash API.
Can also be set via the `CS_CLIENT_ACCESS_KEY` environment variable.
Obtain this from the CipherStash dashboard after creating a workspace.
***
clientId? [#clientid]
```ts
optional clientId: string;
```
Defined in: [packages/stack/src/types.ts:77](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L77)
The client identifier used to authenticate with CipherStash services.
Can also be set via the `CS_CLIENT_ID` environment variable.
Generated during workspace onboarding in the CipherStash dashboard.
***
clientKey? [#clientkey]
```ts
optional clientKey: string;
```
Defined in: [packages/stack/src/types.ts:84](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L84)
The client key material used in combination with ZeroKMS for encryption operations.
Can also be set via the `CS_CLIENT_KEY` environment variable.
Generated during workspace onboarding in the CipherStash dashboard.
***
keyset? [#keyset]
```ts
optional keyset: KeysetIdentifier;
```
Defined in: [packages/stack/src/types.ts:92](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L92)
An optional keyset identifier for multi-tenant encryption.
Each keyset provides cryptographic isolation, giving each tenant its own keyspace.
Specify by name (`{ name: "tenant-a" }`) or UUID (`{ id: "..." }`).
Keysets are created and managed in the CipherStash dashboard.
# Decrypted
[**@cipherstash/stack**](../../../../..)
***
Type Alias: Decrypted [#type-alias-decryptedt]
```ts
type Decrypted<T> = OtherFields<T> & DecryptedFields<T>;
```
Defined in: [packages/stack/src/types.ts:167](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L167)
Model with encrypted fields replaced by plaintext (decrypted) values
Type Parameters [#type-parameters]
T [#t]
`T`
# DecryptedFields
[**@cipherstash/stack**](../../../../..)
***
Type Alias: DecryptedFields [#type-alias-decryptedfieldst]
```ts
type DecryptedFields<T> = { [K in keyof T as NonNullable<T[K]> extends Encrypted ? K : never]: null extends T[K] ? string | null : string };
```
Defined in: [packages/stack/src/types.ts:160](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L160)
Type Parameters [#type-parameters]
T [#t]
`T`
# DecryptionResult
[**@cipherstash/stack**](../../../../..)
***
Type Alias: DecryptionResult [#type-alias-decryptionresultt]
```ts
type DecryptionResult<T> = DecryptionSuccess<T> | DecryptionError<T>;
```
Defined in: [packages/stack/src/types.ts:226](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L226)
Result type for individual items in bulk decrypt operations.
Uses `error`/`data` fields (not `failure`/`data`) since bulk operations
can have per-item failures.
Type Parameters [#type-parameters]
T [#t]
`T`
# EncryptOptions
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptOptions [#type-alias-encryptoptions]
```ts
type EncryptOptions = {
column: | EncryptedColumn
| EncryptedField;
table: EncryptedTable<EncryptedTableColumn>;
};
```
Defined in: [packages/stack/src/types.ts:111](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L111)
Options for single-value encrypt operations.
Use a column from your table schema (from [encryptedColumn](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedColumn)) or a nested
field (from [encryptedField](/stack/reference/stack/latest/packages/stack/src/schema/functions/encryptedField)) as the target for encryption.
Properties [#properties]
column [#column]
```ts
column:
| EncryptedColumn
| EncryptedField;
```
Defined in: [packages/stack/src/types.ts:113](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L113)
The column or nested field to encrypt into. From [EncryptedColumn](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedColumn) or [EncryptedField](/stack/reference/stack/latest/packages/stack/src/schema/classes/EncryptedField).
***
table [#table]
```ts
table: EncryptedTable<EncryptedTableColumn>;
```
Defined in: [packages/stack/src/types.ts:114](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L114)
# EncryptQueryOptions
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptQueryOptions [#type-alias-encryptqueryoptions]
```ts
type EncryptQueryOptions = QueryTermBase;
```
Defined in: [packages/stack/src/types.ts:286](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L286)
# Encrypted
[**@cipherstash/stack**](../../../../..)
***
Type Alias: Encrypted [#type-alias-encrypted]
```ts
type Encrypted = CipherStashEncrypted;
```
Defined in: [packages/stack/src/types.ts:41](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L41)
Structural type representing encrypted data stored in the database. Always
carries a ciphertext. See also `EncryptedValue` for branded nominal typing,
and [EncryptedQuery](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptedQuery) for the search-term shape returned by
`encryptQuery`.
# EncryptedFields
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptedFields [#type-alias-encryptedfieldst]
```ts
type EncryptedFields<T> = { [K in keyof T as NonNullable<T[K]> extends Encrypted ? K : never]: T[K] };
```
Defined in: [packages/stack/src/types.ts:152](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L152)
Type Parameters [#type-parameters]
T [#t]
`T`
# EncryptedFromSchema
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptedFromSchema [#type-alias-encryptedfromschemat-s]
```ts
type EncryptedFromSchema<T, S> = { [K in keyof T]: [K] extends [keyof S] ? [S[K & keyof S]] extends [EncryptedColumn | EncryptedField] ? null extends T[K] ? Encrypted | null : Encrypted : T[K] : T[K] };
```
Defined in: [packages/stack/src/types.ts:190](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L190)
Maps a plaintext model type to its encrypted form using the table schema.
Fields whose keys match columns defined in `S` become `Encrypted`;
all other fields retain their original types from `T`.
When `S` is the widened `EncryptedTableColumn` (e.g. when a user passes an
explicit `<User>` type argument without specifying `S`), the type degrades
gracefully to `T` — preserving backward compatibility.
Type Parameters [#type-parameters]
T [#t]
`T`
The plaintext model type (e.g. `{ id: string; email: string }`)
S [#s]
`S` *extends* [`EncryptedTableColumn`](/stack/reference/stack/latest/packages/stack/src/schema/type-aliases/EncryptedTableColumn)
The table schema column definition, inferred from the `table` argument
Example [#example]
```typescript
type User = { id: string; email: string }
// With a schema that defines `email`:
type Encrypted = EncryptedFromSchema<User, { email: EncryptedColumn }>
// => { id: string; email: Encrypted }
```
# EncryptedQuery
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptedQuery [#type-alias-encryptedquery]
```ts
type EncryptedQuery = CipherStashEncryptedQuery;
```
Defined in: [packages/stack/src/types.ts:49](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L49)
Structural type representing an encrypted query term (search needle)
returned by `encryptQuery` / `encryptQueryBulk` for scalar
(`unique` / `match` / `ore`) lookups and `ste_vec_selector` JSON path
queries. Carries no ciphertext — matched against stored values, never
decrypted. JSON containment queries (`ste_vec_term`) return a
storage-shaped [Encrypted](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/Encrypted) payload instead.
# EncryptedQueryResult
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptedQueryResult [#type-alias-encryptedqueryresult]
```ts
type EncryptedQueryResult =
| Encrypted
| EncryptedQuery
| string
| null;
```
Defined in: [packages/stack/src/types.ts:142](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L142)
Result of encryptQuery (single or batch). `eql` return type yields either a
storage payload (`Encrypted`) or a query-only term (`EncryptedQuery`); the
`composite-literal` return types yield a string.
# EncryptedReturnType
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptedReturnType [#type-alias-encryptedreturntype]
```ts
type EncryptedReturnType = "eql" | "composite-literal" | "escaped-composite-literal";
```
Defined in: [packages/stack/src/types.ts:118](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L118)
Format for encrypted query/search term return values
# EncryptedSearchTerm
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptedSearchTerm [#type-alias-encryptedsearchterm]
```ts
type EncryptedSearchTerm =
| Encrypted
| EncryptedQuery
| string;
```
Defined in: [packages/stack/src/types.ts:134](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L134)
Encrypted search term result. `eql` return type yields either a storage
payload (`Encrypted`, for `ste_vec_term`) or a query-only term
(`EncryptedQuery`, for scalar lookups and `ste_vec_selector`); the
`composite-literal` return types yield a string.
# EncryptedValue
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptedValue [#type-alias-encryptedvalue]
```ts
type EncryptedValue = Brand<CipherStashEncrypted, "encrypted">;
```
Defined in: [packages/stack/src/types.ts:35](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L35)
A branded type representing encrypted data. Cannot be accidentally used as plaintext.
# EncryptionClientConfig
[**@cipherstash/stack**](../../../../..)
***
Type Alias: EncryptionClientConfig [#type-alias-encryptionclientconfig]
```ts
type EncryptionClientConfig = {
schemas: AtLeastOneCsTable<EncryptedTable<EncryptedTableColumn>>;
config?: ClientConfig;
};
```
Defined in: [packages/stack/src/types.ts:97](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L97)
Properties [#properties]
schemas [#schemas]
```ts
schemas: AtLeastOneCsTable<EncryptedTable<EncryptedTableColumn>>;
```
Defined in: [packages/stack/src/types.ts:98](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L98)
***
config? [#config]
```ts
optional config: ClientConfig;
```
Defined in: [packages/stack/src/types.ts:99](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L99)
# KeysetIdentifier
[**@cipherstash/stack**](../../../../..)
***
Type Alias: KeysetIdentifier [#type-alias-keysetidentifier]
```ts
type KeysetIdentifier =
| {
name: string;
}
| {
id: string;
};
```
Defined in: [packages/stack/src/types.ts:55](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L55)
# OtherFields
[**@cipherstash/stack**](../../../../..)
***
Type Alias: OtherFields [#type-alias-otherfieldst]
```ts
type OtherFields<T> = { [K in keyof T as NonNullable<T[K]> extends Encrypted ? never : K]: T[K] };
```
Defined in: [packages/stack/src/types.ts:156](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L156)
Type Parameters [#type-parameters]
T [#t]
`T`
# QueryTypeName
[**@cipherstash/stack**](../../../../..)
***
Type Alias: QueryTypeName [#type-alias-querytypename]
```ts
type QueryTypeName =
| "orderAndRange"
| "freeTextSearch"
| "equality"
| "steVecSelector"
| "steVecTerm"
| "searchableJson";
```
Defined in: [packages/stack/src/types.ts:242](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L242)
User-facing query type names for encrypting query values.
* `'equality'`: Exact match. [Exact Queries](https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption)
* `'freeTextSearch'`: Text search. [Match Queries](https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption)
* `'orderAndRange'`: Comparison and range. [Range Queries](https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption)
* `'steVecSelector'`: JSONPath selector (e.g. `'$.user.email'`)
* `'steVecTerm'`: Containment (e.g. `{ role: 'admin' }`)
* `'searchableJson'`: Auto-infers selector or term from plaintext type (recommended)
# ScalarQueryTerm
[**@cipherstash/stack**](../../../../..)
***
Type Alias: ScalarQueryTerm [#type-alias-scalarqueryterm]
```ts
type ScalarQueryTerm = QueryTermBase & {
value: JsPlaintext;
};
```
Defined in: [packages/stack/src/types.ts:288](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L288)
Type Declaration [#type-declaration]
value [#value]
```ts
value: JsPlaintext;
```
# SearchTerm
[**@cipherstash/stack**](../../../../..)
***
Type Alias: SearchTerm [#type-alias-searchterm]
```ts
type SearchTerm = {
value: JsPlaintext;
column: EncryptedColumn;
table: EncryptedTable<EncryptedTableColumn>;
returnType?: EncryptedReturnType;
};
```
Defined in: [packages/stack/src/types.ts:123](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L123)
Properties [#properties]
value [#value]
```ts
value: JsPlaintext;
```
Defined in: [packages/stack/src/types.ts:124](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L124)
***
column [#column]
```ts
column: EncryptedColumn;
```
Defined in: [packages/stack/src/types.ts:125](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L125)
***
table [#table]
```ts
table: EncryptedTable<EncryptedTableColumn>;
```
Defined in: [packages/stack/src/types.ts:126](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L126)
***
returnType? [#returntype]
```ts
optional returnType: EncryptedReturnType;
```
Defined in: [packages/stack/src/types.ts:127](https://github.com/cipherstash/stack/blob/917b5c00e2c173f90733fa1770c32203e8940879/packages/stack/src/types.ts#L127)