A Shopify Theme App Extension (TAE) that adds per-item gift wrapping to any product page. Customers can select a wrap colour, add a personalised message, and the gift wrap line item is automatically linked to its parent product in the cart.
Works on all Shopify plans — no Shopify Plus required.
📖 Ready to go live? See PUBLISHING.md for the full step-by-step production deploy guide. 🏪 Installing on a client store? See INSTALLING.md for the per-store setup guide. 📋 What's new? See CHANGELOG.md for the full version history.
| Feature | Detail |
|---|---|
| Per-item selection | Gift wrap checkbox on each product page |
| Auto colour variants | Colours pulled directly from your Gift Wrap product — no manual IDs |
| Gift message | Optional 200-character personalised message per item |
| Smart cart sync | Gift wrap is auto-removed when its product is removed from the cart |
| Duplicate prevention | One gift wrap per product, always — no accidental duplicates |
| Cart ordering | Gift wrap line always appears directly below its product |
| Qty control | Option to hide the quantity selector for gift wrap in cart |
| Accent colour | Fully customisable brand colour for checkbox and accents |
| Dawn-compatible | Works with Dawn, Prestige, Impulse and most modern Shopify themes |
gift-wrap-app/
├── shopify.app.toml ← App config (client_id, store URL)
├── package.json
├── web/
│ ├── index.js ← HTTPS admin page (shown in Shopify admin)
│ └── certs/
│ ├── cert.pem ← Self-signed TLS cert (dev only)
│ └── key.pem ← Private key (git-ignored)
└── extensions/
└── gift-wrapping/
├── shopify.extension.toml
├── blocks/
│ └── gift_wrap.liquid ← Liquid block + schema settings
├── assets/
│ ├── gift-wrap.js ← Cart API logic (v6)
│ └── gift-wrap.css ← Styles
└── locales/
└── en.default.json
- Node.js 18+
- Shopify Partner account → https://partners.shopify.com
- Shopify CLI (installed automatically via
npm install) - A dev store and/or a live store where the app will be installed
Do this in every Shopify store where you want the extension active (dev + production).
- Go to Products → Add product
- Title:
Gift Wrapping - Price:
10.00(or whatever you charge — this is the real charge at checkout) - Under Options, add one option:
- Option name:
Colour - Values:
Gray,Gold,Silver(add/remove to match your stock)
- Option name:
- Set status to
Draft— this hides it from your storefront but keeps it purchasable via the cart API - Save
No variant IDs to copy. The theme block reads variants automatically via the product picker.
cd gift-wrap-app
npm installThe admin web page must be served over HTTPS (Shopify requirement). Generate a self-signed cert for localhost:
mkdir -p web/certs
openssl req -x509 \
-newkey rsa:2048 \
-keyout web/certs/key.pem \
-out web/certs/cert.pem \
-days 3650 \
-nodes \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
key.pemis git-ignored.cert.pemis safe to commit (it's public).
Open two terminal tabs:
# Tab 1 — Extension dev server (hot-reloads your Liquid/JS/CSS)
shopify app dev
# Tab 2 — Admin web page (serves the "Manage app" page over HTTPS)
npm run webshopify app dev will:
- Ask you to log into your Partner account (first run only)
- Install the app on your dev store
- Start a preview server at
http://127.0.0.1:9293 - Hot-reload the extension on every file save
Important: Always stop with
Ctrl+Cor pressqin the CLI panel. Never useCtrl+Z— suspended processes hold their ports open and causeEADDRINUSEerrors on next start.
- In Shopify admin → Online Store → Themes → Customize
- Using the page selector (top centre), switch to a Product template
- In the left panel, click Add block → choose Per-Item Gift Wrapping
- In the block settings, configure:
| Setting | What to do |
|---|---|
| Gift Wrap Product | Click the picker and select your Gift Wrapping product |
Colour variants populate automatically from the product's variants — no manual entry needed.
| Setting | Default | Description |
|---|---|---|
| Section title | Add Gift Wrapping |
Heading shown above the block |
| Display price | 10.00 |
Visual price shown to customers (cosmetic only — real price is on the product) |
| Currency | AED |
Currency symbol shown next to the price |
| Setting | Default | Description |
|---|---|---|
| Accent colour | #c0a96e (gold) |
Checkbox fill, price text and focus ring colour |
| Header icon | 🎁 |
Emoji shown before the section title — paste any emoji or leave blank to hide |
| Setting | Default | Description |
|---|---|---|
| Allow gift message | On | Shows a free-text message field (max 200 chars) |
| Message placeholder | Write a personal message... |
Grey hint text inside the textarea |
| Setting | Default | Description |
|---|---|---|
| Hide quantity selector | On | Hides the qty stepper for gift wrap rows in cart — recommended |
| Show Remove button | On | Adds an inline "Remove" link on the product page |
Always on (not configurable): One gift wrap per product (no duplicates), and gift wrap is automatically removed from cart when its parent product is removed.
- Save the theme.
shopify app deployThis pushes the extension code to Shopify's CDN and makes it available to any store that has the app installed. You will be prompted to confirm.
After deploying, the app needs to be installed on your live store:
- Go to your Shopify Partner Dashboard → Apps → Gift Wrap App
- Click Select store → choose your production store
- Click Install — this authorises the app and makes the extension available in the theme editor
- In your production store's Shopify admin → Online Store → Themes → Customize
- Repeat Step 3 above to add and configure the block on the live theme
You only do this install step once per store. After that,
shopify app deploypushes updates automatically to all stores where the app is installed.
After installing:
- Visit a product page on your live store
- The gift wrap block should appear (if it's been added to the theme)
- Add a product + gift wrap to cart and verify both appear correctly
- Remove the product from the cart — the gift wrap should disappear automatically
After making code changes locally:
# 1. Test locally with shopify app dev first
# 2. When ready, deploy:
shopify app deployChanges are live within a few minutes on all stores where the app is installed. No theme re-save needed — the extension assets are served from Shopify's CDN.
Go to Orders → [any order with gift wrap]. Each gift-wrapped item creates a linked line item:
| Line Item | Variant | Properties |
|---|---|---|
| The Complete Snowboard | Ice | — |
| Gift Wrapping | Gold | Wrapped item: The Complete Snowboard |
| Frankincense Oil | Oman 10ml | — |
| Gift Wrapping | Gray | Wrapped item: Frankincense Oil · Gift message: Happy Birthday! |
The Wrapped item property links the gift wrap to its product for fulfilment.
The extension (v6) includes several automatic behaviours:
When a product is removed from the cart (via the cart drawer, cart page, or any method), its gift wrap is automatically removed too. This works even across different product pages by intercepting Dawn's fetch calls to /cart/change.js.
If a customer checks the gift wrap box before adding the product to cart, the wrap is not prematurely removed. The parentEverInCart flag tracks whether the product was ever confirmed present.
The cleanup function uses a cleanupRunning flag to prevent infinite loops — without this, the cleanup's own cart API calls would re-trigger themselves.
Gift wrap rows in the cart drawer are automatically reordered to appear directly below their parent product, regardless of the order they were added.
A previous shopify app dev process is still running.
lsof -ti tcp:9293 | xargs kill -9
# Then restart:
shopify app devThe HTTPS cert is missing. Re-run the openssl command from Step 2.
Then visit https://localhost:3000 in your browser, click Advanced → Proceed to localhost to trust the self-signed cert.
The Gift Wrap Product picker in the block settings is empty. Open Theme Editor → click the block → set the product.
Ensure you are running v6 of the extension (shopify app deploy after the latest changes). Check browser console for [GiftWrap] log messages.
Make sure shopify app dev is running (dev) or shopify app deploy has been run and the app is installed on the store (production).
Dawn's cart drawer uses a <table> element. The reorder logic handles this automatically. If your theme uses a different structure, open an issue with your theme name.
| Theme | Status |
|---|---|
| Dawn (Shopify default) | ✅ Fully supported |
| Prestige | ✅ Supported |
| Impulse | ✅ Supported |
| Other themes | Should work — uses standard Shopify Cart AJAX API |
If a theme doesn't fire cart update events after adding/removing items, the fetch interceptor (added in v5) catches these changes anyway.
web/certs/key.pemis in.gitignore— never commit it- The self-signed cert is for local development only
- For a production-hosted admin page, replace with a cert from a real CA or deploy to Vercel/Netlify with automatic HTTPS
npm run dev # start local dev server (shopify app dev)
npm run web # start local HTTPS admin page at https://localhost:3000
npm run deploy # deploy extension to Shopify CDN
npm run build # build without deploying
npm run cf-deploy # deploy admin page to Cloudflare Pages (production)Nurkamol Vakhidov
| 🌐 Website | nurkamol.com |
| 🐙 GitHub | github.com/nurkamol |
| nurkamol@gmail.com |