---
title: Encrypting parameters
description: Learn how to encrypt parameters so that only certain values can be passed to generate your image.
url: "https://vercel.com/kb/guide/encrypting-parameters"
published: 2025-11-04
last_updated: 2026-10-16
authors: DX Team
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

You can use the following code sample to explore using parameters and different content types with [`next/og`](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image#generate-images-using-code-js-ts-tsx). To learn more about OG Image Generation, see [Open Graph Image Generation](https://vercel.com/docs/og-image-generation).

This is the directory structure for these files:

```plaintext
app
├── api
│   └── encrypted
│       └── route.tsx
└── encrypted
    └── [id]
        └── page.tsx
```
```ts
import { ImageResponse } from 'next/og';
// App router includes @vercel/og.
// No need to install it.

const key = crypto.subtle.importKey(
  'raw',
  new TextEncoder().encode('my_secret'),
  { name: 'HMAC', hash: { name: 'SHA-256' } },
  false,
  ['sign'],
);

function toHex(arrayBuffer: ArrayBuffer) {
  return Array.prototype.map
    .call(new Uint8Array(arrayBuffer), (n) => n.toString(16).padStart(2, '0'))
    .join('');
}

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);

  const id = searchParams.get('id');
  const token = searchParams.get('token');

  const verifyToken = toHex(
    await crypto.subtle.sign(
      'HMAC',
      await key,
      new TextEncoder().encode(JSON.stringify({ id })),
    ),
  );

  if (token !== verifyToken) {
    return new Response('Invalid token.', { status: 401 });
  }

  return new ImageResponse(
    (
      <div
        style={{
          display: 'flex',
          fontSize: 40,
          color: 'black',
          background: 'white',
          width: '100%',
          height: '100%',
          padding: '50px 200px',
          textAlign: 'center',
          justifyContent: 'center',
          alignItems: 'center',
        }}
      >
        <h1>Card generated, id={id}.</h1>
      </div>
    ),
    {
      width: 1200,
      height: 630,
    },
  );
}
```
```js
import { ImageResponse } from 'next/og';
// App router includes @vercel/og.
// No need to install it.

const key = crypto.subtle.importKey(
  'raw',
  new TextEncoder().encode('my_secret'),
  { name: 'HMAC', hash: { name: 'SHA-256' } },
  false,
  ['sign'],
);

function toHex(arrayBuffer) {
  return Array.prototype.map
    .call(new Uint8Array(arrayBuffer), (n) => n.toString(16).padStart(2, '0'))
    .join('');
}

export async function GET(request) {
  const { searchParams } = new URL(request.url);

  const id = searchParams.get('id');
  const token = searchParams.get('token');

  const verifyToken = toHex(
    await crypto.subtle.sign(
      'HMAC',
      await key,
      new TextEncoder().encode(JSON.stringify({ id })),
    ),
  );

  if (token !== verifyToken) {
    return new Response('Invalid token.', { status: 401 });
  }

  return new ImageResponse(
    (
      <div
        style={{
          display: 'flex',
          fontSize: 40,
          color: 'black',
          background: 'white',
          width: '100%',
          height: '100%',
          padding: '50px 200px',
          textAlign: 'center',
          justifyContent: 'center',
          alignItems: 'center',
        }}
      >
        <h1>Card generated, id={id}.</h1>
      </div>
    ),
    {
      width: 1200,
      height: 630,
    },
  );
}
```

> If you're not using a framework, you must either add `"type": "module"` to your `package.json` or change your JavaScript Functions' file extensions from `.js` to `.mjs`

Then, you need to create a frontend component that can take an `id` query parameter, which will be passed to the API route you created above.

Create the dynamic route `[id]/page` under `/app/encrypted` and paste the following code:

```ts
// This page generates the token to prevent generating OG images with random parameters (`id`).
import { createHmac } from 'node:crypto';

function getToken(id: string): string {
  const hmac = createHmac('sha256', 'my_secret');
  hmac.update(JSON.stringify({ id: id }));
  const token = hmac.digest('hex');
  return token;
}

interface PageParams {
  params: {
    id: string;
  };
}

export default function Page({ params }: PageParams) {
  console.log(params);
  const { id } = params;
  const token = getToken(id);

  return (
    <div>
      <h1>Encrypted Open Graph Image.</h1>
      <p>Only /a, /b, /c with correct tokens are accessible:</p>
      <a
        href={`/api/encrypted?id=${id}&token=${token}`}
        target="_blank"
        rel="noreferrer"
      >
        <code>
          /api/encrypted?id={id}&token={token}
        </code>
      </a>
    </div>
  );
}
```
```js
// This page generates the token to prevent generating OG images with random parameters (`id`).
import { createHmac } from 'node:crypto';

function getToken(id) {
  const hmac = createHmac('sha256', 'my_secret');
  hmac.update(JSON.stringify({ id: id }));
  const token = hmac.digest('hex');
  return token;
}

export default function Page({ params }) {
  console.log(params);
  const { id } = params;
  const token = getToken(id);

  return (
    <div>
      <h1>Encrypted Open Graph Image.</h1>
      <p>Only /a, /b, /c with correct tokens are accessible:</p>
      <a
        href={`/api/encrypted?id=${id}&token=${token}`}
        target="_blank"
        rel="noreferrer"
      >
        <code>
          /api/encrypted?id={id}&token={token}
        </code>
      </a>
    </div>
  );
}
```

> If you're not using a framework, you must either add `"type": "module"` to your `package.json` or change your JavaScript Functions' file extensions from `.js` to `.mjs`

Run your project locally and browse to `http://localhost/encrypted/a`(`b` or `c` will also work).

Click on the generated link to be directed to the generated image.

![og-encrypted.png](https://images.ctfassets.net/hjgychtc108g/7nj6ahMvmV5JACg0Zc7g0I/692d3f597d07028c6cb5a886fa18d5de/og-encrypted.png)

Image generated using \`/api/encrypted?id=a\&token=634dd7e46ec814fb105074b73e26755b0d9966c031dca05d7e7cc65a1619058e\`

In your actual implementation, you will use the code in `/app/encrypted/[id]/page.tsx` with a page to create your post html that will look like this.

## More resources

- [Consume the OG route](/docs/og-image-generation#consume-the-og-route)
  
- [Getting started with OG image](/docs/og-image-generation#usage)

## More Next.js guides

- [Next.js on Vercel vs Webflow Cloud](/kb/guide/next-js-on-vercel-vs-webflow-cloud): Compare running Next.js on Vercel Functions with Fluid compute against Webflow Cloud on Cloudflare Workers. Learn how Next.js feature support, runtimes, storage, pricing, and developer experience differ so you can choose the right platform.
- [Migrate a Next.js app from Webflow Cloud to Vercel](/kb/guide/migrate-a-next-js-app-from-webflow-cloud-to-vercel): Move your Next.js app from Webflow Cloud to Vercel: remove the OpenNext Cloudflare adapter, drop the base path, map storage bindings to Vercel Blob, Redis, and Postgres, and deploy with Git or the vercel CLI.
- [Vercel vs Webflow Cloud](/kb/guide/vercel-vs-webflow-cloud): Compare Vercel and Webflow Cloud for deploying Next.js and Astro apps, including runtime, framework support, storage, pricing, and where your code runs.