Creating a Custom Shopify App: A Step-by-Step Guide

Table of Contents
Big thanks to our contributors those make our blogs possible.

Our growing community of contributors bring their unique insights from around the world to power our blog. 

Introduction

Building a custom Shopify app allows you to extend the functionality of Shopify stores with features tailored to specific business needs. Whether you want to automate workflows, integrate third-party services, or create entirely new storefront experiences, a custom app gives you full control. In this guide, we’ll walk through the process of setting up your development environment, creating a simple Shopify app, implementing core features, and deploying it. By the end, you’ll understand how to authenticate with Shopify, interact with its APIs, and deliver a robust solution that seamlessly integrates with any Shopify store.

1. Understand Shopify App Types and Architecture

1.1 Public vs. Custom Apps

  • Public Apps
    • Distributed through the Shopify App Store.
    • Must undergo Shopify’s review and adhere to App Store guidelines.
    • Reach multiple merchants; generate revenue via subscriptions or one-time charges.
  • Custom Apps
    • Installed only on specific stores (yours or your clients’).
    • Bypass App Store approval but require store owner permission via Admin.
    • Ideal for bespoke functionality, private integrations, or white-label solutions.

1.2 Core Components of a Shopify App

  1. Frontend (Admin UI or Embedded App)
    • Built with React and Shopify Polaris for consistent look and feel.
    • Loaded within the Shopify admin via an iframe using Shopify App Bridge.
  2. Backend (Server)
    • Handles OAuth authentication, webhooks, and API requests.
    • Can be implemented in Node.js, Ruby on Rails, PHP, or any server-side language that supports REST or GraphQL.
  3. Shopify APIs
    • Admin REST/GraphQL API: Create, read, update, and delete store data (products, orders, customers).
    • Storefront API: Fetch product and collection data for custom storefronts.
    • Webhooks: Receive real-time notifications for events like orders created or products updated.
  4. Hosting and Deployment
    • Can run on Heroku, AWS, Google Cloud, or any platform that supports your chosen stack.
    • Must be HTTPS-enabled (SSL) for secure communication with Shopify.

2. Prerequisites and Setup

2.1 Required Tools and Accounts

  • Shopify Partner Account
  • Node.js and npm
  • Shopify CLI
    • Installs via npm install -g @shopify/cli @shopify/theme.
    • Simplifies scaffolding, local development, and deployment.
  • ngrok (optional for local testing)
    • Download from ngrok.com.
    • Exposes your local server to the internet so Shopify can communicate with your app.
  • Git
    • Version control for tracking changes and collaborating.

2.2 Create a Development Store

  1. Log into your Shopify Partner Dashboard.
  2. Under “Stores,” click Add storeDevelopment store.
  3. Fill in store details (name, address, settings).
  4. Use this store to install and test your custom app without affecting a live shop.

3. Scaffold a New Shopify App

3.1 Initialize with Shopify CLI

bashCopyEdit# Create a new directory and navigate into it
mkdir shopify-custom-app
cd shopify-custom-app

# Use Shopify CLI to scaffold a new Node.js app
shopify app create node

# When prompted:
# - App Name: custom-app
# - Use ngrok for your local tunnel? Yes
# - Package manager: npm (or yarn)

This generates:

  • server/: Node.js server handling OAuth, API calls, and webhook registration.
  • web/: React frontend using Next.js and Shopify Polaris.
  • shopify.app.toml: Configuration file for Shopify CLI.
  • Preconfigured scripts in package.json for development (npm run dev) and build.

3.2 Configure Environment Variables

In the root directory, find .env.example and create a .env:

envCopyEditSHOPIFY_API_KEY=your_api_key
SHOPIFY_API_SECRET=your_api_secret
SCOPES=read_products,write_orders
HOST=https://your-ngrok-url.ngrok.io
  • SHOPIFY_API_KEY and SHOPIFY_API_SECRET: From your app’s dashboard in the Partner portal.
  • SCOPES: Comma-separated list of permissions (e.g., read_products,write_orders).
  • HOST: Your publicly accessible URL (ngrok or production domain).

4. Implement OAuth Authentication

4.1 OAuth Flow Overview

  1. App Embedded in Shopify Admin: Merchant clicks “Install” on your app listing.
  2. Redirect to Your App: Shopify sends the merchant to your /auth endpoint.
  3. Request Offline Access Token: Your server verifies the HMAC signature and sends a token request to Shopify.
  4. Store the Access Token: Save in your database (or in memory for development).
  5. Set a Session Cookie: So subsequent requests are authenticated.

4.2 Code Snippet: OAuth Handler (server/index.js)

jsCopyEditimport { Shopify, ApiVersion } from "@shopify/shopify-api";
import express from "express";
import dotenv from "dotenv";

dotenv.config();

const app = express();

Shopify.Context.initialize({
  API_KEY: process.env.SHOPIFY_API_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
  SCOPES: process.env.SCOPES.split(","),
  HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
  API_VERSION: ApiVersion.July22,
  IS_EMBEDDED_APP: true,
  SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});

// OAuth initiation
app.get("/auth", async (req, res) => {
  const shop = req.query.shop;
  if (!shop) return res.status(400).send("Missing shop parameter.");
  try {
    const authRoute = await Shopify.Auth.beginAuth(
      req,
      res,
      shop,
      "/auth/callback",
      false
    );
    return res.redirect(authRoute);
  } catch (error) {
    console.error(error);
    return res.status(500).send("Auth initiation failed");
  }
});

// OAuth callback
app.get("/auth/callback", async (req, res) => {
  try {
    const session = await Shopify.Auth.validateAuthCallback(
      req,
      res,
      req.query
    );
    // Save session.accessToken and session.shop in your store
    return res.redirect(`/`);
  } catch (error) {
    console.error(error);
    return res.status(500).send("Auth callback failed");
  }
});

app.listen(3000, () => console.log("Server listening on port 3000"));
  • beginAuth: Generates a permission URL for the merchant.
  • validateAuthCallback: Validates the returned parameters and exchanges the code for an access token.

5. Build the Frontend with Polaris

5.1 Scaffold a Simple Page

Inside web/pages/index.js:

jsxCopyEditimport { Page, Card, Layout, TextContainer, Heading } from "@shopify/polaris";
import { TitleBar } from "@shopify/app-bridge-react";

export default function HomePage() {
  return (
    <Page>
      <TitleBar title="My Custom App" />
      <Layout>
        <Layout.Section>
          <Card sectioned>
            <TextContainer>
              <Heading>Welcome to Your Custom Shopify App</Heading>
              <p>
                This app demonstrates how to display store data fetched from
                Shopify’s Admin API. Use the navigation to explore features.
              </p>
            </TextContainer>
          </Card>
        </Layout.Section>
      </Layout>
    </Page>
  );
}
  • Polaris Components (Page, Card, Layout) ensure consistent Shopify styling.
  • App Bridge’s <TitleBar> integrates seamlessly with Shopify Admin’s UI.

5.2 Fetch Data from the Server

Modify web/pages/index.js to fetch, for example, a list of products:

jsxCopyEditimport { useEffect, useState } from "react";
import { Page, Card, ResourceList, ResourceItem, TextStyle } from "@shopify/polaris";
import axios from "axios";

export default function HomePage() {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    async function fetchProducts() {
      try {
        const response = await axios.get("/api/products");
        setProducts(response.data.products);
      } catch (err) {
        console.error(err);
      }
    }
    fetchProducts();
  }, []);

  return (
    <Page title="Product List">
      <Card>
        <ResourceList
          items={products}
          renderItem={(product) => {
            const { id, title } = product;
            return (
              <ResourceItem id={id}>
                <TextStyle variation="strong">{title}</TextStyle>
              </ResourceItem>
            );
          }}
        />
      </Card>
    </Page>
  );
}
  • /api/products: An endpoint on your server that queries Shopify’s REST or GraphQL Admin API.
  • ResourceList: A Polaris component for listing items in the admin UI.

6. Create API Endpoints for Shopify Data

6.1 Example: Fetching Products (server/routes/products.js)

jsCopyEditimport express from "express";
import { Shopify } from "@shopify/shopify-api";

const router = express.Router();

router.get("/api/products", async (req, res) => {
  try {
    const session = await Shopify.Utils.loadCurrentSession(req, res);
    const client = new Shopify.Clients.Rest(session.shop, session.accessToken);
    const productsData = await client.get({ path: "products", query: { limit: 10 } });
    return res.json({ products: productsData.body.products });
  } catch (error) {
    console.error(error);
    return res.status(500).send("Failed to fetch products");
  }
});

export default router;
  • loadCurrentSession: Retrieves the stored access token and shop domain.
  • Rest Client: Allows REST API calls to Shopify endpoints.

6.2 Register the Route

In server/index.js, import and use the route:

jsCopyEditimport productsRouter from "./routes/products.js";

app.use(productsRouter);

Now, GET /api/products returns up to 10 products from the store.

7. Integrate Webhooks for Real-Time Updates

7.1 Choose Events to Subscribe

  • Popular Webhook Topics:
    • ORDERS_CREATE (new orders placed)
    • PRODUCTS_UPDATE (product changes)
    • APP_UNINSTALLED (app removed from a store)

7.2 Register a Webhook (server/webhooks.js)

jsCopyEditimport { Shopify, ApiVersion, Webhooks } from "@shopify/shopify-api";
import express from "express";

const router = express.Router();

router.post("/webhooks/products_update", async (req, res) => {
  try {
    const { body } = req; // Product details sent by Shopify
    console.log("Product updated", body);
    res.status(200).send("Webhook processed");
  } catch (error) {
    console.error("Webhook error:", error);
    res.status(500).send("Failed to process webhook");
  }
});

// During app setup (after OAuth), register the webhook
async function registerWebhooks(session) {
  const registration = await Webhooks.Registry.register({
    shop: session.shop,
    accessToken: session.accessToken,
    path: "/webhooks/products_update",
    topic: "PRODUCTS_UPDATE",
    webhookHandler: async (topic, shop, body) => {
      console.log(`Received ${topic} from ${shop}: ${body}`);
    },
  });
  if (!registration.success) {
    console.error(`Failed to register ${registration.result}`);
  }
}

export { registerWebhooks };
export default router;
  • Webhooks.Registry.register: Registers the webhook with Shopify so you receive events.
  • webhookHandler: Handles incoming webhook POSTs.

8. Test Locally with ngrok

8.1 Start ngrok

bashCopyEditngrok http 3000
  • Copy the HTTPS URL (e.g., https://abcd1234.ngrok.io).
  • Update HOST in your .env to this ngrok URL.

8.2 Install the App on Your Development Store

  1. In Partner Dashboard, go to Apps → Your App → App Setup.
  2. Under App URLs, set the App URL and Allowed redirection URLs to your ngrok address (e.g., https://abcd1234.ngrok.io/auth/callback).
  3. Click Install on your dev store. This triggers the OAuth flow to your local server.

8.3 Simulate Webhook Events

With ngrok running, use the Shopify Admin to update a product in your dev store.

  • The PRODUCTS_UPDATE webhook fires to https://abcd1234.ngrok.io/webhooks/products_update.
  • Check your server logs to verify it was received.

9. Deploying Your App to Production

9.1 Choose a Hosting Provider

  • Heroku (easy deployment for Node.js apps)
  • AWS Elastic Beanstalk (scales automatically; requires AWS account)
  • Google App Engine (if you prefer GCP)
  • DigitalOcean App Platform (straightforward, budget-friendly)

9.2 Prepare for Deployment

  1. Set Environment Variables on your host (API key, secret, scopes, production domain).
  2. Update HOST to your production domain (e.g., https://mycustomapp.com).
  3. Build Frontend: bashCopyEditcd web npm run build cd ..
  4. Ensure HTTPS: Shopify only allows encrypted endpoints—set up SSL/TLS certificates via Let’s Encrypt or your hosting provider’s managed certificates.

9.3 Deploy to Heroku (Example)

bashCopyEditheroku create my-shopify-app
heroku config:set SHOPIFY_API_KEY=your_prod_api_key SHOPIFY_API_SECRET=your_prod_api_secret SCOPES=read_products,write_orders HOST=https://my-shopify-app.herokuapp.com
git push heroku main
heroku ps:scale web=1
  • Monitor logs with heroku logs --tail to catch errors.
  • In the Partner Dashboard, update App URLs to https://my-shopify-app.herokuapp.com.

10. Release as a Custom App

  1. In Shopify Admin (Target Store)
    • Navigate to Apps → Develop apps.
    • Click Create an app, name it (e.g., “Custom App”), and select Configure Admin API scopes matching your app’s needs.
    • Under App setup, enter your app’s base URL (production domain) and redirection URLs.
  2. Install the App
    • Once configured, click Install app.
    • Shopify performs OAuth (even for custom apps) to grant API access.
    • After installation, the app appears under Installed apps and can be accessed from the store’s Admin.

Conclusion

Creating a custom Shopify app empowers you to tailor store functionality to unique business requirements—whether that’s automating inventory updates, integrating niche shipping services, or delivering personalized storefront experiences. By following these steps—setting up your development environment, scaffolding with Shopify CLI, implementing OAuth, building a Polaris-based frontend, handling API calls and webhooks, testing locally with ngrok, and deploying securely—you’ll have a fully functional app ready for any Shopify store. From here, you can iterate, add advanced features (billing APIs, deeper GraphQL queries, custom storefronts), and, if desired, expand into a public app. The only limit is your creativity.

Let's connect on TikTok

Join our newsletter to stay updated

Sydney Based Software Solutions Professional who is crafting exceptional systems and applications to solve a diverse range of problems for the past 10 years.

Share the Post

Related Posts