# Shopify (/docs/guides/integrations/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. Don't worry—just follow these recommended options to get started quickly and ensure your app is set up for success:

> [!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, but let's take a moment to update it to the latest version. This ensures you have access to the newest features, improvements, and the best possible experience as you build your app.

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:
> 
> 
>   
> 
>   #### npm

>     ```bash
>     npm install dotenv
>     ```
>
> 
>   #### pnpm

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

>     ```bash
>     yarn add dotenv
>     ```
>
> 
>   #### bun

>     ```bash
>     bun add dotenv
>     ```
>
> 

To enable your app to store notes for each product, let's 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:

  

#### npm

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

#### pnpm

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

#### yarn

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

#### bun

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

  

#### npm

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

#### pnpm

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

#### yarn

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

#### bun

```bash
bun add @prisma/client @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](/orm/core-concepts/supported-databases/database-drivers).

Initialize Prisma in your project:

  

#### npm

```bash
npx prisma init
```

#### pnpm

```bash
pnpm dlx prisma init
```

#### yarn

```bash
yarn dlx prisma init
```

#### bun

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

Then create a Prisma Postgres database:

  

#### npm

```bash
npx create-db
```

#### pnpm

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

#### yarn

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

#### bun

```bash
bunx --bun 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:

  

#### npm

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

#### pnpm

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

#### yarn

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

#### bun

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

Then generate Prisma Client:

  

#### npm

```bash
npx prisma generate
```

#### pnpm

```bash
pnpm dlx prisma generate
```

#### yarn

```bash
yarn dlx prisma generate
```

#### bun

```bash
bunx --bun prisma generate
```

Now, before moving on, let's 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]

To keep your project organized, let's create a new `models/` folder. Inside this folder, add a file named `notes.server.js`. This will be the home for all your note-related logic and make your codebase easier to manage as your app grows.

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, let's 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, keeping your app organized and user-friendly.

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](/orm)
* [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 Management 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