Skip to main content

Discounts

Discount details pages display information about a specific discount, including its type, value, conditions, and usage limits. Extensions on these pages help merchants manage promotional campaigns and customize discount workflows.

  • Sync with marketing platforms: Automatically sync discount codes with external marketing tools like email campaigns, social media ads, or affiliate networks.
  • Validate discount rules: Check discount configurations against business rules or inventory levels before merchants activate promotions.
  • Track promotion performance: Display real-time analytics and conversion metrics for active discount campaigns from external analytics systems.
  • Bulk discount management: Enable merchants to update, duplicate, or archive multiple discounts at once from the discount index page.
  • Generate unique codes: Create batches of unique discount codes for influencer campaigns or customer loyalty programs.
Shopify admin discount pages showing all available extension target locations.

Anchor to Discount details targetsDiscount details targets

Use action targets to extend the discount details page with workflows. Action targets open as modal overlays from the More actions menu.

The examples demonstrate fetching data from Shopify's direct API or your app's backend.

admin.discount-details.action.render

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

Extensions at this target can access discount 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 [posSystem, setPosSystem] = useState('square');
const [syncAllLocations, setSyncAllLocations] = useState(true);

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

try {
const response = await fetch('https://your-app.com/api/sync-discount-to-pos', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
discountId,
posSystem,
syncAllLocations,
}),
});

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

return (
<s-admin-action heading="Sync Discount to POS">
<s-stack direction="block" gap="base">
{success && (
<s-banner tone="success" dismissible={false}>
Discount synced to POS successfully!
</s-banner>
)}
{error && (
<s-banner tone="critical" dismissible={false}>
{error}
</s-banner>
)}

<s-section heading="POS Configuration">
<s-stack direction="block" gap="small">
<s-select
label="POS System"
value={posSystem}
onChange={(e) => setPosSystem(e.currentTarget.value)}
>
<s-option value="square">Square</s-option>
<s-option value="clover">Clover</s-option>
<s-option value="lightspeed">Lightspeed</s-option>
</s-select>
<s-checkbox
label="Sync to all store locations"
checked={syncAllLocations}
onChange={(e) => setSyncAllLocations(e.currentTarget.checked)}
/>
</s-stack>
</s-section>

<s-button-group>
<s-button
variant="primary"
onClick={handleSync}
disabled={loading || success}
>
{loading ? 'Syncing...' : 'Sync to POS'}
</s-button>
<s-button onClick={() => shopify.close()}>Cancel</s-button>
</s-button-group>
</s-stack>
</s-admin-action>
);
};

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

admin.discount-details.action.should-render

Controls the render state of an admin action extension on the discounts details page. Use this target to conditionally show or hide your action extension based on the discount'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 discountId = shopify.data.selected[0].id;

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

if (!response.ok) {
console.error('Failed to check discount tracking status');
return { display: false };
}

const result = await response.json();

// Show action if discount is tracked and has performance data
return {
display: result.isTracked && result.hasPerformanceData,
};
} catch (err) {
console.error('Error checking discount analytics:', error);
return { display: false };
}
};

Anchor to Discount index targetsDiscount index targets

Use action targets to extend the discount index page with bulk operations and workflows that help merchants manage multiple discounts efficiently.

admin.discount-index.action.render

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

Extensions at this target can access discount 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 [syncAllLocations, setSyncAllLocations] = useState(true);
const [posSystem, setPosSystem] = useState('square');

const handleSync = async () => {
setLoading(true);
setError(null);

try {
const response = await fetch('https://your-app.com/api/pos/sync-discount', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
discountIds: shopify.data.selected.map(item => item.id),
posSystem,
syncAllLocations,
}),
});

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

const selectedCount = shopify.data.selected.length;

return (
<s-admin-action heading="Sync to POS System">
<s-stack direction="block" gap="base">
{success && (
<s-banner tone="success" dismissible={false}>
{selectedCount} discount(s) synced to POS successfully!
</s-banner>
)}
{error && (
<s-banner tone="critical" dismissible={false}>
{error}
</s-banner>
)}

<s-text>
Sync {selectedCount} selected discount(s) to your POS terminals.
</s-text>

<s-section heading="POS Settings">
<s-stack direction="block" gap="small">
<s-select
label="POS System"
value={posSystem}
onChange={(e) => setPosSystem(e.currentTarget.value)}
>
<s-option value="square">Square</s-option>
<s-option value="clover">Clover</s-option>
<s-option value="lightspeed">Lightspeed</s-option>
</s-select>
<s-checkbox
label="Sync to all store locations"
checked={syncAllLocations}
onChange={(e) => setSyncAllLocations(e.currentTarget.checked)}
/>
</s-stack>
</s-section>

<s-button-group>
<s-button
variant="primary"
onClick={handleSync}
disabled={loading || success}
>
{loading ? 'Syncing...' : `Sync ${selectedCount} Discount(s)`}
</s-button>
<s-button onClick={() => shopify.close()}>Cancel</s-button>
</s-button-group>
</s-stack>
</s-admin-action>
);
};

Anchor to Discount index action (should render) ,[object Object]Discount index action (should render) target

admin.discount-index.action.should-render

Controls the render state of an admin action extension on the discounts index page. Use this target to conditionally show or hide your action extension based on the discount'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 discountId = shopify.data.selected[0].id;

try {
const response = await fetch('https://your-app.com/api/discount-tracking/check', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
discountId,
checkType: 'performance-eligibility',
}),
});

if (!response.ok) {
console.error('Failed to check discount tracking status');
return { display: false };
}

const result = await response.json();

// Show action if discount has tracking enabled and has redemption data
const hasTrackingEnabled = result.trackingEnabled === true;
const hasRedemptionData = result.redemptionCount > 0;

return { display: hasTrackingEnabled && hasRedemptionData };
} catch (err) {
console.error('Error checking discount performance eligibility:', error);
return { display: false };
}
};

admin.discount-index.selection-action.render

Renders an admin action extension on the discounts index page when one or more discounts are selected. Merchants can access this extension from the More actions menu that appears in the bulk action bar. Use this target to provide workflows that operate on the selected discounts, such as bulk exporting, batch syncing to external systems, or mass updating of campaign tags.

Extensions at this target can access the selected discount 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 [format, setFormat] = useState('csv');

const selectedCount = shopify.data.selected.length;
const discountIds = shopify.data.selected.map((item) => item.id);

const handleExport = async () => {
setLoading(true);
setError(null);

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

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

return (
<s-admin-action heading="Export Selected Discounts">
<s-stack direction="block" gap="base">
{success && (
<s-banner tone="success" dismissible={false}>
{selectedCount} discount(s) exported successfully!
</s-banner>
)}
{error && (
<s-banner tone="critical" dismissible={false}>
{error}
</s-banner>
)}

<s-text>
Export {selectedCount} selected discount{selectedCount !== 1 ? 's' : ''} to your marketing platform.
</s-text>

<s-select
label="Export format"
value={format}
onChange={(e) => setFormat(e.currentTarget.value)}
>
<s-option value="csv">CSV</s-option>
<s-option value="json">JSON</s-option>
</s-select>

<s-button-group>
<s-button
variant="primary"
onClick={handleExport}
disabled={loading || success}
>
{loading ? 'Exporting...' : `Export ${selectedCount} Discount${selectedCount !== 1 ? 's' : ''}`}
</s-button>
<s-button onClick={() => shopify.close()}>Cancel</s-button>
</s-button-group>
</s-stack>
</s-admin-action>
);
};

Anchor to Discount index selection action (should render) ,[object Object]Discount index selection action (should render) target

admin.discount-index.selection-action.should-render

Controls the render state of an admin selection action extension on the discounts index page. Use this target to conditionally show or hide your selection action extension based on the selected discounts' properties, merchant permissions, or external business requirements.

This target returns a boolean value that determines whether the corresponding selection action extension appears in the More actions menu when discounts are selected. The extension evaluates each time the selection changes.

Support
Components (0)
APIs (1)

Supported components

-

Available APIs

Examples

jsx

export default async () => {
const discountIds = shopify.data.selected.map((item) => item.id);

try {
const response = await fetch('https://your-app.com/api/discount-tracking/check-batch', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
discountIds,
checkType: 'export-eligibility',
}),
});

if (!response.ok) {
console.error('Failed to check discount tracking status');
return { display: false };
}

const result = await response.json();

return {
display: result.allTracked && result.hasExportData,
};
} catch (err) {
console.error('Error checking discount export eligibility:', err);
return { display: false };
}
};

  • Differentiate discount types: Discounts come in multiple types (basic, BXGY, free shipping, automatic vs code-based). Before displaying discount actions, check the discount type using GraphQL to ensure your extension supports it. For example, POS sync may only work with certain discount types.
  • Validate discount dates: Always check startsAt and endsAt when displaying or syncing discounts. Syncing expired or not-yet-active discounts to external systems can create customer confusion and needs special handling or filtering.
  • Show usage vs limits clearly: When displaying discount analytics, show both asyncUsageCount and usageLimit together. Merchants need to see how close a discount is to its usage limit to decide whether to extend it or create a new code.
  • Handle discount combinations: Shopify has complex discount combination rules. If your extension recommends or creates discounts, validate that they're compatible with existing discount configurations to avoid conflicts that prevent customers from completing checkouts.
  • Account for attribution delays: Discount usage counts (asyncUsageCount) update asynchronously and may lag by several minutes. When displaying real-time analytics, indicate that counts are approximate and mention the last update time if available from your system.


Was this page helpful?