Skip to main content

Product variants

Product variant pages display information about individual product variations, including SKU, price, inventory levels, and option values like size or color. Extensions on these pages help merchants manage variant-specific workflows, sync inventory with external systems, and configure purchase options.

  • Inventory synchronization: Sync variant inventory levels with external warehouse management systems, 3PLs, or ERP platforms to maintain accurate stock counts across multiple locations and sales channels.
  • Pricing and cost management: Display cost information from suppliers, calculate margins, apply bulk pricing rules, or sync variant prices with external pricing engines and wholesale platforms.
  • Marketplace publishing: Push variant data to external marketplaces like Amazon, eBay, or Google Shopping, including SKU mappings, inventory levels, and marketplace-specific attributes.
  • Subscription and purchase options: Configure variant-specific subscription settings, bundle configurations, or pre-order options through external subscription management platforms.
  • Variant analytics: Display variant-level performance metrics, sales velocity, or demand forecasting data from external analytics platforms to help merchants optimize inventory.
Shopify admin product variant pages showing all available extension target locations.

Anchor to Product variant details targetsProduct variant details targets

Use action and block targets to extend the product variant details page with workflows and contextual information.

Extensions can query and mutate Shopify data using the direct API, or call your app's backend for custom business logic and external integrations.

admin.product-variant-details.action.render

Renders an admin action extension on the product variants details page. Merchants can access this extension from the More actions menu. Use this target to provide workflows that operate on product variants data, such as syncing with external systems, exporting product variants information, or managing credit terms.

Extensions at this target can access product variants data through the data property in the Action Extension API. The action renders in a modal overlay, providing space for multi-step workflows, forms, and confirmations.

Examples

jsx

import {render} from 'preact';
import {useState} from 'preact/hooks';

export default async () => {
render(<Extension />, document.body);
};

const Extension = () => {
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);
const [warehouse, setWarehouse] = useState('main');
const [updateThreshold, setUpdateThreshold] = useState(false);

const handleSync = async () => {
setLoading(true);
setError(false);
const variantId = shopify.data.selected[0].id;

try {
const response = await fetch('https://your-app.com/api/inventory/sync', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
variantId,
warehouse,
updateThreshold,
}),
});

if (response.ok) {
setSuccess(true);
shopify.close();
} else {
setError(true);
}
} catch (err) {
setError(true);
} finally {
setLoading(false);
}
};

return (
<s-admin-action heading="Sync Inventory">
{success && (
<s-banner tone="success" dismissible={false}>
Inventory synced successfully from warehouse!
</s-banner>
)}
{error && (
<s-banner tone="critical" dismissible={false}>
Failed to sync inventory. Please try again.
</s-banner>
)}

<s-section heading="Warehouse Settings">
<s-stack gap="base">
<s-select
label="Source Warehouse"
value={warehouse}
onChange={(event) => setWarehouse(event.currentTarget.value)}
>
<s-option value="main">Main Warehouse</s-option>
<s-option value="east">East Coast DC</s-option>
<s-option value="west">West Coast DC</s-option>
</s-select>

<s-checkbox
label="Update low stock threshold"
checked={updateThreshold}
onChange={(event) => setUpdateThreshold(event.currentTarget.checked)}
/>

<s-text color="subdued">
This will fetch the latest inventory count from your warehouse system.
</s-text>
</s-stack>
</s-section>

<s-button
slot="primary-action"
onClick={handleSync}
disabled={loading || success}
>
{loading ? 'Syncing...' : 'Sync Inventory'}
</s-button>
<s-button slot="secondary-actions" onClick={() => shopify.close()}>
Cancel
</s-button>
</s-admin-action>
);
};

Anchor to Product variant details action (should render) ,[object Object]Product variant details action (should render) target

admin.product-variant-details.action.should-render

Controls the render state of an admin action extension on the product variants details page. Use this target to conditionally show or hide your action extension based on the product variant's properties, such as status, configuration, or specific business requirements.

This target returns a boolean value that determines whether the corresponding action extension appears in the More actions menu. The extension evaluates each time the page loads.

Support
Components (0)
APIs (1)

Supported components

-

Available APIs

Examples

jsx

export default async () => {
const variantId = shopify.data.selected[0].id;

try {
// Check with app backend if this variant has inventory sync enabled
const response = await fetch('https://your-app.com/api/check-inventory-sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ variantId }),
});

if (!response.ok) {
console.error('Backend check failed:', response.status);
return { display: false };
}

const result = await response.json();

// Only show action if variant is enrolled in inventory sync
return { display: result.syncEnabled === true };
} catch (err) {
console.error('Error checking inventory sync status:', err);
return { display: false };
}
};

admin.product-variant-details.block.render

Renders an admin block extension inline on the product variants details page. Use this target to display contextual information, analytics, or status updates related to the product variants without requiring merchant interaction to open a modal.

Extensions at this target can access product variants data through the data property in the Block Extension API. Blocks appear as cards on the page and can show real-time data, insights, or quick actions, providing persistent visibility for information merchants need to see at a glance.

Examples

jsx

import {render} from 'preact';
import {useState, useEffect} from 'preact/hooks';

export default async () => {
render(<Extension />, document.body);
};

const Extension = () => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [competitors, setCompetitors] = useState([]);
const [variantPrice, setVariantPrice] = useState(null);

useEffect(() => {
fetchCompetitorPricing();
}, []);

const fetchCompetitorPricing = async () => {
const variantId = shopify.data.selected[0].id;

try {
const response = await fetch('https://your-app.com/api/competitor-pricing', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({variantId}),
});

if (response.ok) {
const data = await response.json();
setCompetitors(data.competitors || []);
setVariantPrice(data.yourPrice);
} else {
setError(true);
}
} catch (err) {
setError(true);
} finally {
setLoading(false);
}
};

if (loading) {
return (
<s-admin-block heading="Competitor Pricing">
<s-stack gap="base">
<s-spinner size="base" />
<s-text color="subdued">Loading competitor data...</s-text>
</s-stack>
</s-admin-block>
);
}

if (error) {
return (
<s-admin-block heading="Competitor Pricing">
<s-banner tone="critical" dismissible={false}>
Unable to fetch competitor pricing data.
</s-banner>
</s-admin-block>
);
}

return (
<s-admin-block heading="Competitor Pricing">
<s-stack gap="base">
<s-box>
<s-text type="strong">Your price: </s-text>
<s-text>{variantPrice || '$29.99'}</s-text>
</s-box>
<s-divider />
<s-section heading="Market Comparison">
<s-stack gap="small">
<s-box>
<s-text>Amazon: $32.99 </s-text>
<s-badge tone="success">You're lower</s-badge>
</s-box>
<s-box>
<s-text>Walmart: $27.99 </s-text>
<s-badge tone="warning">$2 cheaper</s-badge>
</s-box>
<s-box>
<s-text>Target: $29.99 </s-text>
<s-badge>Same price</s-badge>
</s-box>
</s-stack>
</s-section>
<s-text color="subdued">Last updated: 2 hours ago</s-text>
</s-stack>
</s-admin-block>
);
};

Anchor to Product variant details configuration ,[object Object]Product variant details configuration target

Examples

jsx

import {render} from 'preact';
import {useState} from 'preact/hooks';

export default async () => {
render(<Extension />, document.body);
};

const Extension = () => {
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState(null);
const [updateBackorder, setUpdateBackorder] = useState(true);
const [syncResult, setSyncResult] = useState(null);

const handleSync = async () => {
setLoading(true);
setError(null);
const variantId = shopify.data.selected[0].id;

try {
const response = await fetch('https://your-app.com/api/inventory/sync', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
variantId,
updateBackorder,
}),
});

if (response.ok) {
const result = await response.json();
setSyncResult(result);
setSuccess(true);
shopify.close();
} else {
const errorData = await response.json();
setError(errorData.message || 'Failed to sync inventory');
}
} catch (err) {
setError('Connection error. Please try again.');
} finally {
setLoading(false);
}
};

return (
<s-admin-action heading="Sync Variant Inventory">
{success && syncResult && (
<s-banner tone="success" dismissible={false}>
Inventory synced! New quantity: {syncResult.quantity} units
</s-banner>
)}
{error && (
<s-banner tone="critical" dismissible={false}>
{error}
</s-banner>
)}

<s-section heading="Sync Options">
<s-stack gap="base">
<s-text color="subdued">
Pull the latest inventory count from your warehouse system.
</s-text>
<s-checkbox
label="Update backorder availability"
checked={updateBackorder}
onChange={(event) => setUpdateBackorder(event.currentTarget.checked)}
/>
</s-stack>
</s-section>

<s-button
slot="primary-action"
onClick={handleSync}
disabled={loading || success}
>
{loading ? 'Syncing...' : 'Sync Inventory'}
</s-button>
<s-button slot="secondary-actions" onClick={() => shopify.close()}>
Cancel
</s-button>
</s-admin-action>
);
};

Anchor to Product variant purchase option targetsProduct variant purchase option targets

Use action targets to extend the product variant purchase option page with workflows and operations.

Extensions can query and mutate Shopify data using the direct API, or call your app's backend for custom business logic and external integrations.

Anchor to Product variant purchase option action ,[object Object]Product variant purchase option action target

admin.product-variant-purchase-option.action.render

Renders an admin action extension on the product variants details page. Merchants can access this extension from the More actions menu. Use this target to provide workflows that operate on product variants data, such as syncing with external systems, exporting product variants information, or managing credit terms.

Extensions at this target can access product variants data through the data property in the Action Extension API. The action renders in a modal overlay, providing space for multi-step workflows, forms, and confirmations.

Examples

jsx

import {render} from 'preact';
import {useState} from 'preact/hooks';

export default async () => {
render(<Extension />, document.body);
};

const Extension = () => {
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState(null);
const [warehouse, setWarehouse] = useState('main');
const [updateThreshold, setUpdateThreshold] = useState(false);

const handleSync = async () => {
setLoading(true);
setError(null);
const variantId = shopify.data.selected[0].id;

try {
const response = await fetch('https://your-app.com/api/inventory/sync', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
variantId,
warehouse,
updateThreshold,
}),
});

if (response.ok) {
setSuccess(true);
shopify.close();
} else {
const data = await response.json();
setError(data.message || 'Failed to sync inventory');
}
} catch (err) {
setError('Connection error. Please try again.');
} finally {
setLoading(false);
}
};

return (
<s-admin-action heading="Sync Inventory">
{success && (
<s-banner tone="success" dismissible={false}>
Inventory synced successfully!
</s-banner>
)}
{error && (
<s-banner tone="critical" dismissible={false}>
{error}
</s-banner>
)}

<s-section heading="Sync Settings">
<s-stack gap="base">
<s-select
label="Source Warehouse"
value={warehouse}
onChange={(event) => setWarehouse(event.currentTarget.value)}
>
<s-option value="main">Main Warehouse</s-option>
<s-option value="east">East Coast DC</s-option>
<s-option value="west">West Coast DC</s-option>
</s-select>

<s-checkbox
label="Update low stock threshold"
checked={updateThreshold}
onChange={(event) => setUpdateThreshold(event.currentTarget.checked)}
/>

<s-text color="subdued">
This will fetch the latest inventory count from your warehouse system.
</s-text>
</s-stack>
</s-section>

<s-button
slot="primary-action"
onClick={handleSync}
disabled={loading || success}
>
{loading ? 'Syncing...' : 'Sync Inventory'}
</s-button>
<s-button slot="secondary-actions" onClick={() => shopify.close()}>
Cancel
</s-button>
</s-admin-action>
);
};

  • Display variant context clearly: Always show which product a variant belongs to (product title) alongside variant-specific details (option values, SKU). Merchants often view variants out of context and need this information to make decisions.
  • Aggregate inventory across locations: When displaying variant inventory, show total inventory by default but allow filtering by location. Merchants with multi-location setups need location-specific visibility for fulfillment decisions.
  • Validate marketplace requirements: Before publishing variants to external marketplaces, validate that required variant fields (SKU, barcode, weight, dimensions) are populated. Many marketplaces reject variants missing these fields, and early validation prevents failed sync attempts.
  • Handle option combinations carefully: Variants are defined by option combinations (for example, Size: Large, Color: Red). When building extensions that manipulate variants, preserve the option structure and validate that option combinations remain unique within the product.
  • Check inventory tracking status: Use inventoryItem.tracked to determine if a variant tracks inventory before displaying inventory-related actions. Extensions that assume all variants track inventory will fail for digital products or services.

  • Single target per module: Each [[extensions.targeting]] entry in your TOML configuration maps one target to one module file.
  • Purchase option target visibility: The admin.product-variant-purchase-option.action.render target only appears when the product variant has a selling plan group associated with it.
  • Configuration target availability: The admin.product-variant-details.configuration.render target only appears for product variants configured as bundles.
  • Block target visibility: Block extensions must be manually added and pinned by merchants before they appear.
  • Block collapse behavior: Returning null from a block extension collapses the block rather than removing it from the page. Blocks can't be fully hidden at runtime.

Was this page helpful?