Apps de Shopify en PHP y Python: los nuevos paquetes oficiales paso a paso
Shopify publicó paquetes oficiales para crear apps en PHP y Python, y dejó obsoletas las librerías anteriores. Te explico qué incluyen, cómo configurarlos con Shopify CLI y cómo verificar un webhook y consultar la Admin API sin reinventar la seguridad.
- Javi Mata
- 9 min de lectura
Tabla de contenido
- Qué son
- ¿Y las librerías anteriores?
- Qué funciones trae
- No hay plantilla: armas tu proyecto
- El “camino verde”: el mismo flujo en cada petición
- 1. Inicializar
- 2. Convertir la petición y la respuesta
- 3. Verificar un webhook
- 4. Consultar la Admin API
- Client credentials: integraciones de servidor a servidor
- Lo que el README te pide no hacer
- En Python es lo mismo
- ¿Cuándo usarlos?
- Conclusión
- Fuentes
Si alguna vez hiciste una app de Shopify en PHP, seguramente usaste shopify-api-php o armaste a mano la verificación de webhooks con HMAC. Eso cambió: el 26 de agosto de 2026 Shopify publicó paquetes oficiales y de código abierto para crear apps en PHP y Python, y después los incluyó entre las novedades para desarrolladores de la Fall ‘26 Edition. Ya están en la versión 1.0.
En este artículo te explico qué incluyen, qué pasa con las librerías anteriores y cómo dar los primeros pasos. Todo está basado en el changelog oficial y en los README de los paquetes, consultados el 30 de octubre de 2026 (versión 1.0.2 en Packagist y en PyPI).
Qué son
Son dos paquetes con la misma API, uno por lenguaje:
composer require shopify/shopify-app-php
pip install shopifyapp
No son un framework: ofrecen primitivas independientes que combinas como quieras, y funcionan con Laravel, Symfony, CodeIgniter o PHP sin framework, y con Django, Flask o FastAPI en Python. Según el changelog, cubren tres cosas:
- Verificación de peticiones: webhooks, App Home y App Bridge, app proxy y extensiones de Checkout, POS, Admin, Customer Account y Flow.
- Intercambio de tokens: token exchange, client credentials y renovación de tokens obtenidos por intercambio.
- Un cliente para la Admin GraphQL API con reintentos automáticos.
Requisitos según cada README: PHP 8.2 o superior con Composer, o Python 3.8 o superior con pip. Y para desarrollar, una cuenta de Partner, una tienda de desarrollo y Shopify CLI.
Un detalle interesante: los dos paquetes implementan el mismo contrato y pasan la misma batería de pruebas, así que una mejora en uno beneficia al otro.
¿Y las librerías anteriores?
shopify-api-php y shopify_python_api quedan obsoletas: siguen funcionando, pero ya no recibirán funciones nuevas ni correcciones de seguridad. En Packagist, shopify-api-php aparece como abandonada, y en PyPI, shopify_python_api como inactiva.
No hay fecha de retiro ni migración forzada. Los paquetes nuevos están pensados para adoptarse poco a poco, una ruta a la vez, siguiendo las notas de migración de cada README. Como la librería anterior ya no recibe parches de seguridad, mi recomendación es no dejar esa migración indefinidamente.
Qué funciones trae
Los nombres son equivalentes en los dos lenguajes (camelCase en PHP, snake_case en Python):
| Para qué | PHP | Python |
|---|---|---|
| Webhooks | verifyWebhookReq | verify_webhook_req |
| App Home (app embebida con App Bridge) | verifyAppHomeReq | verify_app_home_req |
| App proxy | verifyAppProxyReq | verify_app_proxy_req |
| Extensiones de Admin, POS, Checkout y Customer Account | verifyAdminUIExtReq, verifyPosUIExtReq, verifyCheckoutUIExtReq, verifyCustomerAccountUIExtReq | verify_admin_ui_ext_req, verify_pos_ui_ext_req, verify_checkout_ui_ext_req, verify_customer_account_ui_ext_req |
| Acciones de Flow | verifyFlowActionReq | verify_flow_action_req |
| Token exchange | exchangeUsingTokenExchange | exchange_using_token_exchange |
| Client credentials | exchangeUsingClientCredentials | exchange_using_client_credentials |
| Renovar un token | refreshTokenExchangedAccessToken | refresh_token_exchanged_access_token |
| Admin GraphQL | adminGraphQLRequest | admin_graphql_request |
Además hay helpers para App Home: appHomePatchIdToken (la página que renueva un ID token caducado), appHomeParentRedirect (redirigir fuera del iframe del administrador) y appHomeRedirect (redirigir dentro de tu app).
No hay plantilla: armas tu proyecto
No hay plantilla de app para PHP ni para Python. El changelog propone estos pasos:
- Instalar el paquete de tu lenguaje.
- Crear el proyecto con tu framework o stack preferido.
- Combinar las primitivas que necesites, por ejemplo verificar una petición y luego intercambiar el token.
- Confirmar en una tienda de desarrollo que las peticiones se verifican y que el intercambio devuelve un token de acceso.
El README explica cómo conectar tu proyecto con Shopify CLI. En la carpeta del proyecto creas un shopify.app.toml (que se sobrescribe al ejecutar shopify app init --reset). Este es el del README:
client_id = ""
name = ""
application_url = ""
embedded = true
[access_scopes]
scopes = "write_products"
[webhooks]
api_version = "2025-01"
Necesitas también un package.json mínimo:
{
"name": "my-php-app"
}
Y un shopify.web.toml para que la CLI sepa cómo levantar tu app en desarrollo:
name = "My PHP App"
roles = ["frontend", "backend"]
webhooks_path = "/webhooks/app/uninstalled"
[commands]
dev = "[COMMAND]"
Sustituye [COMMAND] por el comando de tu framework; para Laravel, el README indica php artisan serve. Después ejecutas:
shopify app dev --reset
El flag --reset solo se usa la primera vez.
El “camino verde”: el mismo flujo en cada petición
El README resume lo que hace casi cualquier app en cada petición de Shopify:
- Convertir la petición de tu framework al formato del paquete.
- Verificarla con la función
verify...que corresponda. - Si
okesfalse, devolver laresponseque te da el paquete. - Usar
$result->shoppara buscar o guardar el token de acceso de esa tienda. - Intercambiar o renovar el token cuando haga falta.
- Hacer las llamadas a Admin GraphQL.
Y una regla que repite en todo el documento: si el paquete ya te da un valor o una respuesta, úsalo tal cual. No lo parsees, no lo armes tú ni lo vuelvas a verificar.
1. Inicializar
Shopify CLI te da las variables SHOPIFY_API_KEY y SHOPIFY_API_SECRET:
<?php
use Shopify\App\ShopifyApp;
$shopify = new ShopifyApp(
clientId: getenv('SHOPIFY_API_KEY'),
clientSecret: getenv('SHOPIFY_API_SECRET'),
);
Si vas a rotar el client secret, existe el parámetro opcional oldClientSecret: mientras dure la rotación se aceptan peticiones firmadas con cualquiera de los dos, sin cortes.
2. Convertir la petición y la respuesta
Para ser independiente del framework, el paquete trabaja con su propio formato de petición. Este es el ejemplo para Laravel del README:
// Laravel passes the request to controllers and middleware
function requestToShopifyReq($request)
{
return [
'method' => $request->method(),
'headers' => $request->headers->all(),
'url' => $request->fullUrl(),
'body' => $request->getContent(),
];
}
Lo importante: pasa la petición sin modificar, con todos los encabezados, la URL completa con su query string y el cuerpo original. La verificación depende de los bytes exactos que mandó Shopify.
Y el camino inverso, del resultado del paquete a una respuesta de Laravel, registrando de paso el log que trae cada resultado:
use Illuminate\Support\Facades\Log;
use Illuminate\Http\Response;
function shopifyResultToResponse($result)
{
$log = $result->log;
Log::info("{$log->code} - {$log->detail}");
$resp = $result->response;
return response($resp->body, $resp->status)
->withHeaders((array) $resp->headers);
}
3. Verificar un webhook
Con esas dos funciones, verificar un webhook queda así (ejemplo del README):
function webhookHandler($request)
{
$shopify = getShopifyApp();
$req = requestToShopifyReq($request);
$result = $shopify->verifyWebhookReq($req);
if (!$result->ok) {
return shopifyResultToResponse($result);
}
// Your database logic here
$accessToken = getAccessToken($result->shop, 'offline');
}
getShopifyApp(), getAccessToken() y el resto de la lógica de base de datos los escribes tú: el paquete no guarda tokens. Lo que sí define es la forma del token (shop, accessMode, token, scope, refreshToken, expires, refreshTokenExpires y user).
4. Consultar la Admin API
En un proceso en segundo plano, como procesar un webhook o una tarea programada, pasas null en invalidTokenResponse, porque no hay una petición en curso que Shopify pueda reintentar:
function processJob($shop)
{
$shopify = getShopifyApp();
// Your database logic here
$accessToken = getAccessToken($shop, 'offline');
$graphqlResult = $shopify->adminGraphQLRequest(
'
{
shop {
id
}
}
',
shop: $shop,
accessToken: $accessToken->token,
apiVersion: '2025-01',
invalidTokenResponse: null,
);
if (!$graphqlResult->ok) {
return;
}
$shopId = $graphqlResult->data['shop']['id'];
}
adminGraphQLRequest acepta además variables, headers y maxRetries (2 por defecto). El resultado trae ok, data, extensions (por ejemplo, el costo de la consulta), log y httpLogs. Si algo falla, decide según $graphqlResult->log->code, no según el texto del error: es lo que pide el README.
Los ejemplos del README usan apiVersion: '2025-01'; en tu app, usa la versión de la Admin API con la que estés trabajando.
Client credentials: integraciones de servidor a servidor
Para procesos internos sin un comerciante delante (automatizaciones, servicios de back office), exchangeUsingClientCredentials obtiene un token solo con el client ID y el client secret:
$result = $shopify->exchangeUsingClientCredentials(shop: $shop);
Según el README, estos tokens caducan a las 24 horas y no traen refresh token: cuando caducan, pides otro de la misma forma. Y no se usan para autenticar peticiones de App Home o de extensiones, que traen su propio ID token para intercambiar.
Lo que el README te pide no hacer
Esta tabla resume los errores que evita el paquete. Vale la pena leerla aunque no lo uses:
| No hagas esto | Haz esto |
|---|---|
| Sacar la tienda del ID token o de la petición | Usar $result->shop |
| Crear tu propia página para renovar el token | Usar appHomePatchIdToken |
| Renderizar App Home sin los encabezados de la respuesta | Copiar $result->response->headers (incluyen la protección del iframe, como frame-ancestors) |
| Armar tu propia respuesta cuando la verificación falla | Devolver $result->response tal cual |
| Modificar encabezados, query string o cuerpo antes de verificar | Pasar la petición sin cambios |
| Poner el ID token en la URL para mantener la sesión al navegar | Configurar la ruta de renovación y pasar appHomePatchIdTokenPath en cada verificación |
En Python es lo mismo
El paquete de Python expone las mismas funciones en snake_case. La inicialización:
import os
from shopify_app import ShopifyApp
shopify = ShopifyApp(
client_id=os.getenv("SHOPIFY_API_KEY"),
client_secret=os.getenv("SHOPIFY_API_SECRET"),
)
Y el webhook:
def webhook_handler(request):
req = request_to_shopify_req(request)
result = shopify.verify_webhook_req(req)
if not result.ok:
return shopify_result_to_response(result)
# Your database logic here
access_token = get_access_token(shop=result.shop, mode="offline")
Para frameworks asíncronos como FastAPI, cada llamada de red tiene su versión con sufijo _async, por ejemplo admin_graphql_request_async, para no bloquear el event loop.
¿Cuándo usarlos?
El changelog es claro: para la mayoría de las apps nuevas, React Router sigue siendo el camino recomendado, y nada cambia si ya usas las librerías de Node.js o Ruby. Mi recomendación:
- Usa estos paquetes si tu equipo o tu infraestructura ya son PHP o Python, por ejemplo un Laravel que ya sincroniza inventario con un ERP o un backend en Django.
- Migra las apps que usan
shopify-api-phposhopify_python_apiruta por ruta, empezando por los webhooks, que son lo más sencillo de verificar. - Dale el README a tu agente de IA. El changelog sugiere usarlo como contexto para una herramienta de programación con IA al montar el proyecto. Si usas Claude Code con Shopify, lo expliqué en Shopify AI Toolkit con Claude Code.
Conclusión
Por fin hay una forma oficial y mantenida de crear apps de Shopify en PHP y Python. No te da una app hecha, pero sí las piezas que más se suelen hacer mal a mano: verificar peticiones, manejar tokens y consultar la Admin API con reintentos. Si tienes código con la librería anterior, ya no recibirá parches de seguridad: es buen momento para planear la migración.