# Shopify (/docs/guides/integrations/shopify)

> For the complete Prisma documentation index, see [llms.txt](https://www.prisma.io/docs/llms.txt). A markdown version of any docs page is available by appending `.md` to its URL.

Learn how to use Prisma Postgres with Shopify

Location: Guides > Integrations > Shopify

## Introduction [#introduction]

[Shopify](https://www.shopify.com/) is a popular platform for building e-commerce stores. This guide will show you how to connect a Shopify app to a [Prisma Postgres](https://www.prisma.io/postgres) database in order to create internal notes for products.

## Prerequisites [#prerequisites]

* [Node.js](https://nodejs.org/en/download/)
* [Shopify CLI](https://shopify.dev/docs/api/shopify-cli)
* [Shopify Partner Account](https://www.shopify.com/partners) and a [development store](https://shopify.dev/docs/api/development-stores#create-a-development-store-to-test-your-app)

## 1. Set up your project [#1-set-up-your-project]

> [!NOTE]
> If you do not have the Shopify CLI installed, you can install it with `npm install -g @shopify/cli`.

To start, initialize a new Shopify app using the Shopify CLI:

```bash
shopify app init
```

During setup, you'll be prompted to customize your app. Choose the following options:

> [!NOTE]
> * *Get started building your app:* `Build a Remix app (recommended)`
> * *For your Remix template, which language do you want:* `JavaScript`
> * *App Name:* `prisma-store` *(name cannot contain `shopify`)*

Navigate to the `prisma-store` directory:

```bash
cd prisma-store
```

## 2. Set up Prisma [#2-set-up-prisma]

Prisma comes pre-installed in your project. In this section, you update it to Prisma 7 and switch the app from SQLite to Prisma Postgres.

You will be swapping to a Prisma Postgres database, so delete the `migrations` folder along with the `dev.sqlite` file, inside of the `prisma` directory.

You need to update a few things in the `schema.prisma` file to get it working with Remix and Prisma Postgres.

* Swap to the new `prisma-client` generator.
* Update the provider to `postgresql`.
* Update the url to the new database URL.

```prisma title="prisma/schema.prisma"
generator client {
  provider = "prisma-client-js" // [!code --]
  provider = "prisma-client" // [!code ++]
  output   = "../app/generated/prisma" // [!code ++]
}

datasource db {
  provider = "sqlite" // [!code --]
  provider = "postgresql" // [!code ++]
  url      = "file:../dev.db" // [!code --]
}

model Session {
  // ... existing model
}
```

Create a `prisma.config.ts` file to configure Prisma:

```typescript title="prisma.config.ts"
import "dotenv/config"; // [!code ++]
import { defineConfig, env } from "prisma/config"; // [!code ++]
// [!code ++]
export default defineConfig({
  // [!code ++]
  schema: "prisma/schema.prisma", // [!code ++]
  migrations: {
    // [!code ++]
    path: "prisma/migrations", // [!code ++]
  }, // [!code ++]
  datasource: {
    // [!code ++]
    url: env("DATABASE_URL"), // [!code ++]
  }, // [!code ++]
}); // [!code ++]
```

> [!NOTE]
> Since Shopify apps typically have dotenv pre-installed, you should already have access to it. If not, install it with:
> 
> 
>   
> 
>   #### bun

>     ```bash
>     bun add dotenv
>     ```
>
> 
>   #### pnpm

>     ```bash
>     pnpm add dotenv
>     ```
>
> 
>   #### yarn

>     ```bash
>     yarn add dotenv
>     ```
>
> 
>   #### npm

>     ```bash
>     npm install dotenv
>     ```
>
> 

To enable your app to store notes for each product, add a new `ProductNote` model to your Prisma schema.

This model will allow you to save and organize notes linked to individual products in your database through the `productGid` field.

```prisma title="prisma/schema.prisma"
generator client {
  provider = "prisma-client"
  output   = "../app/generated/prisma"
}

datasource db {
  provider = "postgresql"
}

model Session {
  // ... existing model
}

model ProductNote { // [!code ++]
  id         String   @id @default(uuid()) // [!code ++]
  productGid String // [!code ++]
  body       String? // [!code ++]
  createdAt  DateTime @default(now()) // [!code ++]
  updatedAt  DateTime @updatedAt // [!code ++]
} // [!code ++]
```

Next, Prisma will need to be updated to the latest version. Run:

  

#### bun

```bash
bun add prisma@7.10.0 @types/pg --dev
```

#### pnpm

```bash
pnpm add prisma@7.10.0 @types/pg --save-dev
```

#### yarn

```bash
yarn add prisma@7.10.0 @types/pg --dev
```

#### npm

```bash
npm install prisma@7.10.0 @types/pg --save-dev
```

  

#### bun

```bash
bun add @prisma/client@7.10.0 @prisma/adapter-pg pg
```

#### pnpm

```bash
pnpm add @prisma/client@7.10.0 @prisma/adapter-pg pg
```

#### yarn

```bash
yarn add @prisma/client@7.10.0 @prisma/adapter-pg pg
```

#### npm

```bash
npm install @prisma/client@7.10.0 @prisma/adapter-pg pg
```

> [!NOTE]
> If you are using a different database provider (MySQL, SQL Server, SQLite), install the corresponding driver adapter package instead of `@prisma/adapter-pg`. For more information, see [Database drivers](https://www.prisma.io/docs/orm/v7/core-concepts/supported-databases/database-drivers).

Initialize Prisma in your project:

  

#### bun

```bash
bunx --bun prisma init
```

#### pnpm

```bash
pnpm dlx prisma init
```

#### yarn

```bash
yarn dlx prisma init
```

#### npm

```bash
npx prisma init
```

Then create a Prisma Postgres database:

  

#### bun

```bash
bunx create-db
```

#### pnpm

```bash
pnpm dlx create-db
```

#### yarn

```bash
yarn dlx create-db
```

#### npm

```bash
npx create-db
```

Copy the connection string from the CLI output. It should look similar to this:

```text
DATABASE_URL="postgresql://user:password@host:5432/database?sslmode=require"
```

Replace the generated `DATABASE_URL` in your `.env` file with the value from `npx create-db`.

Apply your database schema:

  

#### bun

```bash
bunx prisma migrate dev --name init
```

#### pnpm

```bash
pnpm dlx prisma migrate dev --name init
```

#### yarn

```bash
yarn dlx prisma migrate dev --name init
```

#### npm

```bash
npx prisma migrate dev --name init
```

Then generate Prisma Client:

  

#### bun

```bash
bunx prisma generate
```

#### pnpm

```bash
pnpm dlx prisma generate
```

#### yarn

```bash
yarn dlx prisma generate
```

#### npm

```bash
npx prisma generate
```

Next, update your `db.server.ts` file to use the newly generated Prisma client with the driver adapter:

```tsx title="app/db.server.ts"
import { PrismaClient } from "@prisma/client"; // [!code --]
import { PrismaClient } from "./generated/prisma/client.js"; // [!code ++]
import { PrismaPg } from "@prisma/adapter-pg"; // [!code ++]
// [!code ++]
const adapter = new PrismaPg({
  // [!code ++]
  connectionString: process.env.DATABASE_URL, // [!code ++]
}); // [!code ++]

if (process.env.NODE_ENV !== "production") {
  if (!global.prismaGlobal) {
    global.prismaGlobal = new PrismaClient(); // [!code --]
    global.prismaGlobal = new PrismaClient({ adapter }); // [!code ++]
  }
}

const prisma = global.prismaGlobal ?? new PrismaClient(); // [!code --]
const prisma = global.prismaGlobal ?? new PrismaClient({ adapter }); // [!code ++]

export default prisma;
```

> [!WARNING]
> It is recommended to add `app/generated/prisma` to your `.gitignore` file.

## 3. Create your Remix model [#3-create-your-remix-model]

Create a new `models/` folder. Inside this folder, add a file named `notes.server.js` to hold the note-related database logic.

The `notes.server.js` file will contain two functions:

* `getNotes` - This will get all the notes for a given product.
* `createNote` - This will create a new note for a given product.

Start by importing the Prisma client from `db.server.ts` and creating the `getNotes` function:

```js title="models/notes.server.js"
import prisma from "../db.server"; // [!code ++]
// [!code ++]
export const getNotes = async (productGid) => {
  // [!code ++]
  const notes = await prisma.productNote.findMany({
    // [!code ++]
    where: { productGid: productGid.toString() }, // [!code ++]
    orderBy: { createdAt: "desc" }, // [!code ++]
  }); // [!code ++]
  return notes; // [!code ++]
}; // [!code ++]
```

To enable users to add new notes to your database, create a function in `notes.server.js` that uses `prisma.productNote.create`:

```js title="models/notes.server.js"
import prisma from "../db.server";

export const getNotes = async (productGid) => {
  const notes = await prisma.productNote.findMany({
    where: { productGid: productGid.toString() },
    orderBy: { createdAt: "desc" },
  });
  return notes;
};

export const createNote = async (note) => {
  // [!code ++]
  const newNote = await prisma.productNote.create({
    // [!code ++]
    data: {
      // [!code ++]
      body: note.body, // [!code ++]
      productGid: note.productGid, // [!code ++]
    }, // [!code ++]
  }); // [!code ++]
  return newNote; // [!code ++]
}; // [!code ++]
```

## 4. Create your layout route [#4-create-your-layout-route]

Before those functions are able to be called, our route needs a layout to sit in. This layout route will feature a button for selecting a product, and will act as the parent for your `ProductNotes` route.

### 4.1. Create the ProductNotesLayout component [#41-create-the-productnoteslayout-component]

Start by creating the folder `routes/app.product-notes.jsx` and adding the `ProductNotesLayout` component inside of it:

```jsx title="app/routes/app.product-notes.jsx"
import { Page, Layout } from "@shopify/polaris"; // [!code ++]
// [!code ++]
export default function ProductNotesLayout() {
  // [!code ++]
  return (
    // [!code ++]
    <Page title="Product Notes">
      {" "}
      // [!code ++]
      <Layout>
        {" "}
        // [!code ++]
        <Layout.Section></Layout.Section> // [!code ++]
      </Layout>{" "}
      // [!code ++]
    </Page> // [!code ++]
  ); // [!code ++]
} // [!code ++]
```

Next, create the `selectProduct` function and a `Button` to let the user pick a product:

```jsx title="app/routes/app.product-notes.jsx"
import { useNavigate } from "@remix-run/react";
import { Page, Layout } from "@shopify/polaris"; // [!code --]
import { Button, Page, Layout } from "@shopify/polaris"; // [!code ++]

export default function ProductNotesLayout() {
  const navigate = useNavigate(); // [!code ++]

  async function selectProduct() {
    // [!code ++]
    const products = await window.shopify.resourcePicker({
      // [!code ++]
      type: "product", // [!code ++]
      action: "select", // [!code ++]
    }); // [!code ++]
    const selectedGid = products[0].id; // [!code ++]
    navigate(`/app/product-notes/${encodeURIComponent(selectedGid)}`); // [!code ++]
  } // [!code ++]

  return (
    <Page title="Product Notes">
      <Layout>
        <Layout.Section>
          <Button onClick={selectProduct} fullWidth size="large">
            {" "}
            // [!code ++] Select Product // [!code ++]
          </Button>{" "}
          // [!code ++]
        </Layout.Section>
      </Layout>
    </Page>
  );
}
```

Remix renders provides the ability to render a nested route. Add an `<Outlet />` to the `routes/app.product-notes.jsx` file where the `ProductNotes` route will be rendered:

```jsx title="app/routes/app.product-notes.jsx"
import { useNavigate } from "@remix-run/react"; // [!code --]
import { Outlet, useNavigate } from "@remix-run/react"; // [!code ++]
import { Page, Button, Layout } from "@shopify/polaris";

export default function ProductNotesLayout() {
  const navigate = useNavigate();

  async function selectProduct() {
    const products = await window.shopify.resourcePicker({
      type: "product",
      action: "select",
    });
    const selectedGid = products[0].id;
    navigate(`/app/product-notes/${encodeURIComponent(selectedGid)}`);
  }

  return (
    <Page title="Product Notes">
      <Layout>
        <Layout.Section>
          <Button onClick={selectProduct} fullWidth size="large">
            Select Product
          </Button>
        </Layout.Section>
        <Outlet /> // [!code ++]
      </Layout>
    </Page>
  );
}
```

### 4.2. Add the ProductNotesLayout to the sidebar [#42-add-the-productnoteslayout-to-the-sidebar]

If you run `npm run dev`, you won't be able to see the `Product Notes` route. To fix this, you need to add the `ProductNotesLayout` to the `app.jsx` file so it shows up in the sidebar:

```jsx title="app/routes/app.jsx"
import { Link, Outlet, useLoaderData, useRouteError } from "@remix-run/react";
import { boundary } from "@shopify/shopify-app-remix/server";
import { AppProvider } from "@shopify/shopify-app-remix/react";
import { NavMenu } from "@shopify/app-bridge-react";
import polarisStyles from "@shopify/polaris/build/esm/styles.css?url";
import { authenticate } from "../shopify.server";

export const links = () => [{ rel: "stylesheet", href: polarisStyles }];

export const loader = async ({ request }) => {
  await authenticate.admin(request);

  return { apiKey: process.env.SHOPIFY_API_KEY || "" };
};

export default function App() {
  const { apiKey } = useLoaderData();

  return (
    <AppProvider isEmbeddedApp apiKey={apiKey}>
      <NavMenu>[ Home ](/app) [Product Notes](/app/product-notes) // [!code ++]</NavMenu>
      <Outlet />
    </AppProvider>
  );
}

// Shopify needs Remix to catch some thrown responses, so that their headers are included in the response.
export function ErrorBoundary() {
  return boundary.error(useRouteError());
}

export const headers = (headersArgs) => {
  return boundary.headers(headersArgs);
};
```

## 5. Create your product notes route [#5-create-your-product-notes-route]

Currently, if you run `npm run dev` and navigate to the `Product Notes` route, you will see nothing once selecting a product.

Follow these steps to create the product notes route:

Create a new `routes/app/app.notes.$productGid.jsx` file which will take in the productGid as a parameter, and return the product notes associated with the product as well as a form to create a new note:

```jsx title="app/routes/app/app.notes.$productGid.jsx"
export default function ProductNotes() {
  // [!code ++]
  return (
    // [!code ++]
    <></> // [!code ++]
  ); // [!code ++]
} // [!code ++]
```

### 5.1. Render the notes [#51-render-the-notes]

On load, the route will need to fetch the notes for the product and display them.

Add a `loader` function to the route:

```jsx title="app/routes/app/app.notes.$productGid.jsx"
import { json } from "@remix-run/node"; // [!code ++]
import { useLoaderData } from "@remix-run/react"; // [!code ++]
import { getNotes } from "../models/note.server"; // [!code ++]

export const loader = async ({ params }) => {
  // [!code ++]
  const { productGid } = params; // [!code ++]
  const notes = await getNotes(productGid); // [!code ++]
  return json({ notes, productGid }); // [!code ++]
}; // [!code ++]

export default function ProductNotes() {
  const { notes, productGid } = useLoaderData(); // [!code ++]

  return <></>;
}
```

Map out the notes in the `ProductNotes` component, using Polaris components:

```jsx title="app/routes/app/app.notes.$productGid.jsx"
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { getNotes } from "../models/note.server";
import { Card, Layout, Text, BlockStack } from "@shopify/polaris"; // [!code ++]

export const loader = async ({ params }) => {
  const { productGid } = params;
  const notes = await getNotes(productGid);
  return json({ notes, productGid });
};

export default function ProductNotes() {
  const { notes, productGid } = useLoaderData();
  return (
    <>
      <Layout.Section>
        {" "}
        // [!code ++]
        <BlockStack gap="200">
          {" "}
          // [!code ++]
          {notes.length === 0 ? ( // [!code ++]
            <Text as="p" variant="bodyMd" color="subdued">
              {" "}
              // [!code ++] No notes yet. // [!code ++]
            </Text> // [!code ++]
          ) : (
            // [!code ++]
            notes.map(
              (
                note, // [!code ++]
              ) => (
                <Card key={note.id} sectioned>
                  {" "}
                  // [!code ++]
                  <BlockStack gap="100">
                    {" "}
                    // [!code ++]
                    {note.body && ( // [!code ++]
                      <Text as="p" variant="bodyMd">
                        {" "}
                        // [!code ++]
                        {note.body} // [!code ++]
                      </Text> // [!code ++]
                    )}{" "}
                    // [!code ++]
                    <Text as="p" variant="bodySm" color="subdued">
                      {" "}
                      // [!code ++] Added: {new Date(note.createdAt).toLocaleString()} // [!code ++]
                    </Text>{" "}
                    // [!code ++]
                  </BlockStack>{" "}
                  // [!code ++]
                </Card> // [!code ++]
              ),
            ) // [!code ++]
          )}{" "}
          // [!code ++]
        </BlockStack>{" "}
        // [!code ++]
      </Layout.Section>{" "}
      // [!code ++]
    </>
  );
}
```

You should be seeing "No notes yet.". If so, you're on the right track.

### 5.2. Add the form [#52-add-the-form]

A few things need to be added to the route in order to create a new note:

* Add an `action` function to the route.
* Display a `Toast` notification when a note is created.
* Import the `createNote` function from `models/note.server.js`.
* Import the `useActionData` and `useAppBridge`

```jsx title="app/routes/app/app.notes.$productGid.jsx"
import { json, redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react"; // [!code --]
import { useLoaderData, useActionData } from "@remix-run/react"; // [!code ++]
import { getNotes } from "../models/note.server"; // [!code --]
import { getNotes, createNote } from "../models/note.server"; // [!code ++]
import { Card, Layout, Text, BlockStack } from "@shopify/polaris";
import { useAppBridge } from "@shopify/app-bridge-react"; // [!code ++]

export const loader = async ({ params }) => {
  const { productGid } = params;
  const notes = await getNotes(productGid);
  return json({ notes, productGid });
};

export const action = async ({ request, params }) => {
  // [!code ++]
  const formData = await request.formData(); // [!code ++]
  const body = formData.get("body")?.toString() || null; // [!code ++]
  const { productGid } = params; // [!code ++]
  // [!code ++]
  await createNote({ productGid, body }); // [!code ++]
  return redirect(`/app/product-notes/${encodeURIComponent(productGid)}`); // [!code ++]
}; // [!code ++]

export default function ProductNotes() {
  const { notes, productGid } = useLoaderData();
  const actionData = useActionData(); // [!code ++]
  const app = useAppBridge(); // [!code ++]

  useEffect(() => {
    // [!code ++]
    if (actionData?.ok) {
      // [!code ++]
      app.toast.show("Note saved", { duration: 3000 }); // [!code ++]
      setBody(""); // [!code ++]
    } // [!code ++]
  }, [actionData, app]); // [!code ++]

  return (
    <>
      <Layout.Section>
        <BlockStack gap="200">
          {notes.length === 0 ? (
            <Text as="p" variant="bodyMd" color="subdued">
              No notes yet.
            </Text>
          ) : (
            notes.map((note) => (
              <Card key={note.id} sectioned>
                <BlockStack gap="100">
                  {note.body && (
                    <Text as="p" variant="bodyMd">
                      {note.body}
                    </Text>
                  )}
                  <Text as="p" variant="bodySm" color="subdued">
                    Added: {new Date(note.createdAt).toLocaleString()}
                  </Text>
                </BlockStack>
              </Card>
            ))
          )}
        </BlockStack>
      </Layout.Section>
    </>
  );
}
```

Now, you can build out the form that will call the `action` function:

```jsx title="app/routes/app/app.notes.$productGid.jsx"
import { json, redirect } from "@remix-run/node";
import { useLoaderData, useActionData } from "@remix-run/react";
import { getNotes, createNote } from "../models/note.server";
import { Card, Layout, Text, BlockStack } from "@shopify/polaris"; // [!code --]
import {
  Card,
  Layout,
  Text,
  BlockStack,
  Form,
  FormLayout,
  TextField,
  Button,
} from "@shopify/polaris"; // [!code ++]
import { useAppBridge } from "@shopify/app-bridge-react";

export const loader = async ({ params }) => {
  const { productGid } = params;
  const notes = await getNotes(productGid);
  return json({ notes, productGid });
};

export const action = async ({ request, params }) => {
  const formData = await request.formData();
  const body = formData.get("body")?.toString() || null;
  const { productGid } = params;

  await createNote({ productGid, body });
  return redirect(`/app/product-notes/${encodeURIComponent(productGid)}`);
};

export default function ProductNotes() {
  const { notes, productGid } = useLoaderData();
  const actionData = useActionData(); // [!code ++]
  const app = useAppBridge(); // [!code ++]

  useEffect(() => {
    // [!code ++]
    if (actionData?.ok) {
      // [!code ++]
      app.toast.show("Note saved", { duration: 3000 }); // [!code ++]
      setBody(""); // [!code ++]
    } // [!code ++]
  }, [actionData, app]); // [!code ++]

  return (
    <>
      <Layout.Section>
        {" "}
        // [!code ++]
        <Card sectioned>
          {" "}
          // [!code ++]
          <Form method="post">
            {" "}
            // [!code ++]
            <FormLayout>
              {" "}
              // [!code ++]
              <BlockStack gap="200">
                {" "}
                // [!code ++]
                <input type="hidden" name="productGid" value={productGid} /> // [!code ++]
                <TextField // [!code ++]
                  label="Note" // [!code ++]
                  value={body} // [!code ++]
                  onChange={setBody} // [!code ++]
                  name="body" // [!code ++]
                  autoComplete="off" // [!code ++]
                  multiline={4} // [!code ++]
                />{" "}
                // [!code ++]
                <Button submit primary>
                  {" "}
                  // [!code ++] Add Note // [!code ++]
                </Button>{" "}
                // [!code ++]
              </BlockStack>{" "}
              // [!code ++]
            </FormLayout>{" "}
            // [!code ++]
          </Form>{" "}
          // [!code ++]
        </Card>{" "}
        // [!code ++]
      </Layout.Section>{" "}
      // [!code ++]
      <Layout.Section>
        <BlockStack gap="200">
          {notes.length === 0 ? (
            <Text as="p" variant="bodyMd" color="subdued">
              No notes yet.
            </Text>
          ) : (
            notes.map((note) => (
              <Card key={note.id} sectioned>
                <BlockStack gap="100">
                  {note.body && (
                    <Text as="p" variant="bodyMd">
                      {note.body}
                    </Text>
                  )}
                  <Text as="p" variant="bodySm" color="subdued">
                    Added: {new Date(note.createdAt).toLocaleString()}
                  </Text>
                </BlockStack>
              </Card>
            ))
          )}
        </BlockStack>
      </Layout.Section>
    </>
  );
}
```

You should now be able to add a note to a product and see it displayed.

### 6. Test your route [#6-test-your-route]

Run `npm run dev` and navigate to the `Product Notes` route.

* Navigate to Product Notes on the sidebar
* Select a product
* Add a note
* Verify that notes are displayed and saved correctly.

## Next Steps [#next-steps]

Now that you have a working Shopify app connected to a Prisma Postgres database, you can:

* Extend your Prisma schema with more models and relationships
* Add create/update/delete routes and forms

### More Info [#more-info]

* [Prisma Documentation](https://www.prisma.io/docs/orm/v7)
* [Shopify Dev Documentation](https://shopify.dev/docs)

## Related pages

- [`AI SDK (with Next.js)`](https://www.prisma.io/docs/guides/integrations/ai-sdk): Build a chat application with AI SDK, Prisma, and Next.js to store chat sessions and messages
- [`Datadog`](https://www.prisma.io/docs/guides/integrations/datadog): Learn how to configure Datadog tracing for a Prisma ORM project. Capture spans for every query using the @prisma/instrumentation package, dd-trace, and view them in Datadog
- [`Embedded Prisma Studio (with Next.js)`](https://www.prisma.io/docs/guides/integrations/embed-studio): Learn how to embed Prisma Studio directly in your Next.js application for database management
- [`GitHub Actions`](https://www.prisma.io/docs/guides/integrations/github-actions): Provision and manage Prisma Postgres databases per pull request using GitHub Actions and Prisma REST API
- [`Neon with Accelerate`](https://www.prisma.io/docs/guides/integrations/neon-accelerate): Learn how to set up PostgreSQL on Neon with Prisma Accelerate's Connection Pool