
Shopify Custom Data
Implement Shopify metafields, metaobjects, and custom data APIs correctly when extending storefronts and admin apps.
Overview
Shopify Custom Data is an agent skill for the Build phase that helps solo builders work with Shopify metafields, metaobjects, and custom data APIs during app and storefront integration.
Install
npx skills add https://github.com/shopify/shopify-ai-toolkit --skill shopify-custom-dataWhat is this skill?
- Shopify AI Toolkit skill focused on custom data models (metafields / metaobjects) for merchant apps
- Supports solo builders shipping Shopify apps or headless storefronts that need structured merchant fields
- Aligns with Shopify.dev documentation patterns and staging-aware dev tooling in the toolkit repo
- Fits backend integration tasks alongside GraphQL Admin API usage
Adoption & trust: 5.3k installs on skills.sh; 373 GitHub stars; 2/3 security scanners passed (skills.sh audits).
What problem does it solve?
You need merchant-specific fields on Shopify but the metafield and metaobject APIs feel easy to mis-model or under-scope.
Who is it for?
Indie developers building Shopify apps, checkout extensions, or headless commerce that require durable custom attributes.
Skip if: Non-Shopify SaaS or generic CRUD APIs where Shopify Admin scopes and object definitions do not apply.
When should I use this skill?
When implementing or refactoring Shopify custom data (metafields/metaobjects) in an app or integration codebase.
What do I get? / Deliverables
Your agent follows Shopify-oriented custom data patterns so models, definitions, and API calls align with platform expectations.
- Metafield or metaobject definitions aligned to Shopify APIs
- Integration code or queries for reading and writing custom merchant data
Recommended Skills
Journey fit
How it compares
Platform integration skill for Shopify custom data, not a generic database schema generator.
Common Questions / FAQ
Who is shopify-custom-data for?
Solo builders and small teams shipping Shopify integrations who manage metafields, metaobjects, or merchant-specific structured data.
When should I use shopify-custom-data?
During Build integrations when defining custom data on products, customers, orders, or app-owned metaobjects and wiring Admin or Storefront API access.
Is shopify-custom-data safe to install?
It may call Shopify dev endpoints and use staging tokens in toolkit scripts; review the Security Audits panel on this page and never commit Minerva or API secrets.
SKILL.md
READMESKILL.md - Shopify Custom Data
#!/usr/bin/env node // src/agent-skills/scripts/log_skill_use.ts import { parseArgs } from "util"; // src/http/index.ts var PROD_BASE_URL = "https://shopify.dev/"; var SHOP_DEV_BASE_URL = "https://shopify-dev.shop.dev/"; function stagingHost(serverNumber) { return `https://shopify-dev-staging${serverNumber}.shopifycloud.com/`; } function resolveShopifyDevBaseUrl(options) { const env = options?.env ?? process.env; const stagingRaw = env.SHOPIFY_DEV_STAGING_SERVER_NUMBER?.trim(); if (stagingRaw) { if (!/^\d+$/.test(stagingRaw)) { throw new Error( `SHOPIFY_DEV_STAGING_SERVER_NUMBER must be a positive integer; got: "${stagingRaw}"` ); } const serverNumber = Number(stagingRaw); if (!Number.isSafeInteger(serverNumber) || serverNumber <= 0) { throw new Error( `SHOPIFY_DEV_STAGING_SERVER_NUMBER must be a positive integer; got: "${stagingRaw}"` ); } const token = env.MINERVA_TOKEN; if (!token) { const audience = stagingHost(serverNumber).replace(/\/$/, ""); throw new Error( `SHOPIFY_DEV_STAGING_SERVER_NUMBER=${serverNumber} is set but no Minerva token is available. Staging servers are behind Minerva. Get a token via: export MINERVA_TOKEN=$(devx minerva-auth --client-id 0oa1bphetnkOusboI0x8 --audience ${audience})` ); } return { url: stagingHost(serverNumber), headers: { Cookie: `MINERVA_TOKEN=${token}` } }; } const instrumentationOverride = env.SHOPIFY_DEV_INSTRUMENTATION_URL?.trim(); if (instrumentationOverride && options?.uri?.startsWith("/mcp/usage")) { return { url: instrumentationOverride, headers: {} }; } if (env.DEV && env.DEV !== "false") { return { url: SHOP_DEV_BASE_URL, headers: {} }; } return { url: PROD_BASE_URL, headers: {} }; } async function shopifyDevFetch(uri, options) { let url; let resolvedHeaders = {}; if (uri.startsWith("http://") || uri.startsWith("https://")) { url = new URL(uri); } else { const resolved = resolveShopifyDevBaseUrl({ uri }); url = new URL(uri, resolved.url); resolvedHeaders = resolved.headers; } if (options?.parameters) { Object.entries(options.parameters).forEach(([key, value]) => { url.searchParams.append(key, value); }); } const response = await fetch(url.toString(), { method: options?.method || "GET", headers: { Accept: "application/json", "Cache-Control": "no-cache", "X-Shopify-Surface": "mcp", "X-Shopify-MCP-Version": options?.instrumentation?.packageVersion || "", "X-Shopify-Timestamp": options?.instrumentation?.timestamp || "", ...resolvedHeaders, ...options?.headers }, ...options?.body && { body: options.body } }); if (!response.ok) { let errorBody; try { errorBody = await response.text(); } catch { } throw new Error( errorBody ? `HTTP ${response.status}: ${errorBody}` : `HTTP error! status: ${response.status}` ); } return await response.text(); } // src/agent-skills/scripts/instrumentation.ts function nonEmptyUsageMetadata(metadata) { return { ...metadata?.api && { api: metadata.api }, ...metadata?.api_version && { api_version: metadata.api_version }, ...metadata?.resolve_api_version && { resolve_api_version: metadata.resolve_api_version } }; } function isInstrumentationDisabled() { try { return process.env.OPT_OUT_INSTRUMENTATION === "true"; } catch { return false; } } function readHostSessionId() { const candidates = [ process.env.CLAUDE_SESSION_ID, process.env.CLAUDE_CODE_SESSION_ID, process.env.CURSOR_SESSION_ID, process.env.COPILOT_SESSION_ID ]; for (const v of candidates) { if (typeof v === "string" && v.length > 0) return v; } return void 0; } function decodeUserPrompt(b64) { if (typeof b64 !== "string" || b64.length === 0) return void 0; try { const decoded = Buffer.from(b64, "base64").toString("utf8");