Custom Cart Integration
Integrating Abra promotions with a native Shopify theme cart. Abra is loaded as a global app embed and already runs on the storefront. This spec is what the theme/cart must do to work with it.
Constraints
Cart mutations must use Shopify's AJAX Cart endpoints:
/cart/add.js,/cart/change.js,/cart/update.js,/cart/clear.js(or full page reload). Abra detects cart changes by observing these requests and re-renders itself. A custom cart that mutates only via Storefront GraphQL with no reload will not trigger Abra.Cart updates must use
fetch(notXMLHttpRequest) for/cart/change.js.
Line-item properties to handle
Abra writes metadata onto cart lines (line-item properties) and onto the cart (cart attributes) to track promotion state: which lines are free gifts/rewards, which lines are a bundle and its components, the bundle definition, and which promo code is attributed to the cart. Your theme reads this metadata to style and lock the relevant lines.
Where to read them:
Liquid:
item.properties.<key>on each cart line;cart.attributes.<key>for cart-level.AJAX API:
GET /cart.js→items[].properties.<key>per line; top-levelattributes.<key>for cart-level.
Values come back as strings. The ones marked "JSON" below are JSON-encoded strings you must JSON.parse.
Property | Value | Required theme behavior |
|
| Hide/disable the quantity selector for this line. |
| non-empty JSON string | This line is the merged bundle (also has Shopify
|
| marker string | This line is a bundle component. Style as bundle child. |
| JSON | Line tied to an Abra promotion (gift/reward). Optional to display. |
Cart-level attributes (on cart.attributes):
Attribute | Value |
| JSON array of bundle configs (bundle definition). |
| JSON |
Gift and reward lines
Free gifts come from Gift-with-Purchase promotions and from any "free gift" tier of a tiered / volume / multi-effect promo — all use the same mechanism. Abra auto-adds the gift as a normal cart line and tags it:
__hide_quantity"true") → Optionally hide/disable the quantity selector on this line.__abra{"discount":"<promo title>"}) → which promotion the gift belongs to.
if (item.properties?.__hide_quantity) {
// gift / reward line → hide the quantity selector
}Bundle identification
Identify Abra bundles by these line item properties:
_abra_bundle= the one merged line the customer sees as "the bundle." This is the line you style and quantity-lock._abra_bundled(the components) = the individual products that were folded into it._abra_bxgy_bundle(cart attribute, not a line property) = the bundle's definition (title, items, fixed price) Abra uses to build/rebuild it.
In other words, the merged _abra_bundle line is the top-level line; the _abra_bundled products are its components beneath it.
What this looks like in /cart.js:
{
"attributes": {
"_abra_bxgy_bundle": "[{\"bundleTitle\":\"Father's Day Bundle\",\"bundleItems\":[...],\"fixedPriceCents\":3999,\"currencyCode\":\"USD\"}]",
"__abra": "{\"code\":\"SAVE20\",\"redeemCode\":null,\"attributedAt\":\"2026-06-16T...\"}"
},
"items": [
{
"has_components": true,
"properties": {
"_abra_bundle": "{\"discountTitle\":\"Father's Day Bundle\",...}"
}
},
{
"properties": {
"_abra_bundled": "Father's Day Bundle::0"
}
},
{
"properties": {
"__hide_quantity": "true",
"__abra": "{\"discount\":\"Free Gift\"}"
}
}
]
}Cart UI events
Abra re-renders its own injected content (discounted prices, banners) on its own when it sees cart requests — you don't trigger that. But when Abra changes the cart contents itself (auto-adds/removes a gift, merges a bundle), your cart drawer doesn't know. Wire up two events:
Listen to
abra:cart:changed— Abra fires this after it mutates the cart. Re-fetch and re-render your cart so Abra-added gifts/bundles appear.
window.addEventListener('abra:cart:changed', async () => {
// Your re-render
});Dispatch
abra:renderif you want to re-render the Abra banner
window.dispatchEvent(new CustomEvent('abra:render'));