Custom Shopify App Development

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1 servicesAll 2065 services
Custom Shopify App Development
Complex
~2-4 weeks
FAQ
Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Developing a Custom Shopify App

A Shopify App is a web application integrated into the Shopify ecosystem via OAuth 2.0 and API. Custom apps are needed when App Store functionality is missing, doesn't scale for your needs, or requires deep integration with internal company systems.

Types of Shopify Apps

Public App — published to the App Store, installable by any shop. Requires Shopify review. Monetized via subscription.

Custom App — created for a specific shop within its Admin. Not published, no review required. Installed via direct link or API token. Optimal for corporate automation.

Embedded App — displayed within Shopify Admin via iframe. Uses Shopify App Bridge for host interface communication.

Technology Stack

The officially recommended Shopify stack:

  • Backend: Node.js (Express) or Ruby on Rails, rarely PHP
  • Frontend: React + Shopify Polaris (design system for Admin UI)
  • App Bridge: @shopify/app-bridge-react — communication with Shopify Admin
  • API client: @shopify/shopify-api (Node) / shopify_api (Ruby gem)
  • Database: PostgreSQL / MySQL for application data
  • Hosting: Heroku, Railway, Fly.io, own VPS with nginx

Scaffold via Shopify CLI:

shopify app init my-custom-app
# Select: Node.js + React
cd my-custom-app
shopify app dev

OAuth Flow and Authentication

// web/index.js — Express + @shopify/shopify-api
import { shopifyApp } from '@shopify/shopify-app-express';
import { PostgreSQLSessionStorage } from '@shopify/shopify-app-session-storage-postgresql';

const shopify = shopifyApp({
  api: {
    apiKey: process.env.SHOPIFY_API_KEY,
    apiSecretKey: process.env.SHOPIFY_API_SECRET,
    scopes: ['read_products', 'write_products', 'read_orders', 'write_orders'],
    hostName: process.env.HOST.replace(/https?:\/\//, ''),
    apiVersion: ApiVersion.January25,
  },
  auth: {
    path: '/api/auth',
    callbackPath: '/api/auth/callback',
  },
  webhooks: {
    path: '/api/webhooks',
  },
  sessionStorage: new PostgreSQLSessionStorage(process.env.DATABASE_URL),
});

app.get(shopify.config.auth.path, shopify.auth.begin());
app.get(shopify.config.auth.callbackPath, shopify.auth.callback(), shopify.redirectToShopifyOrAppRoot());

After OAuth, Shopify returns an offline token (long-lived) and online token (session). The offline token is stored in the app's database and used for background tasks.

Admin API: Working with Store Data

// Fetch orders via GraphQL Admin API
const client = new shopify.api.clients.Graphql({ session });

const response = await client.query({
  data: `{
    orders(first: 50, query: "financial_status:paid created_at:>2025-01-01") {
      edges {
        node {
          id
          name
          totalPriceSet {
            shopMoney { amount currencyCode }
          }
          lineItems(first: 20) {
            edges {
              node {
                title
                quantity
                variant {
                  sku
                  inventoryQuantity
                }
              }
            }
          }
          shippingAddress {
            city
            countryCode
          }
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }`
});

Pagination uses cursor-based (not offset). For >250 objects, iterate with endCursor.

Webhooks

Webhooks asynchronously receive events from Shopify without polling:

// Register webhook
shopify.webhooks.addHandlers({
  ORDERS_PAID: [{
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: '/api/webhooks/orders-paid',
    callback: async (topic, shop, body, webhookId) => {
      const order = JSON.parse(body);
      // Sync with ERP, send to CRM, create warehouse task
      await syncOrderToERP(shop, order);
    }
  }],
  PRODUCTS_UPDATE: [{
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: '/api/webhooks/products-update',
    callback: async (topic, shop, body) => {
      const product = JSON.parse(body);
      await invalidateProductCache(shop, product.id);
    }
  }]
});

Shopify requires a 200 OK response within 5 seconds — move heavy processing to a queue (Bull, BullMQ, Sidekiq).

Polaris UI in Embedded App

// web/frontend/pages/Dashboard.jsx
import {
  Page, Layout, Card, DataTable, Badge, Button, Toast
} from '@shopify/polaris';
import { useAppBridge } from '@shopify/app-bridge-react';
import { Redirect } from '@shopify/app-bridge/actions';

export default function Dashboard() {
  const app = useAppBridge();

  const rows = orders.map(order => [
    order.name,
    <Badge status={order.financial_status === 'paid' ? 'success' : 'warning'}>
      {order.financial_status}
    </Badge>,
    order.total_price,
    <Button onClick={() => {
      const redirect = Redirect.create(app);
      redirect.dispatch(Redirect.Action.ADMIN_PATH, `/orders/${order.id}`);
    }}>Open</Button>
  ]);

  return (
    <Page title="Order Management" primaryAction={{ content: 'Export', onAction: handleExport }}>
      <Layout>
        <Layout.Section>
          <Card>
            <DataTable
              columnContentTypes={['text', 'text', 'numeric', 'text']}
              headings={['Order', 'Payment Status', 'Amount', 'Action']}
              rows={rows}
            />
          </Card>
        </Layout.Section>
      </Layout>
    </Page>
  );
}

App Extensions

A custom app can add extensions to various parts of Shopify:

  • Theme App Extension — blocks in the theme (widgets, buttons, banners)
  • Checkout UI Extension — custom UI in checkout (Plus only or via Function)
  • Admin UI Extension — additional blocks on Admin pages
  • Shopify Functions — server-side business logic (discounts, shipping, validation)
# Add Theme App Extension to your app
shopify app generate extension --template theme_app_extension --name my-widget

Background Tasks and Queues

// BullMQ queue handler
import { Queue, Worker } from 'bullmq';
import Redis from 'ioredis';

const connection = new Redis(process.env.REDIS_URL);
export const syncQueue = new Queue('erp-sync', { connection });

const worker = new Worker('erp-sync', async (job) => {
  const { shopDomain, orderId } = job.data;
  const session = await loadSessionFromDB(shopDomain);
  const client = new shopify.api.clients.Rest({ session });

  const order = await client.get({ path: `orders/${orderId}` });
  await postToERP(order.body.order);
}, { connection, concurrency: 3 });

worker.on('failed', (job, err) => {
  console.error(`Job ${job.id} failed:`, err.message);
});

Deployment and Infrastructure

Minimal production configuration:

  • App server: 1 instance (Node.js / Puma), auto-restart via PM2 or systemd
  • Worker: separate process for queue
  • PostgreSQL: for storing sessions and app data
  • Redis: for queues and cache
  • Nginx: reverse proxy + SSL termination
  • Webhook endpoint: must be publicly accessible (Shopify makes POST)

Timeline

Simple custom app (CRUD over API, basic Polaris UI): 1–2 weeks. App with Theme Extension, Webhooks, and external system integration: 3–5 weeks. Full-featured app with Shopify Functions, checkout UI, background sync, and multi-shop architecture: 2–3 months.