CipherStashDocs

Schema definition

Define which columns to encrypt, what queries to support, and how to handle nested objects

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

Declare your encryption schema in TypeScript — either in a single file or split across multiple files:

src/encryption/
└── schema.ts          # single file
src/encryption/schemas/
├── users.ts           # per-table files
└── posts.ts

Defining a schema

A schema maps your database tables and columns using encryptedTable and encryptedColumn:

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

Full searchable encryption is only supported in Postgres. See 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.

schema.ts
export const protectedUsers = encryptedTable("users", {
  email: encryptedColumn("email")
    .equality()        // exact match queries
    .freeTextSearch()  // full-text search
    .orderAndRange(),  // sorting and range queries
})
MethodPurposeSQL equivalent
.equality()Exact match lookupsWHERE email = '[email protected]'
.freeTextSearch()Full-text / fuzzy searchWHERE description LIKE '%example%'
.orderAndRange()Sorting, comparison, range queriesORDER BY price ASC
.searchableJson()Encrypted JSONB path and containment queriesWHERE metadata @> '{"role":"admin"}'

Only enable the indexes you need — each additional index type has a performance cost.

Equality token filters

The .equality() method accepts an optional array of token filters that are applied before indexing:

schema.ts
email: encryptedColumn("email").equality([{ kind: "downcase" }])
FilterDescription
{ 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 for details.

Data types

Use .dataType() to specify the plaintext type for a column:

schema.ts
encryptedColumn("age").dataType("number").orderAndRange()
Data typeDescription
'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

Customize the tokenizer and filter settings for .freeTextSearch():

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,
})
OptionTypeDefaultDescription
tokenizer{ kind: 'standard' } or { kind: 'ngram', token_length: number }{ kind: 'ngram', token_length: 3 }Tokenization strategy
token_filtersTokenFilter[][{ kind: 'downcase' }]Filters applied to tokens before indexing
knumber6Number of hash functions for the bloom filter
mnumber2048Size of the bloom filter in bits
include_originalbooleantrueWhether to include the original value in the index

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.

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 the values you supply to the encryptedField or encryptedColumn functions. These values are meant to be unique, and may cause unexpected behavior if they are not defined correctly.

Encrypted JSONB

For columns that store JSON objects, use .searchableJson() to enable encrypted JSONB queries:

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

Pass multiple schemas when initializing the client:

encryption/index.ts
import { Encryption } from "@cipherstash/stack"

const client = await Encryption({ schemas: [protectedUsers, documents] })

Type inference

Infer plaintext and encrypted types from your schema:

schema.ts
import type { InferPlaintext, InferEncrypted } from "@cipherstash/stack/schema"

type UserPlaintext = InferPlaintext<typeof protectedUsers>
// { email: string; ... }

type UserEncrypted = InferEncrypted<typeof protectedUsers>
// { email: Encrypted; ... }

Client-safe exports

For client-side code where the native FFI module is not available, import schema builders from the @cipherstash/stack/client subpath:

schema.ts
import { encryptedTable, encryptedColumn } from "@cipherstash/stack/client"

This exports schema builders and types only — no native module dependency.

On this page