diff --git a/content/docs/get-started/choose-your-stack.mdx b/content/docs/get-started/choose-your-stack.mdx
new file mode 100644
index 0000000..94f9e5c
--- /dev/null
+++ b/content/docs/get-started/choose-your-stack.mdx
@@ -0,0 +1,105 @@
+---
+title: Choose your stack
+description: "Pick an integration path: SDK or Proxy, your Postgres platform, your ORM, and your identity provider."
+type: guide
+components: [encryption, proxy, eql, platform]
+audience: [developer, cto]
+---
+
+Four decisions, in order. The first one matters most; the rest usually follow from what you already run.
+
+## 1. SDK or Proxy
+
+| | [Stack SDK](/reference/stack) | [Proxy](/reference/proxy) |
+|---|---|---|
+| Where encryption happens | In your application process | In the proxy, which you run |
+| Application changes | Encrypt and decrypt calls, or an ORM wrapper that adds them for you | Point your connection string at the proxy |
+| Best when | You own the code and want fine-grained control | You cannot change the application, or it isn't TypeScript |
+| Language | TypeScript / JavaScript | Any. It speaks the Postgres wire protocol |
+| Runs where | Your app | A container or sidecar in your infrastructure |
+
+Both produce the same ciphertext and both speak [EQL](/reference/eql), so this is not a one-way door. You can move a table from one to the other without re-encrypting it.
+
+Start with the SDK unless you already know you can't change the application.
+
+## 2. Your Postgres
+
+EQL installs into any Postgres you can connect to. It needs no extension, no superuser, and no `postgresql.conf` changes.
+
+| Platform | Works | Notes |
+|---|---|---|
+| Supabase | Yes | [Supabase integration](/integrations/supabase). Expose the `eql_v3` schema in the dashboard. |
+| Neon, RDS, Aurora, Cloud SQL | Yes | `stash eql install` detects a non-superuser role and adapts. |
+| Self-hosted Postgres | Yes | Full operator-class support, so the ORE ordering mechanism is available. |
+| Postgres behind PgBouncer | Yes | Transaction pooling is fine. Proxy has its own pooling. |
+| DynamoDB | Yes, without EQL | [DynamoDB integration](/stack/cipherstash/encryption/dynamodb). Encrypted attributes and HMAC key lookups; no EQL, because there's no Postgres. |
+| MySQL, MongoDB, others | Not yet | The SDK still encrypts values for you, but nothing is queryable server-side. |
+
+
+Managed platforms usually block custom operator class creation. That's why the default ordering mechanism is moving to OPE, which indexes under Postgres's default btree operator class. See [SEM specifiers](/reference/eql/core-concepts#sem-specifiers).
+
+
+## 3. Your ORM
+
+| Query layer | Path | What it looks like |
+|---|---|---|
+| Drizzle | [Drizzle integration](/stack/cipherstash/encryption/drizzle) | An `encryptedType` column and typed query operators. Your Drizzle code keeps its shape. |
+| Supabase JS | [Supabase integration](/integrations/supabase) | `encryptedSupabase` wraps the client. `.eq()`, `.like()`, `.gt()` keep working. |
+| Prisma | [Prisma integration](/stack/cipherstash/encryption/prisma-next) | Encrypt in the data layer around Prisma calls. |
+| Raw SQL | [Quickstart](/get-started/quickstart) | Encrypt the value, insert it, cast the query operand. |
+| An ORM we don't list | The SDK | Encrypt before write, decrypt after read. Every ORM supports that. |
+
+If you use no ORM, nothing is missing. The SDK's `encrypt` and `encryptQuery` are the whole surface.
+
+## 4. Identity binding
+
+Reach for this when a value should be decryptable only by the user it belongs to, rather than by anything holding the application's credentials. It is also what shrinks the blast radius of a compromised application process.
+
+It requires an OIDC provider, registered on the [OIDC providers](https://dashboard.cipherstash.com/workspaces/_/oidc-providers) page in the Dashboard.
+
+| Provider | Supported |
+|---|---|
+| Supabase Auth | Yes |
+| Clerk | Yes |
+| Auth0 | Yes |
+| Okta | Yes |
+| Any OIDC issuer | Yes, if it publishes a discovery endpoint |
+
+See [provable access control](/solutions/provable-access) for what this buys you, and what it does not.
+
+## Where CipherStash sits among the encryption you already have
+
+Encryption at rest and in transit are not alternatives to this. They protect different things, and you should keep both.
+
+| | At rest | In transit | In use |
+|---|---|---|---|
+| Protects the disk | Yes | No | Yes |
+| Protects the wire | No | Yes | Yes |
+| Protects during a query | No | No | Yes |
+| Plaintext visible to the database | Yes | Yes | **No** |
+| Queryable without decrypting | n/a | n/a | Yes |
+
+A database breach defeats encryption at rest, because the database decrypts on read. That is the gap this fills.
+
+## From development to production
+
+| Stage | Credentials | Setup |
+|---|---|---|
+| Local development | Device-based | `stash init` per developer. No environment variables. |
+| CI and staging | Machine | An application client key, plus `CS_*` environment variables. |
+| Production | Machine | Same, with the client key held in your secret manager. |
+
+Device auth gives each developer their own identity, so local decryptions are attributable. There's no interactive device in CI, hence the switch. See [going to production](/stack/deploy/going-to-production).
+
+## Still not sure
+
+A [discovery session](https://cipherstash.com/contact) is a 60-minute conversation with our engineering team. No slides. Bring whoever owns the data layer, and a compliance lead if that's someone else.
+
+Worth thinking through beforehand:
+
+- Which fields are sensitive, and which regulations cover them.
+- Whether you need those fields **searchable**, or only stored and retrieved. Encrypt-only columns carry no index terms and leak nothing.
+- Whether you need per-user decryption, or per-tenant isolation, or both.
+- Your database, ORM, and deployment target, plus any restriction on native Node modules.
+
+You'll leave with a recommended path and answers to whatever is blocking you.
diff --git a/content/docs/get-started/index.mdx b/content/docs/get-started/index.mdx
index a92e606..14f25bc 100644
--- a/content/docs/get-started/index.mdx
+++ b/content/docs/get-started/index.mdx
@@ -1,9 +1,24 @@
---
title: Get started
-description: "What CipherStash is, a 10-minute quickstart, and how to choose your integration path."
+navTitle: Overview
+description: "Encrypt your first field in ten minutes, then pick the integration path that matches your stack."
type: tutorial
+audience: [developer]
---
-This section is being built as part of the docs V2 overhaul ([CIP-3307](https://linear.app/cipherstash/issue/CIP-3307)). Track progress in [IA.md](https://github.com/cipherstash/docs/blob/v2/IA.md).
+CipherStash encrypts your data at the field level, in your application, and keeps the ciphertext queryable in Postgres. The database never sees plaintext, and neither do we.
-Until it lands, current documentation lives in the [existing docs](/stack).
+This section gets you from nothing to an encrypted, queryable field. It owns no mechanics: each page links to the reference that does.
+
+## Start here
+
+- **[What is CipherStash?](/get-started/what-is-cipherstash)** is the mental model. What the pieces are, what the trade is, and which door to walk through.
+- **[Quickstart](/get-started/quickstart)** is ten minutes of typing. Encrypt a field, store it, query it without decrypting it, and read it back. It works against any Postgres: Supabase, Neon, RDS, or a container.
+- **[Choose your stack](/get-started/choose-your-stack)** picks your integration path: SDK or Proxy, your platform, your ORM, and your identity provider.
+
+## Then pick your path
+
+- **Adopting CipherStash into an existing app?** [Integrations](/integrations) covers the platforms, ORMs, frameworks, and auth providers that wrap your existing client, so your queries keep the shape they already have.
+- **Can't change the application?** [CipherStash Proxy](/reference/proxy) sits in front of Postgres and encrypts on the wire.
+- **Want to understand it before you install it?** [Concepts](/concepts) explains searchable encryption, key management, and identity-aware encryption.
+- **Evaluating for a security review?** [Architecture & security](/security) is written to be read end to end, starting with [Cryptography](/security/cryptography).
diff --git a/content/docs/get-started/meta.json b/content/docs/get-started/meta.json
index 3f92dab..a1ffe31 100644
--- a/content/docs/get-started/meta.json
+++ b/content/docs/get-started/meta.json
@@ -1,5 +1,11 @@
{
"title": "Get started",
"icon": "Rocket",
- "pages": ["..."]
+ "pages": [
+ "index",
+ "what-is-cipherstash",
+ "quickstart",
+ "choose-your-stack",
+ "..."
+ ]
}
diff --git a/content/docs/get-started/quickstart.mdx b/content/docs/get-started/quickstart.mdx
new file mode 100644
index 0000000..999ff0d
--- /dev/null
+++ b/content/docs/get-started/quickstart.mdx
@@ -0,0 +1,203 @@
+---
+title: Quickstart
+description: "Encrypt, store, query, and decrypt your first field in Postgres, using the stash CLI and @cipherstash/stack."
+type: tutorial
+components: [encryption, eql, cli, platform]
+audience: [developer]
+verifiedAgainst:
+ stack: "0.19.0"
+ cli: "0.17.1"
+ eql: "3.0.0"
+---
+
+CipherStash encrypts your data at the field level. Every value gets its own encryption key, and the database never sees plaintext. A breach, a compromised agent, or a curious insider sees ciphertext.
+
+By the end of this page you will have encrypted a field, stored it in Postgres, queried it **without decrypting it**, and read it back. It takes about ten minutes and works with any Postgres: Supabase, Neon, RDS, or a Docker container.
+
+## Before you start
+
+- Node.js 18 or later.
+- A Postgres database you can connect to.
+- A [CipherStash account](https://dashboard.cipherstash.com/sign-up). The free plan is enough.
+
+
+
+
+
+## Initialize your project
+
+```bash cta cta-type="quickstart" example-id="quickstart-stash-init"
+npx stash init
+```
+
+This opens a browser for device-based authentication, so local development needs no environment variables and no shared secrets. `init` then:
+
+1. Connects to your workspace.
+2. Resolves your database connection and runs `stash eql install`, which installs [EQL](/reference/eql), the Postgres surface that makes encrypted columns queryable.
+3. Installs `@cipherstash/stack` if it isn't already present.
+4. Scaffolds an encryption module at `src/encryption/index.ts`.
+5. Writes `.cipherstash/context.json` with what it detected about your project.
+
+See the [CLI reference](/reference/cli/init) for the flags, including `--supabase`, `--drizzle`, and `--prisma-next`.
+
+
+
+
+
+## Install EQL v3
+
+This guide, and the rest of the EQL reference, targets **EQL v3**. `stash init` installs EQL v2 by default today, so ask for v3 explicitly:
+
+```bash example-id="quickstart-eql-v3"
+npx stash eql install --eql-version 3 --force
+```
+
+`--force` reinstalls over the v2 schema that `init` just laid down. On a new database with no encrypted data, that is safe: there is nothing to migrate.
+
+Check what you ended up with:
+
+```bash
+npx stash eql status
+```
+
+
+`--eql-version` accepts `2` or `3` and **defaults to `2`** in `stash` 0.17.1. The default becomes `3` in a future release, at which point this step goes away. Pass the flag until then, so you know which generation you are on rather than inheriting a default that is about to change.
+
+v3 installs directly against the database, so the `--drizzle`, `--migration`, and `--latest` flags do not apply to it. See [installing EQL](/reference/eql) for the full options, including installing across several databases.
+
+
+
+
+
+
+## Define what to encrypt
+
+`init` scaffolds `src/encryption/index.ts`. It declares which columns are encrypted, and exports a ready-to-use client. Edit it to match your table:
+
+```typescript filename="src/encryption/index.ts"
+import { Encryption, encryptedTable, encryptedColumn } from "@cipherstash/stack"
+
+export const users = encryptedTable("users", {
+ email: encryptedColumn("email").equality(),
+})
+
+export const encryptionClient = await Encryption({ schemas: [users] })
+```
+
+`.equality()` declares a **capability**: it is what makes `WHERE email = ?` work later. Add `.orderAndRange()` for `ORDER BY` and comparisons, or `.freeTextSearch()` for token containment. Declare only the capabilities you query on, because each one stores an extra index term alongside the ciphertext.
+
+
+Prefer to have an agent do this? `npx stash plan` inspects your project and drafts `.cipherstash/plan.md` listing the columns worth encrypting. Review it, then `npx stash impl` applies it. This page does it by hand so you can see each moving part.
+
+
+
+
+
+
+## Create the encrypted column
+
+An encrypted column is typed with an EQL domain. The domain you pick has to match the capability you declared: `.equality()` on a text column means `public.text_eq`.
+
+```sql filename="schema.sql"
+CREATE TABLE users (
+ id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
+ email public.text_eq
+);
+```
+
+The type is what fixes the column's searchable surface. There is no separate configuration table. See [core concepts](/reference/eql/core-concepts) for the full variant model.
+
+
+
+
+
+## Encrypt a value and store it
+
+```typescript filename="src/encrypt.ts"
+import { encryptionClient, users } from "./encryption"
+
+const encrypted = await encryptionClient.encrypt("alice@example.com", {
+ column: users.email,
+ table: users,
+})
+
+if (encrypted.failure) {
+ throw new Error(`Encryption failed: ${encrypted.failure.message}`)
+}
+
+await db.query("INSERT INTO users (email) VALUES ($1)", [encrypted.data])
+```
+
+Every operation returns a `Result`: either `data` or `failure`, never both. Encryption happens in your process, so the plaintext never leaves it.
+
+
+
+
+
+## Query without decrypting
+
+Encrypt the search term, then use it in an ordinary `WHERE` clause.
+
+```typescript filename="src/query.ts"
+import { encryptionClient, users } from "./encryption"
+
+const term = await encryptionClient.encryptQuery("alice@example.com", {
+ column: users.email,
+ table: users,
+})
+
+if (term.failure) {
+ throw new Error(`Query encryption failed: ${term.failure.message}`)
+}
+
+const rows = await db.query(
+ "SELECT id, email FROM users WHERE email = $1::public.text_eq",
+ [term.data],
+)
+```
+
+The cast matters. An encrypted operator only resolves against a **typed operand**, so `$1::public.text_eq` is what tells Postgres to compare encrypted terms rather than fall back to raw `jsonb` semantics. See [typed operands](/reference/eql/core-concepts).
+
+Postgres compares ciphertext against ciphertext. It never sees either plaintext.
+
+
+`LIKE` and `ILIKE` do not work on encrypted columns, in any variant. Free-text matching uses bloom-filter token containment (`@>`) on a `text_match` or `text_search` column instead. See [text](/reference/eql/text).
+
+
+
+
+
+
+## Decrypt
+
+```typescript filename="src/decrypt.ts"
+const decrypted = await encryptionClient.decrypt(row.email)
+
+if (decrypted.failure) {
+ throw new Error(`Decryption failed: ${decrypted.failure.message}`)
+}
+
+console.log(decrypted.data) // "alice@example.com"
+```
+
+`decrypt` takes the encrypted payload and nothing else. The payload carries the key ID it needs.
+
+
+
+
+
+## What you just built
+
+You encrypted a field, stored it, queried it without decrypting, and read it back. Underneath:
+
+- **ZeroKMS** returned a key seed, which your application processed with its client key to derive a **unique data key for that value**. The data key existed in your memory for one operation and was then discarded. See [cryptography](/security/cryptography).
+- **Your client key** never left your infrastructure. Without it, nothing decrypts, including CipherStash.
+- **Your keyset** is the isolation boundary. Data encrypted under one keyset cannot be decrypted with another, which is how you separate tenants or environments.
+- **The index term** stored alongside the ciphertext is what let Postgres answer the query. Each capability you declare has a bounded, published leakage profile: an equality term lets an observer see which rows share a value, and nothing more. Declare only the capabilities you query on, so that what a column reveals stays within what your threat model already tolerates. See [searchable encryption](/concepts/searchable-encryption).
+
+## Next steps
+
+- **Using an ORM or Supabase?** The [Drizzle](/stack/cipherstash/encryption/drizzle) and [Supabase](/integrations/supabase) integrations wrap your existing client so queries look normal.
+- **No app changes possible?** [CipherStash Proxy](/reference/proxy) sits in front of Postgres and does this transparently.
+- **Bind decryption to a user.** [Provable access control](/solutions/provable-access) ties a value to the identity that encrypted it.
+- **Ready to deploy?** [Going to production](/stack/deploy/going-to-production) covers the switch from device auth to machine credentials.
diff --git a/content/docs/get-started/what-is-cipherstash.mdx b/content/docs/get-started/what-is-cipherstash.mdx
new file mode 100644
index 0000000..49d4fe1
--- /dev/null
+++ b/content/docs/get-started/what-is-cipherstash.mdx
@@ -0,0 +1,113 @@
+---
+title: What is CipherStash?
+description: "Field-level encryption that stays queryable in Postgres: the mental model, the pieces, and which door to walk through."
+type: concept
+components: [encryption, platform, eql, proxy]
+audience: [developer, cto, ciso]
+---
+
+CipherStash encrypts individual fields in your application, before they reach the database, and keeps them queryable in Postgres. The database stores ciphertext and can still answer `WHERE`, `ORDER BY`, and containment queries against it.
+
+A breach yields ciphertext. So does a compromised agent, an over-permissioned service, and a curious insider.
+
+## The problem
+
+Encrypting a column has always meant losing the query. So teams encrypt at rest, which protects the disk and nothing else: the moment a query runs, the database has plaintext, and so does anyone who reached the database.
+
+That was tolerable when the thing reading your data was a person. It is less so now that it is an agent running on application credentials, at machine speed, one prompt injection away from exfiltration.
+
+## The mental model
+
+Three ideas carry the whole product.
+
+**Encryption happens in your process.** Not in the database, not on our infrastructure. Plaintext never leaves your application.
+
+**Every value gets its own key.** Not a table key or a column key. A per-value key, derived for one operation and discarded. Compromising one value tells an attacker nothing about the next.
+
+**Ciphertext stays queryable.** Alongside each encrypted value, the client stores index terms that let Postgres compare ciphertext without decrypting it. Which terms a column carries is something you choose, and it decides what the database could infer: see [choosing what each column reveals](#choosing-what-each-column-reveals).
+
+## The pieces
+
+```mermaid
+flowchart TB
+ subgraph app["Your application"]
+ sdk["Stack SDK
encrypt / decrypt"]
+ end
+
+ subgraph db["Your Postgres"]
+ eql["EQL
encrypted column types
and operators"]
+ end
+
+ subgraph platform["CipherStash platform"]
+ zerokms["ZeroKMS
key seeds"]
+ cts["CTS
identity tokens"]
+ end
+
+ proxy["Proxy
speaks EQL for you"]
+
+ sdk -- ciphertext + index terms --> eql
+ proxy -- ciphertext + index terms --> eql
+ sdk <-- key seed --> zerokms
+ proxy <-- key seed --> zerokms
+ cts -. binds decryption
to an identity .-> sdk
+```
+
+| Piece | What it does | When you need it |
+|---|---|---|
+| [Stack SDK](/reference/stack) | Encrypts and decrypts values in your application | The default path. Start here. |
+| [EQL](/reference/eql) | The Postgres surface: encrypted column types, operators, and term extractors | Always, if the data lives in Postgres. Installed once per database. |
+| [Proxy](/reference/proxy) | Sits in front of Postgres and speaks EQL for you | When you cannot change the application |
+| ZeroKMS | Returns a key seed per value; never sees your client key or a data key | Always. See [cryptography](/security/cryptography). |
+| CTS | Federates your identity provider so decryption can be bound to a user | Only for [identity-aware encryption](/solutions/provable-access) |
+
+You rarely need all of them at once. The Stack SDK plus EQL is the common case. Proxy is the alternative to the SDK, not an addition to it.
+
+Note that **EQL is only for Postgres**. The SDK also encrypts values that never touch a database, and non-Postgres stores like [DynamoDB](/stack/cipherstash/encryption/dynamodb), with no EQL involved.
+
+## Choosing what each column reveals
+
+Querying ciphertext works because each value carries index terms derived from its plaintext. Those terms are not the plaintext, and what each one reveals is **bounded and published** rather than incidental: each scheme has a formal leakage profile.
+
+That turns leakage into a design choice rather than a surprise. You decide, per column, which capabilities to declare, and that decides what an observer of the database could infer:
+
+| You declare | So you can | An observer could infer |
+|---|---|---|
+| Nothing | Store and decrypt | Nothing from the ciphertext |
+| Equality | `WHERE email = ?`, `GROUP BY`, joins | Which rows share a value |
+| Ordering | `ORDER BY`, ranges, `MIN` / `MAX` | The relative order of values |
+| Free-text | Token containment | Probabilistic token overlap |
+
+The engineering is in matching those choices to your threat model. For most columns, knowing that two rows share some unknown value is not an exposure. For a column whose frequency distribution is itself the secret, it might be, and then you encrypt without that capability and filter after decryption.
+
+So the goal is not to eliminate leakage. It is to choose capabilities whose leakage your threat model already tolerates, and to know exactly what you chose. Declare only what you query on. The per-scheme detail is in [searchable encryption](/concepts/searchable-encryption).
+
+## Which door
+
+**You are building.** Start with the [Quickstart](/get-started/quickstart), then [choose your stack](/get-started/choose-your-stack) to find the integration that matches your platform and ORM.
+
+**You are evaluating.** [Architecture & security](/security) is self-contained for a vendor review, starting with [cryptography](/security/cryptography). [Solutions](/solutions) covers the problems teams bring us.
+
+**You are deciding whether this is the right tool.** [Concepts](/concepts) explains how it works and where it does not apply. The [comparisons](/concepts/compare) are written to be honest about what CipherStash is not.
+
+## Performance
+
+Encryption in use is usually assumed to be slow. It is, if you do it with fully homomorphic encryption. Searchable encryption is a different trade: bounded, published leakage in exchange for the database doing ordinary index work on ciphertext.
+
+| | |
+|---|---|
+| Query overhead | Sub-millisecond. The encrypted operators inline into functional indexes, so Postgres does a normal index scan. |
+| vs fully homomorphic encryption | [410,000x faster](https://github.com/cipherstash/tfhe-ore-bench) on the per-row primitives a database actually executes. |
+| vs AWS KMS | Up to 14x the throughput, because ZeroKMS derives keys in bulk rather than one call per value. |
+
+The FHE comparison is an open benchmark harness you can run yourself. See [CipherStash vs FHE](/stack/reference/comparisons/fhe) for the methodology, and for the workloads where FHE is genuinely the right tool.
+
+## What it protects against
+
+| Threat | What happens |
+|---|---|
+| Database breach | Ciphertext. The keys were never in the database. |
+| Compromised application credentials | Ciphertext, unless the attacker also holds the client key. |
+| AI agent exfiltration | The agent reaches the database and decrypts nothing, because its credentials are not the user's keys. |
+| Curious insider | Every decryption is a key derivation, and ZeroKMS records who performed it. |
+
+What it does **not** protect against: an attacker who has compromised your running application process, holding the client key and able to call `decrypt`. Encryption in the application means the application can decrypt. See [cryptography](/security/cryptography) for the full trust model.
diff --git a/content/docs/index.mdx b/content/docs/index.mdx
index a2f2d22..8d82072 100644
--- a/content/docs/index.mdx
+++ b/content/docs/index.mdx
@@ -1,23 +1,22 @@
---
title: CipherStash Docs
-seoTitle: CipherStash Docs — Searchable encryption for Postgres
-description: "Searchable field-level encryption, identity-bound keys, and cryptographic audit trails — built into your existing Postgres stack."
+seoTitle: "CipherStash Docs: searchable encryption for Postgres"
+description: "Searchable field-level encryption, identity-bound keys, and cryptographic audit trails, built into your existing Postgres stack."
type: concept
audience: [developer, cto, ciso]
---
CipherStash encrypts your data at the field level. Every value gets its own
-key, bound to an identity — and the ciphertext stays queryable in Postgres.
-A breach, a compromised agent, a curious insider: they all see ciphertext
-with no key.
+key, and the ciphertext stays queryable in Postgres. A breach, a compromised
+agent, a curious insider: they all see ciphertext with no key.
## Start here
-
+
-
+
## Browse the docs
@@ -26,9 +25,9 @@ with no key.
-
+
-
+
## AI-ready documentation