Skip to content

Workflow methods use the standard global fetch(). There is no api.fetch() wrapper.

fetch() is available in normal workflow steps and in lifecycle hooks.

When your fetch() URL targets your store’s Admin API domain, the platform automatically injects the X-Shopify-Access-Token header. Do not set that header manually.

Your store domain and Admin API version are available on the global env object:

export class Workflow {
async start(data, headers, api) {
const orderId = data.admin_graphql_api_id ?? `gid://shopify/Order/${data.id}`;
const res = await fetch(
`https://${env.SHOPIFY_STORE}/admin/api/${env.SHOPIFY_API_VERSION}/graphql.json`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `mutation UpdateOrderNote($input: OrderInput!) {
orderUpdate(input: $input) {
order { id note }
userErrors { field message }
}
}`,
variables: { input: { id: orderId, note: 'Processed' } },
}),
}
);
if (!res.ok) {
throw new Error(`Shopify API ${res.status}: ${await res.text()}`);
}
const json = await res.json();
if (json.errors?.length) {
console.log('GraphQL errors:', json.errors);
return;
}
const { order, userErrors } = json.data.orderUpdate;
if (userErrors.length) {
console.log('User errors:', userErrors);
return;
}
console.log('Updated note:', order.note);
}
}

Read-only queries use the same endpoint and the same automatic token injection:

const res = await fetch(
`https://${env.SHOPIFY_STORE}/admin/api/${env.SHOPIFY_API_VERSION}/graphql.json`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `query GetOrders {
orders(first: 10) {
nodes { id name }
}
}`,
}),
}
);
if (!res.ok) {
throw new Error(`Shopify API ${res.status}: ${await res.text()}`);
}
const json = await res.json();
if (json.errors?.length) {
throw new Error(JSON.stringify(json.errors));
}
const orders = json.data.orders.nodes;

Use api.getOAuthToken(handle) to get a valid access token for a connected OAuth service, then add it to your request headers manually. The platform automatically refreshes the token if it has expired.

export class Workflow {
async start(data, headers, api) {
const { token, error } = await api.getOAuthToken('my-slack');
if (error) {
console.log('OAuth error:', error);
return;
}
await fetch('https://slack.com/api/chat.postMessage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({ channel: '#orders', text: `New order: ${data.id}` }),
});
}
}

The handle is the name you assigned when connecting the service in OAuth Connections.

Store API keys via More actions → Manage variables in the workflow editor and access them via env.*:

const res = await fetch('https://api.example.com/notify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${env.EXAMPLE_API_KEY}`,
},
body: JSON.stringify({ event: 'order_paid', orderId: data.id }),
});

Pass a plain JavaScript object as the body by serialising it with JSON.stringify(). Set Content-Type: application/json when doing this:

const res = await fetch('https://api.example.com/endpoint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' }),
});