CommunityEscrita e Ediçãogithub.com

khadinakbarlabs/shopify-app-builder

Plugin de desenvolvimento de aplicativos Shopify de código aberto com 32 habilidades para Claude Code, Codex, Cursor, OpenCode, Gemini CLI e agentes de codificação de IA.

O que é shopify-app-builder?

Este plugin de código aberto fornece um conjunto abrangente de 32 habilidades para construir, autenticar e implantar aplicativos Shopify usando frameworks modernos como Remix e Next.js. Ele suporta múltiplos fluxos de autenticação, incluindo a recomendada Troca de Token (2024+) para aplicativos renderizados no servidor, Instalação Gerenciada para aplicativos headless e o legado OAuth 2.0 Authorization Code Grant. O plugin descreve implementações detalhadas em TypeScript para cada fluxo de autenticação, armazenamento de sessão com Prisma, Redis, Memória e DynamoDB, além de cobrir tópicos como tokens online vs. offline e validação de proxy de aplicativo. Projetado para agentes de codificação de IA como Claude Code e Cursor, os desenvolvedores podem usar prompts de linguagem natural para gerar código Shopify pronto para produção com configuração mínima.

Funciona com✓Claude Code✓Codex CLI✓Cursor✓Gemini CLI✓OpenCode
npx skills add khadinakbarlabs/shopify-app-builder

Installed? Explore more Escrita e Edição skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Autenticação de Aplicativos Shopify

O Shopify fornece múltiplos fluxos de autenticação dependendo do tipo de aplicativo e caso de uso. O padrão moderno é a Troca de Token (2024+) para aplicativos renderizados no servidor, a Instalação Gerenciada para aplicativos headless e o OAuth 2.0 Authorization Code Grant para implementações legadas/personalizadas. Todos os fluxos resultam em um token de acesso para a Shopify GraphQL Admin API.

Visão Geral dos Fluxos de Autenticação

1. Troca de Token (Recomendado para 2024+)

Caso de Uso: Aplicativos renderizados no servidor (Remix, Next.js com SSR), aplicativos Shopify CLI Fluxo: Comerciante instala o aplicativo → Shopify gera um token de troca temporário → O aplicativo troca por um token de acesso Segurança: Nenhum segredo de cliente exposto; usa rotação estilo PKCE por requisição Tempo de Vida do Token: Tokens de acesso são de curta duração; tokens de atualização giram automaticamente

Diagrama de Troca de Token:

1. O comerciante clica em "Instalar" no Admin Shopify
2. Shopify redireciona: https://seu-aplicativo.com/auth/callback?code=EXCHANGE_TOKEN
3. O aplicativo valida o HMAC, troca o código por token de acesso (privado, apenas no servidor)
4. A Shopify Admin API concede escopos; token armazenado na sessão/banco de dados
5. O token é atualizado automaticamente na próxima requisição se expirar

Implementação Remix (Troca de Token):

// shopify.app.ts (Configuração do Aplicativo)
import { shopifyApp } from '@shopify/shopify-app-remix/server';
import { restResources } from '@shopify/shopify-api/rest/admin/2026-07';
import { PrismaSessionStorage } from '@shopify/shopify-app-session-storage-prisma';
import { prisma } from '~/db.server';

const shopify = shopifyApp({
  apiKey: process.env.SHOPIFY_API_KEY || '',
  apiSecret: process.env.SHOPIFY_API_SECRET || '',
  scopes: process.env.SCOPES?.split(',') || [
    'write_products',
    'read_products',
    'write_orders',
    'read_orders',
    'write_customers',
    'read_customers',
    'write_discounts',
    'read_discounts',
    'write_fulfillments',
    'read_fulfillments',
    'write_inventory',
    'read_inventory',
  ],
  appUrl: process.env.SHOPIFY_APP_URL || 'http://localhost:3000',
  auth: {
    path: '/auth',
    callbackPath: '/auth/callback',
  },
  webhooks: {
    path: '/webhooks',
  },
  isEmbeddedApp: true, // Painel de administração Polaris
  sessionStorage: new PrismaSessionStorage(prisma),
  restResources, // Inclui auxiliares da REST API
});

export default shopify;

Variáveis de Ambiente (.env):

SHOPIFY_API_KEY=sua-chave-publica-da-api-do-painel-de-parceiros
SHOPIFY_API_SECRET=seu-segredo-privado-da-api
SHOPIFY_APP_URL=https://seu-dominio.ngrok.io  # ou URL de produção
SCOPES=write_products,read_products,write_orders,read_orders

Rotas de Autenticação (routes/auth.$.tsx - Rota curinga):

import { redirect } from '@shopify/remix-oxygen';
import { authenticate } from '~/shopify.server';

export const loader = async ({ request }) => {
  const { authenticate } = await import('~/shopify.server');
  return authenticate.admin(request); // A Troca de Token acontece aqui
};

Manipulação de Callback (routes/auth.callback.tsx):

import { json } from '@shopify/remix-oxygen';
import { authenticate } from '~/shopify.server';

export const loader = async ({ request }) => {
  const { session } = await authenticate.admin(request);
  // A sessão contém:
  // - session.accessToken (válido para chamadas de API)
  // - session.shop (domínio da loja do comerciante)
  // - session.scope (escopos concedidos)
  // - session.state (dados personalizados opcionais)

  return redirect('/app'); // Redireciona para o painel após autenticação
};

Usando o Token em Chamadas de API (routes/app.products.tsx):

import { json } from '@shopify/remix-oxygen';
import { authenticate } from '~/shopify.server';
import { GraphQLClient } from 'graphql-request';

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

  // Método 1: Usar o auxiliar admin do Shopify (recomendado)
  const response = await admin.graphql(`
    query GetProducts {
      products(first: 10) {
        edges {
          node {
            id
            title
            handle
          }
        }
      }
    }
  `);

  // Método 2: Chamada GraphQL manual
  const client = new GraphQLClient(
    `https://${session.shop}/admin/api/2026-07/graphql.json`,
    {
      headers: {
        'X-Shopify-Access-Token': session.accessToken,
        'Content-Type': 'application/json',
      },
    }
  );

  const data = await client.request(/* ... */);

  return json({ products: response.data?.products?.edges || [] });
};

Atualização de Token (Automática): Os tokens da Troca de Token são atualizados automaticamente pelo middleware de sessão do Shopify. Nenhuma atualização manual é necessária:

// Os tokens são atualizados de forma transparente em cada requisição
const response = await admin.graphql(query); // Lida com a atualização internamente

2. Instalação Gerenciada (Aplicativos Headless / Personalizados)

Caso de Uso: Loja virtual headless, aplicativos móveis, integrações de terceiros Fluxo: Comerciante autoriza o aplicativo → Shopify gera um token de acesso permanente (sem rotação de segredo) Tempo de Vida do Token: Longa duração; nenhuma atualização necessária Segurança: O token é permanente; armazene com segurança em variável de ambiente

Configuração da Instalação Gerenciada:

No Painel de Parceiros Shopify:

  1. Configurações do Aplicativo > Credenciais da API
  2. Selecione "Instalação gerenciada" nos escopos de acesso da API Admin
  3. O comerciante concede permissão uma vez
  4. Copie o token de acesso para seu ambiente
# .env
SHOPIFY_ADMIN_ACCESS_TOKEN=<SHOPIFY_ADMIN_ACCESS_TOKEN>
SHOPIFY_SHOP_URL=exemplo-loja.myshopify.com

Usando o Token de Instalação Gerenciada:

import { GraphQLClient } from 'graphql-request';

const client = new GraphQLClient(
  `https://${process.env.SHOPIFY_SHOP_URL}/admin/api/2026-07/graphql.json`,
  {
    headers: {
      'X-Shopify-Access-Token': process.env.SHOPIFY_ADMIN_ACCESS_TOKEN || '',
    },
  }
);

// O token nunca expira; chame a API a qualquer momento
const query = `query { products(first: 10) { edges { node { id title } } } }`;
const products = await client.request(query);

3. OAuth 2.0 Authorization Code Grant (Legado, Ainda Suportado)

Caso de Uso: Aplicativos públicos com fluxo OAuth tradicional Fluxo: Comerciante clica em "Instalar" → O aplicativo redireciona para o OAuth do Shopify → Comerciante autoriza → O aplicativo recebe o código → O aplicativo troca o código pelo token Tempo de Vida do Token: Token de acesso de longa duração (sem expiração, a menos que revogado) Segurança: Segredo do cliente necessário; PKCE opcional

Diagrama do Fluxo OAuth:

1. O comerciante visita: https://seu-aplicativo.com/auth
2. O aplicativo redireciona para: https://sua-loja.myshopify.com/admin/oauth/authorize?client_id=KEY&scope=write_products&redirect_uri=https://seu-aplicativo.com/auth/callback&state=RANDOM
3. O comerciante autoriza o aplicativo no Admin Shopify
4. Shopify redireciona de volta: https://seu-aplicativo.com/auth/callback?code=AUTHORIZATION_CODE&hmac=SIGNATURE&state=RANDOM&shop=sua-loja.myshopify.com
5. O aplicativo valida o HMAC e o estado
6. O aplicativo troca o código pelo token (no servidor, usando o segredo do cliente)
7. O aplicativo armazena o token no banco de dados

Exemplo OAuth com Express.js:

import express from 'express';
import axios from 'axios';
import crypto from 'crypto';

const app = express();

const API_KEY = process.env.SHOPIFY_API_KEY || '';
const API_SECRET = process.env.SHOPIFY_API_SECRET || '';
const REDIRECT_URI = process.env.REDIRECT_URI || 'https://seu-aplicativo.com/auth/callback';
const SCOPES = 'write_products,read_products';

// Etapa 1: Redirecionar o comerciante para o OAuth do Shopify
app.get('/auth', (req, res) => {
  const shop = req.query.shop as string;

  if (!shop || !shop.includes('.myshopify.com')) {
    return res.status(400).send('Parâmetro shop ausente ou inválido');
  }

  const state = crypto.randomBytes(16).toString('hex');
  const nonce = crypto.randomBytes(16).toString('hex');

  // Armazenar o estado na sessão (ou banco de dados) para validação
  req.session.state = state;
  req.session.nonce = nonce;

  const authUrl = new URL(
    `/admin/oauth/authorize`,
    `https://${shop}`
  );
  authUrl.searchParams.append('client_id', API_KEY);
  authUrl.searchParams.append('scope', SCOPES);
  authUrl.searchParams.append('redirect_uri', REDIRECT_URI);
  authUrl.searchParams.append('state', state);

  res.redirect(authUrl.toString());
});

// Etapa 2: Lidar com o callback OAuth
app.get('/auth/callback', async (req, res) => {
  const { code, hmac, shop, state } = req.query;

  // Validar HMAC
  const message = Object.entries(req.query)
    .filter(([key]) => key !== 'hmac')
    .map(([key, value]) => `${key}=${value}`)
    .sort()
    .join('&');

  const hash = crypto
    .createHmac('sha256', API_SECRET)
    .update(message, 'utf8')
    .digest('base64');

  if (hash !== hmac) {
    return res.status(401).send('Requisição não autorizada detectada');
  }

  // Validar estado
  if (state !== req.session.state) {
    return res.status(401).send('Incompatibilidade de estado');
  }

  try {
    // Trocar o código pelo token de acesso
    const response = await axios.post(
      `https://${shop}/admin/oauth/access_token`,
      {
        client_id: API_KEY,
        client_secret: API_SECRET,
        code,
      }
    );

    const { access_token, scope } = response.data;

    // Armazenar o token de acesso (no banco de dados, não na sessão, para persistência)
    await storeAccessToken(shop as string, access_token, scope);

    // Redirecionar para o painel do aplicativo
    res.redirect(`/app?shop=${shop}`);
  } catch (error) {
    console.error('Erro na troca de token:', error);
    res.status(500).send('Autenticação falhou');
  }
});

// Função auxiliar para armazenar o token
async function storeAccessToken(shop: string, token: string, scope: string) {
  // Armazenar no banco de dados (exemplo usando armazenamento fictício)
  const db = {
    shops: {} as Record<string, { token: string; scope: string }>,
  };
  db.shops[shop] = { token, scope };
}

app.listen(3000);

Adaptadores de Armazenamento de Sessão

Os tokens de acesso devem ser armazenados de forma persistente. O Shopify fornece adaptadores para backends de armazenamento comuns:

Prisma (Recomendado para Remix)

Esquema (prisma/schema.prisma):

model Session {
  id        String    @id
  shop      String
  state     String
  isOnline  Boolean   @default(false)
  accessToken String
  refreshToken String?
  scope     String
  expiresAt DateTime?
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt

  @@unique([shop, state])
  @@index([shop])
}

Configuração (shopify.app.ts):

import { PrismaSessionStorage } from '@shopify/shopify-app-session-storage-prisma';
import { prisma } from '~/db.server';

const shopify = shopifyApp({
  // ...
  sessionStorage: new PrismaSessionStorage(prisma),
});

Redis

import { RedisSessionStorage } from '@shopify/shopify-app-session-storage-redis';
import redis from 'redis';

const redisClient = redis.createClient({
  host: 'localhost',
  port: 6379,
});

const sessionStorage = new RedisSessionStorage({
  client: redisClient,
  prefix: 'shopify_session:',
});

const shopify = shopifyApp({
  // ...
  sessionStorage,
});

Em Memória (Apenas Desenvolvimento)

import { MemorySessionStorage } from '@shopify/shopify-app-session-storage';

const sessionStorage = new MemorySessionStorage();

const shopify = shopifyApp({
  // ...
  sessionStorage, // ATENÇÃO: Sessões perdidas na reinicialização do aplicativo; apenas desenvolvimento
});

DynamoDB

import { DynamoDBSessionStorage } from '@shopify/shopify-app-session-storage-dynamodb';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';

const dynamoDBClient = new DynamoDBClient({ region: 'us-east-1' });

const sessionStorage = new DynamoDBSessionStorage({
  client: dynamoDBClient,
  tableName: 'shopify-sessions',
});

const shopify = shopifyApp({
  // ...
  sessionStorage,
});

Tokens Online vs. Offline

Token Online:

  • Escopo: Permissões do usuário atual (tipicamente usuário administrador)
  • Expiração: 24 horas
  • Caso de Uso: Ações baseadas em navegador (painel de administração Polaris)
  • Limitações: Não pode executar tarefas em segundo plano; limitado quando o usuário faz logout

Token Offline:

  • Escopo: Escopos concedidos ao aplicativo
  • Expiração: Nenhuma; permanente até ser revogado
  • Caso de Uso: Tarefas em segundo plano, webhooks, tarefas agendadas
  • Limitações: Nenhuma; use por padrão para operações do aplicativo

Solicitando Token Offline na Troca de Token:

// shopify.app.ts
const shopify = shopifyApp({
  // ...
  auth: {
    path: '/auth',
    callbackPath: '/auth/callback',
  },
});

// Remix solicita automaticamente o token offline por padrão
// Nenhuma ação necessária; use session.accessToken para chamadas de API

Usando Token Offline para Webhooks:

// webhooks/products-update.ts
export const webhooks = {
  APP_UNINSTALLED: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: '/webhooks/app-uninstalled',
  },
  PRODUCTS_UPDATE: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: '/webhooks/products-update',
  },
};

export default defineWebhooksConfig(
  async (request, { admin, session }) => {
    const { body, query } = await graphql.query(request, {
      query: GET_PRODUCT,
      variables: { id: 'gid://shopify/Product/123' },
    });

    // session.accessToken é um token offline; válido aqui
    console.log(`Webhook processado com token para a loja: ${session.shop}`);
  },
  webhooksConfig
);

Autenticação de Proxy de Aplicativo

Caso de Uso: Requisições de loja virtual (públicas) para o backend do aplicativo Autenticação: Validação de assinatura HMAC (como webhooks) Fluxo: Loja virtual → Requisição de proxy Liquid → Backend do aplicativo (valida HMAC) → Resposta

Configurando Proxy de Aplicativo (shopify.app.toml):

[[extensions]]
type = "app_proxy"
name = "API da Loja Virtual"
url = "/api/proxy"
subpath = "lealdade"  # Requisições para /apps/lealdade/* são roteadas para aqui

Manipulador de Proxy de Aplicativo (Remix routes/api/proxy.ts):

import { json } from '@shopify/remix-oxygen';
import crypto from 'crypto';

export const loader = async ({ request }) => {
  const url = new URL(request.url);
  const hmac = url.searchParams.get('hmac') || '';
  const timestamp = url.searchParams.get('_t') || '';
  const shop = url.searchParams.get('shop') || '';

  // Construir string de mensagem para validação HMAC
  const params = new URLSearchParams(url.searchParams);
  params.delete('hmac');
  params.sort();
  const message = params.toString();

  // Calcular HMAC esperado
  const hash = crypto
    .createHmac('sha256', process.env.SHOPIFY_API_SECRET || '')
    .update(message)
    .digest('hex');

  if (hash !== hmac) {
    return json({ error: 'HMAC inválido' }, { status: 401 });
  }

  // Processar a requisição do proxy (exemplo: buscar dados de fidelidade)
  const products = await shopify.api.rest.Product.all({
    session: { shop, accessToken: 'offline-token-storage' },
  });

  // Retornar dados renderizados em Liquid ou JSON
  return json({ products: products.data });
};

Próximos Passos

  1. Escolha seu fluxo de autenticação (Troca de Token para novos projetos)
  2. Configure o adaptador de armazenamento de sessão (Prisma para Remix)
  3. Implemente rotas de autenticação e callback
  4. Use tokens para chamar a Shopify Admin API
  5. Implante com Shopify CLI ou sua plataforma de hospedagem

💡 Dica: Para novos projetos, sempre use a Troca de Token com tokens offline. Evite OAuth legado, a menos que tenha requisitos específicos de compatibilidade.

Habilidades Relacionadas