# Adding CipherStash Encryption to a Next.js app that uses the Supabase SDK

*Published on 2025-03-26T00:00:00.000Z*

*By CJ Brewer*

Let's look at how to add CipherStash Encryption to a Next.js app that uses the Supabase SDK and server actions to read and insert encrypted data into a Supabase Postgres instance.

## Content

{% callout type="important" title="Use Stack instead" %}
Stack supersedes Protect.js. For new Next.js + Supabase projects, read the current Stack-based guide instead: [Adding CipherStash Stack to a Next.js + Supabase app](/blog/adding-cipherstash-stack-to-nextjs-supabase) — covers both App Router and Pages Router with the API-compatible `encryptedSupabase` wrapper. This post stays up for reference.
{% /callout %}


This is the first installment of the CipherStash Encryption tutorial blog series. In these posts I’ll be covering how you can integrate CipherStash Encryption into popular frameworks and tools, rather than just focusing on business value. I want to show how convenient it is to add CipherStash Encryption to your tech stack and gain all the security benefits of CipherStash Encryption!

This post will cover how to add CipherStash Encryption to a Next.js app that uses the Supabase SDK and server actions to read and insert encrypted data into a Supabase Postgres instance.

For the security-minded folks, it’s worth calling out some points about CipherStash encryption before continuing:
- CipherStash encryption is backed by ZeroKMS which will use a unique encryption key for every single record, also referred to as field-level encryption

    This enables cryptographically proven audit trails of decryption events. In simpler terms, we can provide you an audit log of every data access event to help meet compliance and stringent customer requirements.

- CipherStash Encryption encrypts data using AES-256-GCM-SIV with a unique key per value
- ZeroKMS supports bulk operations, and is [up to 14x faster than AWS KMS](https://cipherstash.com/blog/introducing-protectjs)

The following tutorial assumes a few things:
- You have an existing Next.js app
- You have an existing Supabase instance

## **Step 1: Add CipherStash Encryption**

Install the CipherStash Encryption npm package:

```jsx
npm install @cipherstash/stack
```

Under the hood, CipherStash Encryption uses the [Neon framework](https://github.com/neon-bindings/neon) to create JavaScript Rust bindings to the CipherStash SDK which handles the crypto functions and communication with ZeroKMS. Given this, you'll need to use the native Node.js `require` which is achieved by opting out of server bundling.

In your `next.config.json` file add the following directive:

```jsx
// Referenced in the Next.js docs: https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages
const nextConfig = {
  serverExternalPackages: ['@cipherstash/stack'],
}

module.exports = nextConfig
```

## **Step 2:  Install the CipherStash CLI**

The CLI is used to bootstrap your CipherStash environment so go ahead and install it:

```bash
brew install cipherstash/tap/stash
```

If you're using Linux, [follow the instructions in the docs](https://cipherstash.com/docs/stack/cipherstash/encryption/getting-started).

## **Step 3: Bootstrap your environment**

Now that the CLI is installed, run the setup command and follow the prompts to either login/signup, create a new keyset, and store the settings in the TOML files.

```bash
stash setup
```

When it's time to go to production, you can run this process again and output the settings as environment variables which you can then store securely 🚀.

Before continuing to the next step, make sure that you have the following files in the root of your project directory.

- `cipherstash.toml` 
- `cipherstash.secret.toml` (the CLI tool will automatically append this file name to your `.gitignore` file to ensure it is not checked into source)

## **Step 4: Init the Encryption client**

CipherStash Encryption uses a domain-specific schema definition to configure the state of CipherStash Encryption. This state will determine how to encrypt the data, whether you need encrypted indexes or not. Encrypted indexes is how CipherStash encryption enables the use of Searchable Encryption, which you can [read more about it in the docs](https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption).

Create a `protect` directory in either the root of your app, or in the `src` directory if you have that configured for your project. Also create the following files inside the `protect` directory:
- `index.ts`
- `schema.ts`

Open the `schema.ts` file and define a schema using the exported helper functions:

```jsx
import { encryptedTable, encryptedColumn } from ‘@cipherstash/stack/schema’

// The encryptedTable function takes two arguments:
// - the corresponding table name in your Supabase database
// - An object, where the keys are the columns that store encrypted data
export const users = encryptedTable(‘users’, {
	// The encryptedColumn function takes a single argument:
	// - the corresponding column name in your Supabase table
	email: encryptedColumn(‘email’),
})
```

Now that a schema is defined, open the `protect/index.ts` file and initialize the Encryption client:

```jsx
import { Encryption } from ‘@cipherstash/stack’
import { users } from ‘./schema’

// The Encryption function takes N number of arguments,
// where each argument is a CipherStash Encryption schema.
export const encryptionClient = await Encryption(users)
```

These patterns might look familiar and that’s because when we were creating the CipherStash Encryption interface we didn’t want to re-invent the wheel, but rather use existing patterns leveraged in other libraries (e.g. [Drizzle ORM](https://orm.drizzle.team/) was a major influence).

## **Step 5: Update your Supabase schema**

Encrypted data is stored using the data type `jsonb` so update the column in the Supabase instance. 

![jsonb-encrypted-data](/images/blog/add-protectjs-to-nextjs-app-with-supabase-sdk-aoawne.png)

In most cases, this tutorial is meant to be run through with a playground environment where the environment can be modified easily and without side effects. If you're looking to modify a production instance, you'll need to consider a migration plan. If you’d like to chat about what that might look like, our solutions team would love to help out. Multiple contact options are available [here](https://cipherstash.com/contact).

## **Step 6: Encrypt data**

You can use the `encrypt` and `bulkEncrypt` functions in any server side component (which includes [Vercel Edge Functions](https://vercel.com/docs/functions) or [Cloudflare workers](https://developers.cloudflare.com/workers/runtime-apis/nodejs/)). Each operation in the CipherStash Encryption library leverages a [Result pattern](https://github.com/ByteSliceHQ/byteslice) which is outlined in the code example below:

```jsx
import { encryptionClient } from '@/encryption'
import { users } from '@/encryption/schema'

// The encrypt function takes two arguments:
// - the plaintext data
// - an object defining the table and column that the data will be stored into
const result = encryptionClient.encrypt('plaintext', {
	table: users,
	column: users.email,
})

// The result will alway return either a failure OR data key but never both
if (result.failure) {
	// handle the failure explicitly
}

const encryptedPayload = result.data
```

## **Step 7: Insert the data using the Supabase SDK**

The [Supabase SDK for JavaScript](https://supabase.com/docs/reference/javascript/insert) makes it really easy to insert data directly into your Supabase instance. Given you’ve already configured the SDK, you would insert the data like this:

```jsx
const { data, error } = await supabase
    .from('users')
    .insert([{ email: encryptedPayload }])
    .single()
```

If you want to learn more about the types for the encrypted payload data, read more about [Encrypted Query Language format in the docs](https://github.com/cipherstash/encrypt-query-language/blob/main/docs/reference/PAYLOAD.md).

Typically you’d handle the inserting of data in a [Next.js server action](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations)  or through your API!

## Conclusion

Now you’ve seen how easy it is to integrate CipherStash encryption into your Next.js project, using CipherStash Encryption and storing the encrypted data in a Supabase Postgres instance! As you try it out, keep in mind all of the security enhancements you’re getting with field-level encryption, plus those handy audit logs of every data access event.

That’s it for this first installment of the CipherStash Encryption tutorial series!
Stay tuned for more tutorials where we’ll dive into advanced features, like searchable encryption, and continue to provide examples for other popular databases and frameworks. Cheers!

If you’re interested in giving CipherStash Encryption a try, [install the npm package](https://github.com/cipherstash/stack) and [create a CipherStash account](https://dashboard.cipherstash.com/sign-up)!

## Related blog posts

- [Introducing Protect.js](https://cipherstash.com/blog/introducing-protectjs.md) — Protecting data that’s sensitive (such as personal health or financial information) is crucial to building applications that users trust. But getting access control right is a tricky business. Complex requirements and a plethora of tools, as well as performance considerations, can kill dev team productivity.
- [And now… CipherStash Proxy!](https://cipherstash.com/blog/introducing-proxy.md) — CipherStash Proxy keeps your sensitive data in PostgreSQL encrypted and searchable, without changing your SQL queries. This means that you can protect your most sensitive data with strong security controls, without slowing down your dev team.

