Shopify

Lightweight, dependency-free, in-memory fake of the Shopify Admin REST API (2024-01) for testing code that talks to Shopify.

Default port: 4758

Quick start

import { ShopifyServer } from "./services/shopify/src/server.js";

const server = new ShopifyServer(4758);
await server.start();
// ... run your app/tests ...
await server.stop();

Point your client at the fake and use any access token:

const res = await fetch("http://127.0.0.1:4758/admin/api/2024-01/products.json", {
  method: "POST",
  headers: {
    "X-Shopify-Access-Token": "shpat_parlel",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ product: { title: "Snowboard", vendor: "Parlel" } }),
});
// => 201 { product: { id, title, handle, status, variants: [...], ... } }

Implemented operations

All /admin/api/2024-01/* routes require an X-Shopify-Access-Token header (any non-empty token, or Authorization: Basic for private apps). Resources are wrapped under the singular key on read/write and the plural key on list. State is in-memory and ephemeral.

Products — /admin/api/2024-01/products.json

Orders — /admin/api/2024-01/orders.json

Customers — /admin/api/2024-01/customers.json

Shop — /admin/api/2024-01/shop.json

Service & inspection operations (parlel extensions)

Access via MCP / preview URL

Inside a parlel sandbox the service is reachable at its preview URL (SHOPIFY_BASE_URL, e.g. http://127.0.0.1:4758). Point your Admin REST client at that host and pass any X-Shopify-Access-Token. MCP agents can call any documented endpoint; /__parlel/reset clears state between scenarios.

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatus
products CRUD✅ Supported
orders CRUD✅ Supported
customers CRUD✅ Supported
shop.json✅ Supported
{ resource: {...} } wrap / plural list shape✅ Supported
Numeric resource ids + admin_graphql_api_id✅ Supported
Required-field validation → 422 { errors: { field: [...] } }✅ Supported (product title, customer email-or-name + unique email)
Server-derived create fields (product handle/status/default variants/options; customer state/total_spent/orders_count)✅ Supported
List filters ids / limit / since_id✅ Supported
count.json endpoints✅ Supported
GraphQL Admin API⟳ Roadmap — REST only
Webhooks / fulfillment / inventory / metafields⟳ Roadmap
Link-header cursor pagination⟳ Roadmap
Variants/images side-effects, price rules, taxes◐ Stored as-is, not computed
Rate limiting / leaky bucket (429)✓ By design — Never throttles — local tests run at full speed, zero cost
Token validity / scopes✓ By design — Any non-empty credential is accepted — no real secrets needed

Error codes & shapes

Auth, routing, and not-found errors use the string envelope:

{ "errors": "Not Found" }

Validation errors use the field-keyed envelope, matching the real Admin REST API:

{ "errors": { "title": ["can't be blank"] } }
StatusWhen
400malformed JSON body
401missing X-Shopify-Access-Token
404unknown id or endpoint
405method not allowed
422failed validation (missing required field, duplicate unique value)

Manifest

See services/shopify/manifest.json:

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

SHOPIFY_ACCESS_TOKEN=shpat_parlel
SHOPIFY_SHOP=parlel-test.myshopify.com
SHOPIFY_BASE_URL=http://parlel-bridge:4758
<!-- parlel:testenv:end -->