Разработка кастомного Shopify App
Shopify App — это веб-приложение, интегрированное в экосистему Shopify через OAuth 2.0 и API. Кастомное приложение нужно когда функционал из App Store отсутствует, не масштабируется под задачу или требует глубокой интеграции с внутренними системами компании.
Типы Shopify Apps
Public App — публикуется в App Store, устанавливается любым магазином. Требует прохождения review от Shopify. Монетизация через подписку.
Custom App — создаётся для конкретного магазина в рамках его Admin. Не публикуется, не требует review. Устанавливается через прямую ссылку или API токен. Оптимальный вариант для корпоративной автоматизации.
Embedded App — отображается внутри Shopify Admin через iframe. Использует Shopify App Bridge для коммуникации с хост-интерфейсом.
Технологический стек
Официально рекомендованный Shopify стек:
- Backend: Node.js (Express) или Ruby on Rails, реже PHP
- Frontend: React + Shopify Polaris (дизайн-система для Admin UI)
-
App Bridge:
@shopify/app-bridge-react— коммуникация с Shopify Admin -
API client:
@shopify/shopify-api(Node) /shopify_api(Ruby gem) - Database: PostgreSQL / MySQL для хранения данных приложения
- Hosting: Heroku, Railway, Fly.io, собственный VPS за nginx
Scaffold через Shopify CLI:
shopify app init my-custom-app
# Выбор: Node.js + React
cd my-custom-app
shopify app dev
OAuth flow и аутентификация
// 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());
После OAuth Shopify возвращает офлайн-токен (долгоживущий) и онлайн-токен (сессионный). Офлайн хранится в БД приложения и используется для фоновых задач.
Admin API: работа с данными магазина
// Получение заказов через 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
}
}
}`
});
Пагинация через cursor-based (не offset). При >250 объектов — итерация с endCursor.
Webhooks
Webhooks — асинхронное получение событий от Shopify без polling:
// Регистрация 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);
// Синхронизация с 1C, отправка в CRM, создание задачи в складской системе
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 требует ответа 200 OK за 5 секунд — тяжёлую обработку выносим в очередь (Bull, BullMQ, Sidekiq).
Polaris UI в 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}`);
}}>Открыть</Button>
]);
return (
<Page title="Управление заказами" primaryAction={{ content: 'Экспорт', onAction: handleExport }}>
<Layout>
<Layout.Section>
<Card>
<DataTable
columnContentTypes={['text', 'text', 'numeric', 'text']}
headings={['Заказ', 'Статус оплаты', 'Сумма', 'Действие']}
rows={rows}
/>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}
App Extensions
Кастомное приложение может добавлять расширения в различные части Shopify:
- Theme App Extension — блоки в тему (виджеты, кнопки, баннеры)
- Checkout UI Extension — кастомный UI в чекауте (только Plus или через Function)
- Admin UI Extension — дополнительные блоки в страницы Admin
- Shopify Functions — серверная бизнес-логика (скидки, доставка, валидация)
# Добавление Theme App Extension к приложению
shopify app generate extension --template theme_app_extension --name my-widget
Фоновые задачи и очереди
// Обработчик очереди BullMQ
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);
});
Деплой и инфраструктура
Минимальная конфигурация для продакшена:
- App server: 1 инстанс (Node.js / Puma), auto-restart через PM2 или systemd
- Worker: отдельный процесс для очереди
- PostgreSQL: для хранения сессий и данных приложения
- Redis: для очередей и кэша
- Nginx: reverse proxy + SSL termination
- Webhook endpoint: должен быть доступен публично (Shopify делает POST)
Сроки
Простое кастомное приложение (CRUD над API, базовый Polaris UI): 1–2 недели. Приложение с Theme Extension, Webhooks и интеграцией с внешней системой: 3–5 недель. Полноценное приложение с Shopify Functions, checkout UI, фоновой синхронизацией и мультимагазинной архитектурой: 2–3 месяца.







