# PromoSync Documentation > PromoSync is a Shopify app that syncs promotional products, inventory, and pricing from PromoStandards suppliers to your Shopify store. - [Home](https://promosync-docs.psrestful.com): Learn how PromoSync syncs promotional products, inventory, and pricing from PromoStandards suppliers to your Shopify store. Guides, settings, and API reference. # PromoSync Documentation Welcome to the official documentation for **PromoSync** - the Shopify app that seamlessly syncs promotional products from PromoStandards-compliant suppliers directly to your Shopify store. ## What is PromoSync? PromoSync connects your Shopify store to the promotional products industry through the [PSRESTful API](https://api.psrestful.com/docs). It enables you to: - **Import Products** from 100+ promotional product suppliers - **Sync Inventory** with configurable scheduling - **Sync Prices** with configurable scheduling - **Customize Pricing** with advanced markup and discount strategies - **Optimize for Google Shopping** with 20+ merchant center fields ## Quick Start 1. [Install PromoSync](/installation) from the Shopify App Store 2. Complete the [three-step onboarding wizard](/installation#onboarding-wizard): paste your PSRESTful API key, add a notification contact, and import two sample products so you can see a finished PromoSync listing before importing your full catalog 3. [Configure your settings](/settings) for pricing, inventory, and product imports 4. [Import your first products](/guides/importing-products) ## App Overview ## Key Features | Feature | Description | |---------|-------------| | **Smart Product Import** | Import products with customizable title formats, SKU strategies, and variant grouping | | **Automated Inventory Sync** | Keep inventory levels current with configurable update schedules | | **Advanced Pricing** | MQ, HUNDREDS, MAP pricing plus custom markup/discount rules | | **Google Merchant Center** | Full integration with 20+ Google Shopping metafields | | **Bulk Admin Actions** | Manage products efficiently with 10 bulk operations | | **70+ Metafields** | Rich product data for customized storefront experiences | ## Documentation Sections ### [Installation](/installation) Get started with installing PromoSync and connecting your API credentials. ### [Features](/features) Explore the complete list of PromoSync capabilities. ### [Settings](/settings) Configure pricing, inventory, and product import options. ### [Guides](/guides) Step-by-step tutorials for common tasks. ### [Metafields](/metafields) Reference documentation for all product and variant metafields. ### [Advanced](/advanced) Advanced features including custom pricing and multi-location inventory. ## Need Help? - **PSRESTful API Reference:** [api.psrestful.com/docs](https://api.psrestful.com/docs) - **PSRESTful Website:** [psrestful.com](https://psrestful.com) - **Support:** Contact us through the Shopify App Store listing ## Video Overview Watch our introduction video to see PromoSync in action: ## Getting Started - [Installation](https://promosync-docs.psrestful.com/installation): Install PromoSync from the Shopify App Store, walk through the three-step onboarding wizard, and import your first sample products. # Installation This guide walks you through installing PromoSync and connecting it to your PSRESTful API account. ## Prerequisites Before installing PromoSync, you'll need: 1. **A Shopify Store** - PromoSync works with all Shopify plans 2. **A PSRESTful API Account** - Sign up at [psrestful.com](https://psrestful.com/sign-up) if you don't have one 3. **API Key** - Generate an API key from your [PSRESTful](https://psrestful.com/dashboard/api-keys/private/) dashboard Your PSRESTful plan determines the number of API calls available to your account. ## Installing from Shopify App Store 1. Visit the [PromoSync listing](https://apps.shopify.com/promosync) on the Shopify App Store 2. Click **Add app** to begin installation 3. Review the permissions and click **Install app** 4. You'll be redirected to the PromoSync onboarding wizard ## Onboarding Wizard On first launch, the dashboard runs a three-step wizard that gets you from a fresh install to a working catalog. Each step gates the next, and the dashboard remembers where you left off if you close the tab. ### Step 1 of 3: Connect PSRESTful PromoSync needs a PSRESTful API key to read supplier catalogs, pricing, and inventory. 1. Log in to your [PSRESTful dashboard](https://psrestful.com/dashboard/) 2. Open the [API Keys](https://psrestful.com/dashboard/api-keys/private/) section 3. Click **Generate New Key** or copy an existing one 4. Paste it into the PromoSync wizard and submit If the key is rejected, double-check for stray whitespace, confirm your PSRESTful subscription is active, and confirm the key has not been revoked. ### Step 2 of 3: Notification contacts Tell PromoSync where to send async job summaries (inventory syncs, price updates, onboarding results, etc.). 1. Enter at least one email address that should receive notifications 2. Submit to advance You can add or remove notification contacts later from **Settings**. ### Step 3 of 3: Try it, import your first products PromoSync adds two sample products from HIT Promo to your Shopify store so you can see how a finished PromoSync listing looks before importing your full catalog. The two products vary by environment but are picked to show variants, pricing tiers, and metafields populated automatically. 1. Click **Import these samples** 2. The wizard advances to an "importing" screen that polls for completion 3. When the products land in Shopify, you'll see a celebration screen with links to the new listings 4. Click **Continue to dashboard** to exit the wizard The demo products are real supplier records, so they include live pricing tiers, variants, and metafields. Feel free to delete them after you've inspected them; your dashboard will revert to the regular Overview the next time you load it. ## Initial Configuration After connecting your API key, configure these essential settings: ### Pricing Strategy Choose how pricing is calculated for imported products: | Strategy | Description | |----------|-------------| | **MQ** | Minimum Quantity pricing - uses the lowest tier | | **HUNDREDS** | Price per 100 units | | **MAP** | Manufacturer's Advertised Price | | **MDP** | Apply your own markup/discount rules | See [Pricing Settings](/settings/pricing) for detailed configuration. If your pricing strategy cannot be mapped to one of the options above, [contact us](https://psrestful.com/contact-us) to discuss custom pricing logic. A one-time fee applies for custom implementations. ### Inventory Location Select which Shopify location should receive inventory updates: 1. Go to **Settings** > **Inventory** 2. Select your primary location from the dropdown 3. Optionally enable multi-location sync for advanced setups Multi-location sync is currently in Beta. See [Inventory Settings](/settings/inventory) for more details. ### Product Import Defaults Configure default settings for product imports: - **Title Format** - How product titles are generated - **SKU Strategy** - How SKUs are created for variants - **Variant Grouping** - How product variants are organized See [Product Import Settings](/settings/product-import) for all options. ## Verifying Your Setup To verify PromoSync is properly configured: 1. Navigate to **Products** in PromoSync 2. Click **Import Products** 3. Search for any product (e.g., "t-shirt") 4. Preview a product to verify: - Images load correctly - Pricing displays properly - Inventory shows available If you see product data, your setup is complete. ## Troubleshooting ### API Key Issues | Problem | Solution | |---------|----------| | "Invalid API key" | Verify the key was copied correctly without extra spaces | | "API key expired" | Generate a new key from your PSRESTful dashboard | | "Rate limit exceeded" | Wait a few minutes before retrying | | "Unauthorized" | Check your PSRESTful subscription is active | ### Connection Issues If PromoSync cannot connect to the PSRESTful API: 1. Check your internet connection 2. Verify the API key is valid in your PSRESTful dashboard 3. Ensure your PSRESTful subscription is active 4. Try refreshing the page and reconnecting ### Getting Help If you continue to experience issues: - Review the [PSRESTful API documentation](https://docs.psrestful.com) - Review the [PSRESTful API reference](https://api.psrestful.com/docs) - Contact support through the Shopify App Store listing - Contact [PSRESTful support](https://psrestful.com/contact-us) ## Next Steps Once installation is complete: 1. **[Configure your settings](/settings)** - Set up pricing, inventory, and import preferences 2. **[Import your first products](/guides/importing-products)** - Learn how to find and import products 3. **[Explore features](/features)** - Discover all PromoSync capabilities - [Features](https://promosync-docs.psrestful.com/features): Explore PromoSync's product import, automated inventory sync, advanced pricing, Google Merchant integration, bulk actions, and 70+ metafields for Shopify. # Features PromoSync provides a comprehensive set of features for importing and managing promotional products in your Shopify store. ## At a Glance - **Onboarding Wizard** — Three-step in-app setup that takes new merchants from a fresh install to a working catalog: PSRESTful key, notification contacts, and two sample products imported on your behalf so you can see a finished listing immediately - **Product Import** — Search 100+ PromoStandards suppliers, preview products, and import with customizable titles, SKU strategies, and variant grouping - **Pricing** — Multiple pricing strategies (MQ, Hundreds, MAP) plus advanced Markup/Discount Pricing (MDP) with per-supplier and per-brand rules - **Inventory Sync** — Automatic inventory updates on configurable schedules, with collection-based frequency control - **Bulk Actions** — Update inventory, update prices, refresh metafields, [add new variants](/guides/bulk-actions/add-new-variants), [apply decoration rules](/guides/bulk-actions/apply-default-decorations), [enrich location decorations](/guides/bulk-actions/enrich-location-decorations), and register existing products directly from Shopify's product list - **SEO-Friendly Images** — Automatic image file renaming and alt text optimized for search engines (free) - **Google Merchant Center** — Auto-populate 20+ Google Shopping metafields — brand, GTIN, MPN, and more (Trial, Business, and Enterprise plans) - **Shopify Taxonomy Category** — Map PromoStandards classifications to Shopify's built-in product taxonomy so products land in the right category at import (Trial, Business, and Enterprise plans) - **Color Swatches** — Render native Shopify color swatches by linking each color variant to a metaobject populated from PromoStandards (Trial, Business, and Enterprise plans) - **[Per-Variant Media Gallery](/guides/variant-media-gallery)** — Attach a color-matched, multi-image gallery (front, back, side, lifestyle) to each variant from the supplier's media feed, so shoppers see every angle of the color they pick (Trial, Business, and Enterprise plans) - **Metafields** — 70+ product-level and variant-level metafields for pricing, media, specs, and compliance data - **Default Location Decorations** — Tag-based rules that automatically assign decoration locations and methods to products - **Theme App Extensions** — No-code blocks for product pages: - [Volume Pricing](/guides/theme-extensions/volume-pricing) — Display quantity-based price breaks - [Minimum Quantity](/guides/theme-extensions/minimum-quantity) — Enforce per-product minimum order quantities - [Decoration Selector](/guides/theme-extensions/decoration-selector) — Let shoppers choose decoration location and method To receive customer-specific pricing from a supplier, you must configure your own PromoStandards credentials for that supplier. While using PSRESTful/Gallardo Solutions default credentials, you will only receive standard (non-customer) pricing. ## Onboarding Wizard On first launch, the dashboard runs a three-step wizard that gets new merchants productive in minutes: 1. **Connect PSRESTful** — paste your private API key; the wizard validates it before letting you continue. 2. **Notification contacts** — add at least one email address for async-job summaries. 3. **Try it: import your first products** — PromoSync imports two sample HIT Promo products into your store so you can inspect a finished PromoSync listing (variants, pricing tiers, metafields) before importing your full catalog. The wizard remembers where you left off if you close the tab, and gates the regular dashboard until all three steps are complete. See the [First-time Setup guide](/guides/first-time-setup) for the full walkthrough. ## Product Import ### Smart Product Search - Search across 100+ PromoStandards suppliers - Filter by supplier, brand, category, or product type - Preview products before importing - View pricing tiers and inventory levels ### Customizable Product Titles Choose from multiple title format strategies: | Strategy | Description | |----------|-------------| | **Use Supplier Title** | Use the product title exactly as provided by the supplier | | **Strip Product ID from Title** | Remove the product ID/code from the supplier title | | **Reorder with Brand and Code** | Reformat the title with the brand name and product code | ### SKU Strategies Generate SKUs using 7 different strategies: | Strategy | Format | |----------|--------| | **Use Supplier SKU/Part ID** | Supplier's original SKU or part ID | | **ProductId-ColorName.Size** | `ABC123-Black.Large` | | **ProductId-StandardColorName.Size** | `ABC123-BLK.LG` | | **ProductId:ColorName-Size** | `ABC123:Black-Large` | | **ProductId:ColorName.Size** | `ABC123:Black.Large` | | **ProductId:StandardColorName-Size** | `ABC123:BLK-Large` | | **ProductId-PartId** | `ABC123-PARTID456` | Need SKU formats beyond what PromoStandards provides? You can use apps like Matrixify to bulk-update SKUs and other product data from supplier files. ### Variant Grouping Control how product variants are organized: - **Single** - All variants in one product(*Preferred now with the maximum variants limit at 2048*) - **By Color** - Separate products per color family - **Split Limit** - Split when variants exceed a threshold(preventing Shopify Max Variant Limit, not needed any more) ## Pricing ### Standard Pricing Strategies | Strategy | Description | |----------|-------------| | **MQ (Minimum Quantity)** | Uses the lowest quantity tier price | | **HUNDREDS** | Calculates price per 100 units | | **MAP** | Uses Manufacturer's Advertised Price | | **MDP** | Uses Advanced Pricing (MDP) | ### Advanced Pricing (MDP) Markup/Discount Pricing allows custom rules per supplier or brand: Features: - Percentage markup/discount - Per-supplier pricing rules - Per-brand pricing rules See [Custom Pricing](/advanced/custom-pricing) for setup details. ## Inventory Management ### Inventory Sync - Automatic inventory updates on configurable schedules - Manual sync option for immediate updates ### Collection-Based Scheduling Set different update frequencies per collection: - High-demand products: twice a day updates - Standard products: daily updates - Archive products: weekly updates ## SEO-Friendly Images PromoSync automatically renames product image files and generates descriptive alt text optimized for search engines — included free with all plans. ## Google Merchant Center Integration Google Merchant Center metafields are populated automatically on the Trial, Business, and Enterprise PSRESTful plans. Shops on lower plans can still buy this feature individually by enabling it from PromoSync settings. ### Supported Fields PromoSync populates 20+ Google Shopping metafields: | Field | Metafield | |-------|-----------| | Brand | `google.custom.brand` | | GTIN | `google.custom.gtin` | | MPN | `google.custom.mpn` | | Condition | `google.custom.condition` | | Product Category | `google.custom.product_category` | | Age Group | `google.custom.age_group` | | Gender | `google.custom.gender` | | Material | `google.custom.material` | | Pattern | `google.custom.pattern` | | Size | `google.custom.size` | | Size Type | `google.custom.size_type` | | Color | `google.custom.color` | See [Google Merchant Fields](/metafields/google-merchant) for the complete list. ## Bulk Admin Actions Manage multiple products efficiently from Shopify's product list: 1. **[Update Inventory](/guides/bulk-actions/update-inventory)** — Update stock levels from suppliers 2. **[Update Prices](/guides/bulk-actions/update-prices)** — Recalculate prices with current pricing rules 3. **[Update Metafields](/guides/bulk-actions/update-metafields)** — Rebuild the full PromoSync metafield bundle on selected products 4. **[Add New Variants](/guides/bulk-actions/add-new-variants)** — Add colors and sizes a supplier added after import, without touching existing variants 5. **Disable Price Updates** — Prevent automatic price syncing for selected products 6. **Enable Price Updates** — Re-enable automatic price syncing for selected products 7. **[Apply Default Location Decorations](/guides/bulk-actions/apply-default-decorations)** — Apply decoration location rules to selected products 8. **[Enrich Location Decorations](/guides/bulk-actions/enrich-location-decorations)** — Wire each decoration to its Setup Fee and Run Charge product for correct checkout charges 9. **[Onboard to PromoSync](/guides/bulk-actions/onboard-to-promosync)** — Attach a supplier link to hand-created products so other bulk actions can run on them 10. **[Link to PromoSync](/guides/bulk-actions/link-to-promosync)** — Register existing/duplicated products so syncs skip redundant API calls (formerly Sync Duplicate Products) See [Bulk Actions Guide](/guides/bulk-actions) for details. ## Storefront Widgets ### Tier Pricing Block Display quantity-based pricing tiers on product pages. Tier pricing is stored at the **variant level** in `part_price_array`: ```liquid {% raw %}{% assign tiers = variant.metafields.psrestful.part_price_array.value %} {% if tiers %} {% for tier in tiers %} {{ tier.quantityMin }}+ {{ tier.price | divided_by: 100.0 | money }} {% endfor %} {% endif %}{% endraw %} ``` **Note:** Prices are stored as integers in cents (e.g., `1250` = `$12.50`). ### Location Decorations Block Show available decoration methods and locations: ```liquid {% raw %}{% assign locations = product.metafields.psrestful.location_decorations.value %} {% if locations %} {% for location in locations %} {{ location.locationName }} {% endfor %} {% endif %}{% endraw %} ``` See [Storefront Widgets Guide](/guides/storefront-widgets) for more examples. ## Metafields ### Product-Level Metafields 70+ metafields including: - Basic product info (description, category, brand) - Pricing data (tier pricing, MAP) - Media (images, videos, documents) - Specifications (dimensions, weight, materials) - Compliance (safety warnings, certifications) ### Variant-Level Metafields Variant-specific data including: - Color and size information - UPC/GTIN barcodes - Inventory quantities - Supplier-specific attributes See [Metafields Reference](/metafields) for the complete list. ## Default Location Decorations Configure decoration rules to automatically assign decoration locations based on product tags: Click **Add Rule** or **Edit** to configure a rule: Automatically assign decoration locations based on product tags: ```json { "rules": [ { "tag": "apparel", "default_location": "Left Chest", "default_method": "Embroidery" }, { "tag": "drinkware", "default_location": "Wrap", "default_method": "Full Color" } ] } ``` ## Manufacturer Management Map Shopify vendors to PromoStandards supplier codes: | Shopify Vendor | PromoStandards Code | |---------------|---------------------| | SanMar | SanMar | | S&S Activewear | SS | | Hit Promotional | HIT | See [Manufacturer Mapping](/advanced/manufacturer-mapping) for configuration. ## Sales Priority Sorting (Premium) Order variants by sales performance: - Best-selling variants appear first - Automatic reordering based on sales data - Configurable sorting rules ## Feature Comparison | Feature | Basic | Professional | Enterprise | |---------|-------|--------------|------------| | Product Import | ✓ | ✓ | ✓ | | Inventory Sync | ✓ | ✓ | ✓ | | Standard Pricing | ✓ | ✓ | ✓ | | Custom Pricing (MDP) | - | ✓ | ✓ | | Multi-Location | - | ✓ | ✓ | | Google Merchant | - | ✓ | ✓ | | Sales Priority | - | - | ✓ | | Dedicated Support | - | - | ✓ | - [What is PromoStandards?](https://promosync-docs.psrestful.com/what-is-promostandards): Learn what PromoStandards is, how the promotional products industry uses it, and how PromoSync brings it into your Shopify store via the PSRESTful API. # What is PromoStandards? **PromoStandards** is an open data-exchange standard used by the promotional products industry. It defines a common set of web-service endpoints that suppliers, distributors, and decorators use to share product data, inventory levels, order status, and more — in real time. ## Who Uses PromoStandards? The promotional products industry includes thousands of suppliers (manufacturers) and distributors who sell branded merchandise — think custom T-shirts, promotional pens, trade-show giveaways, and corporate apparel. Organizations like **PPAI** and **ASI** support the standard, and over 100 suppliers expose PromoStandards-compliant endpoints today. ## What Services Does It Cover? PromoStandards defines several services, each covering a different part of the product lifecycle: | Service | What It Provides | |---------|-----------------| | **Product Data** | Full product catalog — names, descriptions, images, colors, sizes, pricing tiers | | **Inventory** | Real-time stock levels by warehouse and variant | | **Order Status** | Tracking and status updates for placed orders | | **Order Shipment Notification** | Shipment details including carrier and tracking numbers | | **Purchase Order** | Programmatic order placement | | **Product Pricing & Configuration** | Decoration methods, locations, charges, and imprint areas | | **Invoice** | Electronic invoicing for completed orders | ## The Problem: SOAP, XML, and Complexity PromoStandards services are built on **SOAP/XML** — a protocol that's powerful but difficult to work with in modern web applications. Each supplier hosts their own endpoints with slightly different implementations. Integrating directly means writing SOAP clients, handling XML parsing, managing authentication per supplier, and dealing with inconsistencies across implementations. ## How PSRESTful Solves This **PSRESTful** is a RESTful proxy layer that sits in front of PromoStandards. It translates every SOAP/XML call into a clean **REST/JSON** API. Instead of writing SOAP clients for each supplier, you make standard HTTP requests to a single API and get back JSON. - One API key, one base URL, 100+ suppliers - REST/JSON instead of SOAP/XML - Consistent response formats across all suppliers - Hosted and maintained — no infrastructure to manage ## How PromoSync Brings It Into Shopify **PromoSync** is a Shopify app built on top of PSRESTful. It automates the flow of data from PromoStandards suppliers into your Shopify store: - **Product Import** — Search and import products from any supported supplier directly into Shopify - **Inventory Sync** — Automatically keep Shopify stock levels in sync with supplier warehouses - **Pricing** — Apply global markups, per-supplier discounts, or custom pricing rules - **Metafields** — Populate 70+ Shopify metafields with rich product data for storefront displays and Google Merchant Center - **Theme Extensions** — Add volume pricing, minimum quantity enforcement, and decoration selectors to your product pages with no code If you're a promotional products distributor using Shopify, PromoSync eliminates the manual work of keeping product data, inventory, and pricing up to date across suppliers. ## Next Steps - [Install PromoSync](/installation) and connect your PSRESTful API key - [Import your first products](/guides/importing-products) from a supplier - Explore the [full feature set](/features) - [Why PromoSync?](https://promosync-docs.psrestful.com/why-promosync): Discover why promotional products distributors choose PromoSync to automate product imports, inventory sync, and pricing from 100+ PromoStandards suppliers to Shopify. # Why PromoSync? Promotional products distributors manage thousands of SKUs across dozens of suppliers. Keeping product data, inventory, and pricing accurate in Shopify is time-consuming and error-prone when done manually. PromoSync automates it. ## The Problem Without automation, distributors face: - **Manual product entry** — copying product names, descriptions, images, colors, and sizes from supplier catalogs into Shopify, one product at a time - **Stale inventory** — stock levels change constantly across supplier warehouses; manual updates can't keep up, leading to oversells and backorders - **Pricing drift** — supplier cost changes, MAP updates, and tier pricing adjustments require re-calculating and updating every affected product - **Missing data** — Shopify products lack the rich metadata (decoration options, compliance info, country of origin) that buyers and Google Shopping expect ## The Solution PromoSync connects your Shopify store to **100+ PromoStandards suppliers** through the PSRESTful API. It handles the entire data pipeline automatically: ### Product Import Search any supplier's catalog, preview products, and import them into Shopify with a few clicks. PromoSync creates products with proper titles, descriptions, images, variants (color/size), and 70+ metafields — ready to sell. ### Automated Inventory Sync PromoSync checks supplier stock levels on a schedule you control and updates your Shopify inventory automatically. Support for multi-location inventory means you can map supplier warehouses to Shopify locations for accurate fulfillment routing. ### Advanced Pricing Go beyond simple markups. PromoSync supports: - Global markup percentages - Per-supplier and per-brand pricing rules (MDP) - Quantity-based tier pricing - MAP enforcement ### Google Merchant Integration PromoSync populates Google Merchant Center metafields (GTIN, MPN, brand, condition, product category) so your products are ready for Google Shopping campaigns without extra data entry. ### Theme App Extensions Add volume pricing tables, minimum quantity enforcement, and decoration selectors to your product pages using drag-and-drop app blocks — no code required. Works with any Online Store 2.0 theme. ### Bulk Operations Sync inventory, update prices, or refresh product data across your entire catalog with bulk actions. No need to update products one at a time. ### Frictionless Onboarding A three-step in-app wizard takes new merchants from a fresh install to a working catalog: paste your PSRESTful key, add a notification contact, and import two sample products to see a finished PromoSync listing before importing your full catalog. See the [First-time Setup guide](/guides/first-time-setup) for what to expect. ## Who Is PromoSync For? PromoSync is built for **promotional products distributors** who sell on Shopify. Whether you carry products from a handful of suppliers or hundreds, PromoSync scales with your catalog. Typical users include: - Distributors building B2B or B2C Shopify stores for branded merchandise - Companies migrating from legacy platforms to Shopify - Businesses that need real-time inventory accuracy across multiple suppliers ## Get Started 1. [Install PromoSync](/installation) from the Shopify App Store 2. Walk through the [three-step onboarding wizard](/guides/first-time-setup): paste your PSRESTful key, add a notification contact, and import two sample products 3. [Import your first products](/guides/importing-products) (or [connect existing ones](/guides/connecting-existing-products)) 4. [Configure pricing](/settings/pricing) and [inventory sync](/settings/inventory) 5. Explore [all features](/features) ## Settings - [Settings Overview](https://promosync-docs.psrestful.com/settings): Configure pricing, inventory sync, and product import settings to control how PromoSync syncs promotional products to your Shopify store. # Settings Overview PromoSync provides extensive configuration options to customize how products are imported, priced, and synchronized with your Shopify store. ## Accessing Settings 1. Open your Shopify admin 2. Navigate to **Apps** > **PromoSync** 3. Click **Settings** in the navigation ## Settings Categories ### [Pricing Configuration](/settings/pricing) Control how product prices are calculated: - Pricing strategies (MQ, HUNDREDS, MAP) - Markup and discount rules - Custom(if your logic doesn't map with one of the above) ### [Inventory & Scheduling](/settings/inventory) Configure inventory synchronization: - Sync frequency and scheduling - Location assignments - Collection-based scheduling ### [Product Import Settings](/settings/product-import) Customize product imports: - Title format templates - SKU generation strategies - Variant grouping options - Tag management - Image import settings ### [Decoration Defaults](/settings/decoration-defaults) Set the fallback decoration price for blanks: - Per (method, kind) default pricing class - Auto-pick the most popular class, or pin your own - Used for SanMar / S&S Activewear-style blanks ## Quick Settings Reference | Setting | Location | Default | |---------|----------|---------| | Pricing Strategy | Pricing | MQ | | Default Markup | Pricing | 0% | | Sync Frequency | Inventory | On Demand | | Primary Location | Inventory | Default | | Title Format | Product Import | Product Name | | SKU Strategy | Product Import | part_id | | Variant Grouping | Product Import | single | ## Settings Best Practices ### Before First Import 1. Configure your pricing strategy and markup 2. Set your primary inventory location 3. Choose your preferred title and SKU formats 4. Test with a single product before bulk imports ### Ongoing Maintenance - Review pricing settings when supplier costs change - Adjust sync frequency based on sales volume - Update title formats if SEO requirements change - Monitor inventory sync logs for issues ## Common Configurations ### Distributor Setup For promotional product distributors: ``` Pricing: MQ with 40% markup Inventory: Once a Day sync, single location Title: Brand + Product Name SKU: part_id_color_size ``` ### Decorator Setup For decoration/printing businesses: ``` Pricing: HUNDREDS with 25% markup Inventory: Twice a sync for popular items Title: Product Name + Part ID SKU: part_id ``` ## Notification Contacts Manage email contacts for receiving import notifications and error reports: ## Need Help? - [Pricing Configuration Guide](/settings/pricing) - [Inventory Setup Guide](/settings/inventory) - [Product Import Settings](/settings/product-import) - [PSRESTful API Documentation](https://docs.psrestful.com) - [PSRESTful API Reference](https://api.psrestful.com/docs) - [Pricing Configuration](https://promosync-docs.psrestful.com/settings/pricing): Set global markup percentages, choose pricing tiers, and configure MAP enforcement for promotional products synced with PromoSync. # Pricing Configuration PromoSync offers flexible pricing options to match your business model. This guide covers all pricing strategies and configuration options. ## Pricing Strategies ### MQ (Minimum Quantity) Uses the lowest quantity tier price from the supplier's pricing table. **Best for:** Distributors selling to end consumers or businesses ordering small quantities. ```json // Example supplier pricing table [ {"minQuantity": 24, "price": 1250}, {"minQuantity": 48, "price": 1100}, {"minQuantity": 144, "price": 950}, {"minQuantity": 288, "price": 825} ] // MQ strategy selects: $12.50 (24 unit tier) ``` ### HUNDREDS Calculates price per 100 units, useful for high-volume pricing. **Best for:** Businesses that quote prices per hundred or per thousand units. ```json // Same pricing table // HUNDREDS calculation: // Uses 144-tier: $9.50/unit × 100 = $950.00 per hundred ``` ### MAP (Manufacturer's Advertised Price) Uses the manufacturer's suggested retail price when available. For now, we are only processing SanMar's MAP pricing. ## Setting Your Default Strategy 1. Navigate to **Settings** > **Pricing** 2. Select your **Default Pricing Strategy** from the dropdown 3. Click **Save** The default strategy applies to all new product imports unless overridden. ## Markup and Discount Configuration Manage your product pricing by adding markup or discount prices based on suppliers and brands. ### Default Markup Set a default markup that applies to all products: | Setting | Description | |---------|-------------| | **Default Base** | The price base to apply markup on (Cost, List, or MSRP) | | **Default %** | The percentage markup or discount | ### Supplier/Brand-Specific Rules Create custom pricing rules for specific supplier and brand combinations. Click **+ New Price** to add a rule: | Field | Description | |-------|-------------| | **Supplier** | The supplier this rule applies to | | **Brand** | The brand within the supplier (or "All Brands") | | **Base** | Price base: Cost, List, or MSRP | | **Percentage** | Markup (positive) or discount (negative) percentage | ### Price Base Options | Base | Description | |------|-------------| | **Cost** | Supplier's cost price | | **List** | Supplier's list price | | **MSRP** | Manufacturer's Suggested Retail Price | ### Example Rules | Supplier | Brand | Price | Result | |----------|-------|-------|--------| | S&S Activewear | A4 | 25.00% on Cost | 25% markup on cost price | | SanMar | Brooks Brothers | -10.00% on MSRP | 10% discount from MSRP | | Dunbrooke | All Brands | -10.00% on List | 10% discount from list price | ### Prefill SanMar Click **Prefill SanMar** to automatically populate recommended pricing rules for all SanMar brands based on their MSRP pricing structure. ## Tier Pricing Display Tier pricing data is stored at the **variant level** in the `part_price_array` metafield, following the PromoStandards specification. ```json // Stored in variant.metafields.psrestful.part_price_array [ {"quantityMin": 24, "price": 1250}, {"quantityMin": 48, "price": 1100}, {"quantityMin": 144, "price": 950}, {"quantityMin": 288, "price": 825} ] ``` **Note:** Prices are stored as integers in cents for fast and exact calculations. For example, `1250` means `$12.50`. Display in your theme: ```liquid {% raw %}{% assign tiers = variant.metafields.psrestful.part_price_array.value %} {% if tiers %} {% for tier in tiers %} {% endfor %}
Quantity Price Each
{{ tier.quantityMin }}+ {{ tier.price | divided_by: 100.0 | money }}
{% endif %}{% endraw %} ``` See [Variant Metafields](/metafields/variant#part_price_array) for more details on the `part_price_array` structure. ## Currency Settings | Setting | Description | |---------|-------------| | **Currency** | Your store's currency(PromoStandards offers USD, CAD) | Note: PromoSync uses Shopify's currency settings for display. Supplier prices are typically in USD. ## Advanced: Per-Supplier Pricing For supplier-specific pricing rules, see [Custom Pricing (MDP)](/advanced/custom-pricing). ## Troubleshooting ### Prices Not Updating 1. Check that the product is still syncing (not removed from sync) 2. Verify the supplier has updated pricing 3. Manually trigger a sync from the product page 4. Check for pricing errors in the sync log ### Incorrect Pricing 1. Verify your pricing strategy selection 2. Check markup/discount configuration 3. Review rounding settings 4. Compare with supplier's actual pricing table ### MAP Pricing Unavailable Not all suppliers provide MAP pricing. If MAP is selected but unavailable: - PromoSync falls back to MQ pricing - A warning is logged in the sync report - [Update Prices Schedule](https://promosync-docs.psrestful.com/settings/update-prices): How the Update Prices action works, why there is no "Twice a Day" option, and why Once a Week is the right default for most stores. # Update Prices The **Update Prices** action recalculates each product's pricing using the strategy and rules you configured in [Pricing Settings](/settings/pricing), then writes the new values back to Shopify. This page covers when to run it, how to schedule it, and why we deliberately don't offer the same frequencies as Update Inventory. ## What Update Prices Does For every product in scope, Update Prices: 1. Fetches the supplier's current pricing tiers from PSRESTful. 2. Applies your default strategy (MQ, HUNDREDS, or MAP) and any per-supplier or per-brand markup/discount rules. 3. Writes the resulting price (and `part_price_array` tier metafield) back to each Shopify variant. It runs as a background job and emails a summary when it finishes. ## Schedule Options You can pick one of three frequencies from the **Update Prices** card on the dashboard (click the gear icon to edit): | Frequency | Behavior | Best for | |-----------|----------|----------| | **On Demand** | Never runs automatically. Use **Run Now** or the bulk action when you need it. | Stores that re-price manually after promos, supplier announcements, or strategy changes. | | **Once a Day** | Runs once every 24 hours. | High-velocity catalogs where supplier pricing changes are frequent. | | **Once a Week** | Runs once every 7 days. **Recommended for most stores.** | Catalogs where supplier pricing is stable. | ## Why There Is No "Twice a Day" Update Inventory offers a **Twice a Day** option because stock levels move constantly: a popular SKU can go from in-stock to out-of-stock within hours, and an oversell is immediately painful for the merchant and the customer. Pricing does not behave that way. Suppliers typically change list pricing on a quarterly or annual basis, with the occasional mid-season adjustment. Syncing prices twice a day would burn through API quota without changing any actual values on 99% of runs, so we don't expose that frequency at all. ## Why "Once a Week" Is the Recommended Default Two reasons: **1. Pricing rarely changes.** For most catalogs, a weekly sync is more than enough to catch supplier updates before they go stale. Daily syncs are useful for stores that re-quote aggressively or run flash promotions tied to supplier list changes, but they are the exception. **2. Update Prices is expensive in API calls.** Refreshing pricing requires several PSRESTful calls per product (a pricing call, often a configuration call, plus a Shopify mutation for each variant). On a catalog of a few thousand products, that is tens of thousands of API calls per run, against both your Shopify rate limit and your PSRESTful daily/monthly quota. The PSRESTful [API Usage Calculator](https://psrestful.com/api-usage-calculator/) shows the exact call counts for a given catalog size and frequency. Plug in your numbers before switching to a more aggressive schedule, then compare against your plan's quota. If your supplier's pricing is genuinely stable, weekly is plenty. Save your API budget for inventory, where the data actually changes hour to hour. ## When to Use Run Now Even on a weekly schedule, a few situations call for an immediate sync: - You just changed your default pricing strategy (MQ → MAP, for example). - You added or edited a markup/discount rule in [Pricing Settings](/settings/pricing). - A supplier announced a price change you don't want to wait a week to apply. - You ran **Prefill SanMar** or another bulk pricing tool and want to verify the results immediately. For these one-off needs, **Run Now** is the right call. ## Updating Selected Products Only You don't have to re-price your whole catalog every time. From Shopify's product list, select the products you care about, open **More actions** > **Sync Using PSRESTful**, and pick **Update Prices**. The job runs only against the selected products, which keeps API usage low and is ideal for testing a new pricing rule on a small batch before rolling it out catalog-wide. See [Bulk Actions > Update Prices](/guides/bulk-actions#update-prices) for the full walkthrough. ## Update Prices vs. Update Inventory | | Update Inventory | Update Prices | |--|------------------|---------------| | What changes | Stock quantity per variant | Variant price + tier metafield | | Typical change cadence | Hourly | Quarterly | | Cost of being stale | High (overselling) | Low (until a supplier change ships) | | API calls per product | Few | Several | | Available frequencies | On Demand, Once a Day, Twice a Day, By Collection, By Inventory Location | On Demand, Once a Day, Once a Week | | Recommended default | Once a Day (or Twice a Day for fast movers) | Once a Week | ## Related - [Pricing Configuration](/settings/pricing) — strategies, markup/discount rules, MAP enforcement. - [Inventory & Scheduling](/settings/inventory) — companion settings for stock sync, including the higher-frequency options. - [Bulk Actions](/guides/bulk-actions) — how to run Update Prices on a selected subset of products. - [PSRESTful API Usage Calculator](https://psrestful.com/api-usage-calculator/) — model API consumption before picking a schedule. - [Inventory & Scheduling](https://promosync-docs.psrestful.com/settings/inventory): Control how often PromoSync syncs supplier stock levels to Shopify and configure automatic inventory scheduling for your store. # Inventory & Scheduling PromoSync keeps your Shopify inventory synchronized with supplier stock levels. This guide covers inventory settings and scheduling options. ## Inventory Overview PromoSync syncs inventory data from PromoStandards suppliers: - Real-time stock quantities - Warehouse availability - Estimated restock dates (when available) ## Location Configuration ### Primary Location Select the Shopify location that receives inventory updates: 1. Go to **Settings** > **Inventory** 2. Under **Primary Location**, select your location 3. Click **Save** ## Sync Scheduling ### Default Schedule Set your default inventory update frequency: | Frequency | Description | Best For | |-----------|-------------|----------| | **Once a Day** | Updates once per day | Standard inventory | | **Twice a Day** | Updates twice per day | Standard inventory | | **Weekly** | Updates once per week | Slow-moving items | | **On Demand** | Only when triggered | Use Button or Bulk Actions | ### Setting Default Schedule 1. Navigate to **Settings** > **Inventory** 2. Select **Default Sync Frequency** 3. Choose your preferred schedule 4. Click **Save** ## Collection-Based Scheduling Set different sync frequencies for different collections: ### Setting Collection Schedules 1. Go to **Settings** > **Inventory** 2. Scroll to **Collection-Based Scheduling** 3. Click **Add Rule** 4. Select a collection and frequency 5. Click **Save** ## Inventory Sync Settings ### Sync Options | Setting | Description | |---------|-------------| | **Track Inventory** | Enable inventory tracking in Shopify | | **Continue Selling** | Allow sales when out of stock | | **Update Zero Stock** | Set to 0 when supplier has no stock | ### Out of Stock Handling Configure what happens when products go out of stock: | Option | Behavior | |--------|----------| | **Set to Zero** | Inventory shows as 0 | | **Hide Product** | Product becomes unavailable | | **Tag Product** | Add "out-of-stock" tag | | **Keep Last Value** | Don't update (risky) | ## Manual Sync Operations ### Sync Individual Products 1. Navigate to the product in PromoSync 2. Click **Sync Inventory** 3. Wait for confirmation ### Bulk Sync 1. Select multiple products in the product list 2. Choose **Bulk Actions** > **Sync Inventory** 3. Confirm the operation ### Full Catalog Sync 1. Go to **Settings** > **Inventory** 2. Click **Sync All Products** 3. Note: This may take significant time for large catalogs ## Inventory Metafields PromoSync stores inventory metadata in metafields: ```json // product.metafields.psrestful.inventory_data { "lastSync": "2024-01-20T14:30:00Z", "supplierCode": "SanMar", "warehouses": [ {"location": "Dallas", "qty": 100}, {"location": "Phoenix", "qty": 75} ], "totalAvailable": 175, "restockDate": null } ``` Access in Liquid: ```liquid {% raw %}{% assign inv = product.metafields.psrestful.inventory_data.value %} {% if inv.totalAvailable > 0 %} In Stock ({{ inv.totalAvailable }} available) {% else %} Out of Stock {% endif %}{% endraw %} ``` ## Performance Considerations ### Sync Volume Limits | Plan | Max Products | Sync Frequency | |------|--------------|----------------| | Basic | 500 | Daily | | Professional | 5,000 | Hourly | | Enterprise | Unlimited | Real-time | ### Optimization Tips 1. **Use collection-based scheduling** - Sync popular items more frequently 2. **Set appropriate buffers** - Prevent overselling without over-holding 3. **Monitor sync logs** - Identify and fix errors promptly 4. **Stagger sync times** - Avoid syncing all products at once ## Troubleshooting ### Inventory Not Updating 1. Check sync schedule settings 2. Verify the product is in sync 3. Review sync logs for errors 4. Test with a manual sync ### Incorrect Quantities 1. Compare with supplier's live inventory 2. Check buffer settings 3. Verify location mapping 4. Look for recent sync errors ### Sync Taking Too Long 1. Reduce sync frequency for low-priority products 2. Use collection-based scheduling 3. Contact support if persistent - [Discontinued Products](https://promosync-docs.psrestful.com/settings/discontinued-products): How PromoSync tracks supplier product status, stops updating discontinued products, and optionally prefixes their titles in Shopify. # Discontinued Products PromoSync tracks each product's lifecycle status at the supplier and stores it on the product's `psrestful.status` metafield. For stores that opt in, a daily check keeps that value current, and PromoSync uses it to stop wasting updates on products that are no longer available. ## Product Status Values Every synced product carries one of four statuses: | Status | Meaning | |--------|---------| | **active** | Product is available and updated normally | | **draft** | Product is not yet released for sale | | **closeout** | Product is being sold off but still available | | **discontinued** | Product is no longer offered by the supplier | When the `psrestful.status` metafield is missing, PromoSync treats the product as **active**. See the [product metafield reference](/metafields/product#status) for the raw field details. ## How Status Is Detected Discontinued handling is **opt in** per store. When **Enable Discontinued Handling** is turned on, PromoSync runs an automatic status check once a day for that store: 1. It reads the current status for each of the store's synced products directly from PromoStandards (via PSRESTful). 2. Whenever the status differs from what is stored in Shopify, PromoSync writes the new value to the `psrestful.status` metafield. Stores that leave this setting off are skipped entirely, so they spend no extra API calls on the daily check. The check only looks at products that have been synced into PromoSync. Products that exist in your Shopify store but were never imported or linked through PromoSync are not part of this process (this is the same scope used by the inventory and price syncs). ### Enabling Discontinued Handling 1. Go to **Settings**. 2. Turn on **Enable Discontinued Handling**. 3. Click **Save**. ## What Happens to Discontinued Products Once a product's status is `discontinued`, PromoSync stops updating it: | Operation | Behavior for discontinued products | |-----------|------------------------------------| | **Inventory sync** | Skipped. Stock levels are left untouched. | | **Price sync** | Skipped. Prices are left untouched. | This prevents discontinued items from being reset to stale or zero values, and it keeps sync runs focused on products that are still for sale. The product itself is not deleted or hidden; you stay in control of whether to keep selling, archive, or remove it. ## Discontinued Title Prefix You can also have PromoSync visually flag discontinued products by adding a prefix to their title. This is a sub-option of discontinued handling: it only takes effect when **Enable Discontinued Handling** is on. When **Prefix Discontinued Titles** is enabled: - When a product becomes **discontinued**, PromoSync prepends `Discontinued - ` to its Shopify title (for example, `Clean Up Cap` becomes `Discontinued - Clean Up Cap`). - If that product later returns to a non-discontinued status, the `Discontinued - ` prefix is removed automatically. This setting is **off by default** and only controls the title prefix. Turning it off (while discontinued handling stays on) keeps the status detection and the inventory/price skip behavior, just without changing titles. ### Enabling the Title Prefix 1. Go to **Settings**. 2. Make sure **Enable Discontinued Handling** is on. 3. Turn on **Prefix Discontinued Titles**. 4. Click **Save**. ## Frequently Asked Questions **Why are only some of my products being checked?** PromoSync only checks products that are synced into PromoSync. If your Shopify store has more products than were checked, the extra products are not linked to PromoSync and are therefore outside the status check, the inventory sync, and the price sync. **Will a discontinued product be deleted from my store?** No. PromoSync only stops updating it and (optionally) prefixes its title. Removing or archiving the product is your decision. **What if a discontinued product becomes available again?** On the next status check PromoSync updates the `psrestful.status` metafield back to its new value, resumes inventory and price updates, and removes the `Discontinued - ` title prefix if the prefix setting is enabled. - [Product Import Settings](https://promosync-docs.psrestful.com/settings/product-import): Customize product titles, SKU formats, variant grouping, tags, and image handling when importing promotional products with PromoSync. # Product Import Settings Configure how products are created in Shopify when importing from suppliers. These settings affect product titles, SKUs, variant organization, tags, and images. ## Title Format ### Available Formats | Format | Example | |--------|---------| | **Product Name** | Unisex Jersey Tee | | **Product Name + Part ID** | Unisex Jersey Tee (3001) | | **Supplier + Product Name** | Bella+Canvas Unisex Jersey Tee | | **Brand + Product Name** | BELLA+CANVAS Unisex Jersey Tee | | **Part ID + Product Name** | 3001 - Unisex Jersey Tee | | **Custom** | Use placeholders | ### Custom Title Format Use placeholders to create custom title formats: | Placeholder | Description | Example Value | |-------------|-------------|---------------| | `{product_name}` | Product name | Unisex Jersey Tee | | `{part_id}` | Supplier part ID | 3001 | | `{brand}` | Brand name | BELLA+CANVAS | | `{supplier}` | Supplier name | Bella+Canvas | | `{category}` | Product category | T-Shirts | Example custom format: ``` {brand} {product_name} - Style {part_id} → BELLA+CANVAS Unisex Jersey Tee - Style 3001 ``` ### Setting Title Format 1. Navigate to **Settings** > **Product Import** 2. Select **Title Format** 3. Choose a preset or enter a custom format 4. Click **Save** ## SKU Strategies ### Available Strategies PromoSync offers 7 SKU generation strategies: | Strategy | Format | Example | |----------|--------|---------| | `part_id` | Part ID only | `3001` | | `part_id_color` | Part ID + Color | `3001-BLK` | | `part_id_size` | Part ID + Size | `3001-L` | | `part_id_color_size` | Full variant ID | `3001-BLK-L` | | `sku` | Supplier SKU | `BC3001BLK-L` | | `upc` | UPC/Barcode | `882932789234` | | `custom` | Custom format | `YOUR-3001-BLK-L` | ### Custom SKU Format Create custom SKU formats with placeholders: | Placeholder | Description | |-------------|-------------| | `{part_id}` | Supplier part ID | | `{color_code}` | Color abbreviation | | `{size_code}` | Size abbreviation | | `{upc}` | UPC barcode | | `{prefix}` | Your custom prefix | Example: ``` {prefix}-{part_id}-{color_code}-{size_code} → ABC-3001-BLK-L ``` ### SKU Configuration 1. Go to **Settings** > **Product Import** 2. Select **SKU Strategy** 3. For custom, enter your format and prefix 4. Click **Save** ## Variant Grouping Control how product variants are organized in Shopify. Products built from more than one part (kits, sets, a plush toy with apparel, a tumbler with a color-matched straw) can be imported as a single product with the correct option matrix. See [Configurable Products](/settings/configurable-products). ### Grouping Strategies | Strategy | Description | Use Case | |----------|-------------|----------| | **Single** | All variants in one product | Standard setup | | **By Color** | Separate product per color | Large variant counts | | **Split Limit** | Split when exceeding limit | Performance optimization | ### Single Grouping All variants appear as one product: ``` Product: Unisex Jersey Tee ├── Black / S ├── Black / M ├── Black / L ├── White / S ├── White / M ├── White / L └── (etc.) ``` ### By Color Grouping Creates separate products per color: ``` Product: Unisex Jersey Tee - Black ├── Black / S ├── Black / M └── Black / L Product: Unisex Jersey Tee - White ├── White / S ├── White / M └── White / L ``` ### Split Limit Grouping Splits products when variants exceed a threshold: ```json { "strategy": "split_limit", "limit": 100, "splitBy": "color" } ``` Shopify's variant limit is 100. Use this strategy for products with many combinations. ## Shopify Taxonomy Category PromoSync can map each imported product to Shopify's built-in product taxonomy so it lands in the right category for search, filtering, and Shop Pay. The category is derived from the supplier's PromoStandards classification data, so no manual taxonomy picking is required. Shopify Taxonomy Category mapping is populated automatically on the Trial, Business, and Enterprise PSRESTful plans. Shops on other plans can buy this feature individually by enabling it from PromoSync settings. When eligible, PromoSync sets the product's `category` (Shopify's `TaxonomyCategory` GID) at import time. If the supplier did not classify the product, or did not provide a Shopify-compatible classification, the field is left unset and Shopify falls back to its own auto-categorization. To upgrade your plan, visit [psrestful.com/pricing](https://psrestful.com/pricing/). ## Color Swatches PromoSync can render native Shopify color swatches by linking each color option to a `custom-color-swatch` metaobject populated from PromoStandards color data (label and hex). Themes that support metaobject-linked options will then display real swatches in the variant picker instead of plain text. Color Swatches are populated automatically on the Trial, Business, and Enterprise PSRESTful plans. Shops on other plans can buy this feature individually by enabling it from PromoSync settings. When the feature is active for a shop, PromoSync ensures the required metaobject and metafield definitions exist (creating them on first import if missing) and upserts one color metaobject per unique color encountered in the catalog. The color metaobject definition lives at `custom-color-swatch` with `label` and `hex_color` fields and is exposed to the storefront so Liquid can read it. Color swatches need the `write_metaobjects` and `write_metaobject_definitions` OAuth scopes. When a shop is promoted into a plan that grants the feature, PromoSync triggers the standard scope-change banner so the merchant can re-authorize before the next import runs. To upgrade your plan, visit [psrestful.com/pricing](https://psrestful.com/pricing/). ## Tag Management ### Automatic Tags PromoSync can automatically add tags based on product attributes: | Tag Type | Example Tags | |----------|--------------| | **Supplier** | `supplier:sanmar`, `supplier:ss` | | **Brand** | `brand:bella-canvas`, `brand:gildan` | | **Category** | `category:t-shirts`, `category:polos` | | **Color Family** | `color:black`, `color:blue`, `color:red` | | **Material** | `material:cotton`, `material:polyester` | ### Tag Configuration 1. Go to **Settings** > **Product Import** 2. Enable/disable each tag type 3. Configure tag format (with or without prefix) 4. Click **Save** ### Custom Tags Add custom tags to all imported products: ``` promostandards, synced, {supplier} ``` ## Vendor Settings ### Vendor Assignment Choose how the Shopify vendor field is populated: | Option | Value Assigned | |--------|----------------| | **Supplier Name** | SanMar | | **Brand Name** | BELLA+CANVAS | | **Custom Value** | Your Company | | **Leave Empty** | (blank) | ### Product Type Assignment Set the Shopify product type: | Option | Value Assigned | |--------|----------------| | **Category** | T-Shirts | | **Supplier Category** | Apparel > Tees | | **Custom Value** | Promotional Apparel | ## Image Import Settings ### Image Options | Setting | Description | |---------|-------------| | **Import Images** | Enable/disable image import | | **Primary Image** | Which image is set as featured | | **Image Quality** | Standard or high resolution | | **Max Images** | Limit images per product | ### Image Order Configure the order of imported images: ```json { "imageOrder": [ "primary", "front", "back", "detail", "lifestyle" ] } ``` ### Image Alt Text Automatically generate alt text: ``` {product_name} - {color} - View {position} → Unisex Jersey Tee - Black - View 1 ``` ## Variant Options ### Option Names Customize variant option labels: | Default | Custom | |---------|--------| | Color | Garment Color | | Size | Garment Size | | Style | Product Style | ### Option Order Set the order of variant options: ```json { "optionOrder": ["Color", "Size"], "colorFirst": true } ``` ### Size Normalization Normalize size values across suppliers: | Supplier Value | Normalized | |---------------|------------| | SM | S | | MED | M | | LG | L | | XLG | XL | | 2XL | 2XL | | 2X | 2XL | ## Description Settings ### Description Format Choose what to include in product descriptions: | Content | Include | |---------|---------| | Product Description | ✓ | | Features List | ✓ | | Specifications | Optional | | Size Chart | Optional | | Care Instructions | Optional | ### HTML Formatting Enable rich HTML in descriptions: ```html

{{ description }}

Features

``` ## Weight and Dimensions ### Unit Settings | Setting | Options | |---------|---------| | **Weight Unit** | lb, kg, oz, g | | **Dimension Unit** | in, cm | ### Default Values Set defaults when supplier data is missing: ```json { "defaultWeight": 0.5, "defaultWeightUnit": "lb", "requireWeight": false } ``` ## Import Preview Before importing, preview how products will appear: ```json // Import preview { "title": "BELLA+CANVAS Unisex Jersey Tee - Style 3001", "vendor": "Bella+Canvas", "productType": "T-Shirts", "tags": ["supplier:bella-canvas", "brand:bella-canvas", "category:t-shirts"], "variants": 45, "images": 8, "price": "$14.99", "sku": "3001-BLK-S" } ``` ## Applying Settings to Existing Products Settings apply to new imports by default. To update existing products: 1. Select products in PromoSync 2. Choose **Bulk Actions** > **Reapply Import Settings** 3. Select which settings to reapply 4. Confirm the operation ## Best Practices 1. **Test with one product** - Import a single product to verify settings 2. **Use consistent SKUs** - Choose a strategy and stick with it 3. **Optimize for SEO** - Include relevant keywords in titles 4. **Control variant count** - Use grouping to manage large catalogs 5. **Tag strategically** - Enable tags that help with collections and filtering - [Configurable Products](https://promosync-docs.psrestful.com/settings/configurable-products): Import kits, sets, and bundled promotional products (a plush toy with apparel, a tumbler with a color-matched straw, a multi-piece new-hire kit) as a single Shopify product with the correct variant options instead of a flat list of parts. # Configurable Products Some promotional products are not a single item with a list of colors. They are **built from more than one part**: a plush bear that ships with a printed shirt, a tumbler that comes with a color-matched straw, or a new-hire kit that bundles a notebook, pen, mug, and pouch. Suppliers describe these as multiple **part groups** in their pricing and configuration data. When **Configurable Products** is enabled, PromoSync reads those part groups and builds the **real variant matrix** as one Shopify product. When it is disabled, these products import the old way: every part is flattened into a single option list, which produces confusing variants (for example, a `$0.00` bear color and a separately priced shirt that has no bear attached). Configurable Products only changes products that have **more than one part group**. Regular single-group products (a tee with colors and sizes) import exactly as before, with no change. ## Enabling Configurable Products Configurable Products is an **admin feature flag** and is **off by default**. You can turn it on from **Shop Settings**, in the import settings card, by checking **Enable Configurable Products**. Once enabled, the next import (or re-import) of a multi-part product builds the variant matrix automatically. This is a newer capability. We recommend enabling it and re-importing a few configurable products first, then reviewing pricing, inventory, and images before relying on it across your catalog. Delete and re-import a product to apply the new logic to something already in your store. ## How variants are built Each **part group** that represents a real choice becomes a Shopify **option**, and every sellable variant is a combination of one selection from each option. Parts that are not a real choice (a single fixed component, or an accessory that is locked to another part's color) are attached to the variant automatically. | Product | Parts | Result | |---------|-------|--------| | Plush bear + apparel | 2 bear colors, 26 apparel items (shirts, hoodies, bandanas) | 1 product, 2 options, **52 variants** | | Tumbler + straw | 7 body colors, each with a matching straw | 1 product, 1 option, **7 variants** (the straw rides along) | | Sunglasses + pouch | 24 sunglasses colors, 1 printed pouch | 1 product, 1 option, **24 variants** (the pouch is included on every one) | | New-hire kit | 6 fixed pieces (notebook, pen, mug, pouch, etc.) | 1 product, **1 variant** (the whole kit) | ### Option names and values - **Option names** use the supplier's group label when there is one (for example `Sunglasses`). When the supplier only provides a generic label, the option is named `Option 1`, `Option 2`, and so on. You can rename options in Shopify after import. - **Option values** are kept unique. When one option mixes item types (shirts, hoodies, and bandanas that all include a "Navy Blue"), the values are labeled to stay distinct, for example `Shirt - Navy Blue` and `Hoodie - Navy Blue`. ## Pricing The price of a configurable variant comes from the **priced part** of the combination, with your store's normal pricing rules applied (MQ, MAP, MDP, or a custom strategy). In most kits one part carries the price and the rest are included: - For the plush bear, the **apparel** carries the price and the bear is included, so a "Brown bear + Athletic Gold shirt" variant is priced from the shirt. - For the tumbler, the **body** carries the price and the straw is included. - For a kit, the **anchor item** carries the price and the other pieces are included. Volume and tier pricing work exactly as they do for any other product: the variant's price breaks come from its priced part. ## Inventory Each variant tracks inventory based on its priced part. Because a configurable product can reuse the same component across several variants (for example the same shirt color paired with two different bear colors), review inventory after import to confirm it reflects how you actually fulfill the product. ## Images PromoSync assigns each variant the best matching image it can find for that variant's parts: 1. An exact match on the part. 2. A match on the **shared color code** when the supplier's image is keyed slightly differently from the variant (for example a recycled body `5306RBLK` matched to the supplier's `5306BLK` blank image). 3. The product's general image, so no variant is ever left without a picture. Some suppliers only publish general or grouped marketing images for these products. In that case the matching above does its best, but per-color images are limited by what the supplier provides. ## SKUs A configurable variant's SKU lists every component, joined with a hyphen, so the full bill of materials is visible on the variant. For example: - `1200BRN-1210BLK` (brown bear with a black shirt) - `5306RBLK-5306RBLKSTRAW` (black tumbler with its matching straw) This makes it clear which supplier parts make up each sellable variant when you place purchase orders. ## Related - [Product Import Settings](/settings/product-import) - [Pricing Configuration](/settings/pricing) - [Volume Pricing](/settings/volume-pricing) - [Location Rules](https://promosync-docs.psrestful.com/settings/location-rules): Define tag-based default decoration rules so PromoSync can stamp locations and decoration methods onto products from suppliers that don't publish decoration data. # Location Rules **Location Rules** let you teach PromoSync your house decoration defaults so they're applied to imported products even when the supplier doesn't ship decoration data. Each rule says: "for any product with these tags, the default locations and decoration methods are these." ## Why You Need These Rules The PromoSync metafield that powers the storefront decoration block is `psrestful.location_decorations`. PromoSync populates that metafield from the supplier's Media Content / Decoration data. When a supplier doesn't publish that data, the metafield is empty and the decoration block on your product page has nothing to show. This happens in two common situations: 1. **Suppliers that ship blanks.** SanMar, S&S, and most apparel suppliers don't decorate themselves; they sell undecorated goods and leave decoration to you. Their PromoStandards feeds therefore don't include location or decoration pricing data. PromoSync has nothing to write into the metafield. 2. **Suppliers part-way through PromoStandards.** Some suppliers are still rolling out PromoStandards services and have Product Data live but Media Content / Decoration not yet ready. The end result is the same: no decoration data to import. Location Rules close that gap. You configure your own defaults once (for example, "every product tagged `polo` gets Front Chest with Embroidery"), and PromoSync applies them automatically. ## When Rules Are Applied A rule fires in two situations: - **At import time.** When PromoSync imports a product that lacks supplier-provided decoration data, it checks the product's tags against your active rules and stamps the matching rule's locations and decorations onto the new product. - **When the [Apply Default Location Decorations](/guides/bulk-actions/apply-default-decorations) bulk action runs.** Use this for products you've already imported, or to re-apply rules after editing them. Rules only add to what's already there. If the supplier *did* publish decoration data for a location, that data is kept as-is; the rule only fills in locations the supplier left out. The action is safe to re-run. ## Finding the Settings Page 1. Open PromoSync from your Shopify admin. 2. Go to **Shop Settings**. 3. In the sidebar, click **Location Rules**. The **Location Rules** tab appears in the sidebar only when the **Default Location Decorations** feature is turned on in Shop Settings. If you don't see it, enable the feature first. The table lists every rule you've created, with columns for **Name**, **Tags**, **Locations**, **Priority**, **Status**, and **Created**. The pencil icon opens the rule for editing; the trash icon deletes it. ## Creating a Rule Click **+ Add Rule** in the top right of the table to open the **Create Location Decoration Rule** dialog: | Field | What it does | |---|---| | **Name** | A label for you. PromoSync never shows it to customers; it only appears in this settings table. | | **Tags** | Comma-separated product tags this rule applies to. Matching is case-insensitive and exact: `polo` matches `Polo` but not `polo-shirt`. A product matches the rule if it carries any one of the listed tags. | | **Priority** | If a product matches more than one rule, the higher-priority rule wins. `0` is the lowest. Use higher numbers for more specific rules so they override generic ones. | | **Is active** | Uncheck to disable a rule temporarily without deleting it. Inactive rules are ignored at import time and by the bulk action. | | **Location decorations** | The decoration data itself, as a JSON array of locations and decorations. Click **Show JSON example** under the editor to paste a working sample you can edit. | Click **Create** to save. The rule takes effect immediately for the next import and for the next run of the bulk action. ## Writing the JSON The **Location decorations** field is a JSON array. Each entry describes one location, with a nested list of decoration methods available at that location. A minimal one-location example: ```json [ { "locationId": 53, "locationName": "Front", "decorationsIncluded": 1, "defaultLocation": true, "decorations": [ { "decorationId": 3764, "decorationName": "LASER ENGRAVE", "maxImprintColors": 1, "default": true, "priceIncludes": false } ] } ] ``` The fields you'll typically set per location: | Field | What it means | |---|---| | `locationId` | The PromoStandards location ID. PromoSync uses this to pair the decoration with [Decoration Listings](/guides/decoration-listings) charges. | | `locationName` | Human-readable label shown on the storefront (e.g. `Front`, `Back`, `Left Sleeve`). | | `decorationsIncluded` | How many decoration methods at this location are included in the base price (vs. priced separately). | | `defaultLocation` | `true` for the location pre-selected when a customer opens the product. Exactly one location should have this set. | | `decorations` | Array of decoration methods available at this location. | And per decoration inside `decorations`: | Field | What it means | |---|---| | `decorationId` | The PromoStandards decoration ID (e.g. embroidery, screen print, laser engrave). | | `decorationName` | Human-readable label for the method (e.g. `EMBROIDERY`, `SCREEN PRINT`). | | `maxImprintColors` | Max number of imprint colors the method supports at this location. | | `default` | `true` for the decoration pre-selected when the customer picks this location. | | `priceIncludes` | `true` if this decoration's cost is rolled into the variant price; `false` if it's billed via [Decoration Listings](/guides/decoration-listings). | The **Show JSON example** toggle in the dialog inserts a multi-location, multi-decoration sample you can copy and edit, which is usually easier than typing from scratch. ## Tag and Priority Strategy A few patterns that work well: - **One specific tag per rule.** A single tag like `embroidered-polo` matched by one rule is easier to debug than overlapping tags across many rules. - **Generic at low priority, specific at high priority.** Build a `default-decoration` rule at priority `0` that covers your fallback case, then layer specific rules (`embroidered-polo`, `laser-engrave-possible`) at higher priorities to override it. - **Use Shopify's bulk-tag tools to apply your tags.** You can filter products by vendor or collection in Shopify Admin and add a tag to all selected products at once, which makes large rollouts cheap. Only **one** rule fires per product: the highest-priority active rule whose tags match. Ties are broken by most-recently-created. ## Verifying a Rule Worked After importing a product (or running **Apply Default Location Decorations** on it): 1. Open the product in Shopify Admin and scroll to its metafields. 2. Look for `psrestful.location_decorations`. It should match the JSON you defined in the rule, possibly merged with any decoration data the supplier did publish. 3. Or load the product page on your storefront and confirm the decoration block shows the expected locations and methods. ## Editing or Disabling a Rule In the Location Rules table: - Click the pencil icon next to a rule to edit any field. Saves take effect for the next import and the next bulk-action run, but they do not retroactively update products that have already been imported. Run **Apply Default Location Decorations** on the affected products to apply the new rule. - Uncheck **Is active** to pause a rule without deleting it. Inactive rules are skipped during matching. - Click the trash icon to permanently delete a rule. Deleting a rule does not remove decoration data from products that previously matched it. ## Related - [Apply Default Location Decorations](/guides/bulk-actions/apply-default-decorations): the bulk action that stamps these rules onto products you've already imported. - [Enrich Location Decorations](/guides/bulk-actions/enrich-location-decorations): wires each decoration on a product to its Setup Fee and Run Charge product so checkout charges the imprint correctly. Run this after applying default decorations. - [Decoration Listings](/guides/decoration-listings): creates the Setup Fee and Run Charge products that **Enrich Location Decorations** references. - [Decoration Defaults](https://promosync-docs.psrestful.com/settings/decoration-defaults): Pin the pricing class your storefront charges for each decoration method and charge kind when a product has no per-product decoration pricing, used for SanMar / S&S Activewear-style blanks. # Decoration Defaults **Decoration Defaults** control the price your storefront falls back to when a physical product has no decoration pricing of its own for a given imprint method and charge kind. This is the case for blanks: SanMar, S&S Activewear, and most apparel suppliers sell undecorated goods and publish no decoration pricing, so PromoSync has no per-product number to charge. Decoration Defaults fill that gap with a sensible, shop-wide price. ## How Decoration Pricing Normally Works PromoSync stores decoration pricing on each product in the `psrestful.location_decorations` metafield. For suppliers that decorate (and publish PromoStandards Media Content), that metafield carries the exact setup fee, run charge, and extras for every method the supplier offers, so the storefront charges the product's own pricing. To make this efficient across thousands of products, PromoSync builds a **canonical decoration catalog**: one Shopify product per imprint method (Screen Print, 3D Embroidery, 4-Color Process, and so on), each carrying one variant per **pricing class**. A pricing class is a group of suppliers that all charge the same price for the same (method, kind). Each class is identified by a stable 8-character **pricing hash** (for example `#e6b507fc`), which doesn't change when the catalog is re-imported as long as the underlying supplier prices stay the same. When a product *does* publish decoration pricing, the storefront points straight at the matching class. When it *doesn't*, the storefront needs a fallback, and that is what Decoration Defaults set. ## What a "Default" Is For every combination of: - **Method** — the imprint method, for example `3d-embroidery`, `4-color-process`, `applique`, `screen-print`. - **Kind** — the charge being priced. The charge kinds are: | Kind | Meaning | |------|---------| | `setup_fee` | One-time setup charge for the first location | | `run_charge` | Per-unit run charge | | `extra_color_setup` | One-time setup for each additional color | | `extra_color_run` | Per-unit run charge for each additional color | | `extra_location_setup` | One-time setup for each additional location | | `extra_location_run` | Per-unit run charge for each additional location | | `pms_match` | PMS color match charge | PromoSync designates one pricing class as the **current default**. That class's variant is what the storefront charges for any blank product that maps to this (method, kind) but has no per-product pricing of its own. ## Auto-Pick vs. Pinning By default, PromoSync picks the default for you. On every **Insert decorations** run it auto-picks the **most popular pricing class**, the one shared by the most suppliers in your selected-supplier subset, so the fallback price reflects the most market-typical number. Rows priced this way are labeled **auto-picked** in the **Actions** column. If the auto-picked price isn't the one you want to charge, you can **pin** a specific pricing class instead. A pinned class: - Overrides the auto-pick for that (method, kind) in your shop. - **Survives re-imports.** PromoSync remembers your choice by its pricing hash, not by the underlying Shopify variant ID (which can rotate), so re-importing the canonical catalog won't lose your pin. - Is labeled **Override** next to the current default, with a **Clear override** action to return the row to auto-pick. The default only ever applies to products that have **no** per-product pricing for that (method, kind). Products whose supplier publishes decoration pricing always use their own price, regardless of what you pin here. ## Finding the Settings Page 1. Open PromoSync from your Shopify admin. 2. Go to **Shop Settings**. 3. In the sidebar, click **Decoration Defaults**. The table has one row per (method, kind) and these columns: - **Method** — the imprint method. The method name links to the canonical decoration product in your Shopify admin. - **Kind** — the charge kind from the table above. - **Current Default** — the price and pricing hash the storefront currently charges for blanks (for example `$2.50 #e6b507fc`). An **Override** badge appears here when you've pinned the class yourself. - **Pin pricing class** — a dropdown of every available class for that row, shown as `price — #hash — N supplier(s)`. The classes are ordered with the most popular (most suppliers) at the top, so the recommended pick is first. - **Actions** — shows **auto-picked** for rows PromoSync chose, or a **Clear override** button for rows you've pinned. ## Pinning a Different Class 1. Find the (method, kind) row you want to change. 2. Open the **Pin pricing class** dropdown. 3. Choose the class you want. The supplier count next to each price helps you judge how common that price is. 4. The change saves as soon as you pick it. The **Current Default** column updates and the row is now marked **Override**. PromoSync writes your choice both to its own records and to the canonical product's `default_variant_id` metafield, so the storefront reflects the new default immediately. ## Clearing an Override To go back to letting PromoSync auto-pick: 1. In the **Actions** column for that row, click **Clear override**. 2. Confirm. PromoSync removes your pin and clears the stored default. On the next **Insert decorations** run, PromoSync will auto-pick the most popular class for that row again. ## Bulk-Setting Defaults If you built your canonical catalog before this feature existed, some rows may have **no default set yet** (shown as *– not set –*). When that happens, a banner appears at the top of the table telling you how many rows are unset, with a **Save all auto-picks** button. Clicking it writes the recommended (most popular) class as the default for **every** row that currently has none, in one step. Rows that already have a default (auto-picked or pinned) are left untouched, so you won't accidentally overwrite a deliberate pin. Each row is one Shopify API call, so bulk-saving across many methods can take a few seconds. ## When Defaults Are Written The default for each (method, kind) is (re)written automatically on every **Insert decorations** run that rebuilds the canonical catalog. Auto-picked rows are recomputed from the current supplier mix; pinned rows keep your choice as long as that pricing class still exists in the catalog. If a pinned class disappears (for example, every supplier in it changed pricing), PromoSync falls back to the recommended class for that run and logs the change. ## Related - [Location Rules](/settings/location-rules) — set default locations and decoration methods for blanks that ship no decoration data at all. - [Tier Pricing Rules](/settings/tier-pricing-rules) — configure quantity-based decoration pricing tiers. - [Tier Pricing Rules](https://promosync-docs.psrestful.com/settings/tier-pricing-rules): Define tag-based default tier pricing so PromoSync can populate the tier-price array on products from suppliers that don't publish tier pricing, or to override the supplier's tiers with the distributor's own structure. # Tier Pricing Rules **Tier Pricing Rules** let you define quantity-break pricing for your products even when the supplier doesn't publish tier pricing data. Each rule says: "for any product with these tags, build a tier-price array with these quantity breakpoints, this markup, and this per-piece decoration charge." ## Why You Need These Rules The variant-level metafield that powers the tier-pricing block on your product pages is `psrestful.part_price_array`. PromoSync populates it from the supplier's PPC (Product Pricing and Configuration) response. When a supplier doesn't publish PPC tier data, or publishes tiers that don't fit your distributor model, that metafield ends up empty or with the wrong shape, and the storefront tier-pricing block has nothing useful to show. This matters in two common situations: 1. **Suppliers that don't publish tier pricing.** Suppliers like SanMar and S&S sell blanks at simple per-unit prices; they don't ship quantity-break tables through PromoStandards. PromoSync has nothing to put in the tier-price array. 2. **Distributors who want their own tier structure.** Even when a supplier *does* publish tiers, those tiers reflect the supplier's business (their wholesale ladder), not yours. A distributor who decorates and resells typically wants a different ladder, often with a markup applied uniformly across tiers and a per-piece decoration charge added on top. Tier Pricing Rules close that gap. You define one rule per tag (e.g. `t-shirt`, `embroidery`), PromoSync builds the tier array from your rule's breakpoints, applies your markup, and adds your decoration charge. When a rule matches, it **wins** over the supplier's own tier pricing. The distributor's rule reflects the distributor's business, so PromoSync intentionally lets it override any supplier-supplied tier array. The visible variant price also follows from the rule's first tier. ## When Rules Are Applied A rule fires whenever PromoSync rebuilds variant pricing for a matched product: - **At import time.** Newly imported variants get their `psrestful.part_price_array` built from your matching rule. - **When [Update Metafields](/guides/bulk-actions/update-metafields) runs.** That action rebuilds the full PromoSync metafield bundle, including the tier-price array, against your current rules. - **When [Update Prices](/guides/bulk-actions/update-prices) runs.** That action recalculates the visible variant price; under the hood it consults the same pricing pipeline, so an active rule reshapes the tier array at the same time. There is no dedicated "Apply Default Tier Pricing" bulk action. After adding or editing a rule, run **Update Metafields** (or **Update Prices**) on the affected products to re-apply. The rule only fires when the supplier returned PPC data for the product. If the supplier exposes no pricing service at all, there's no base cost for the rule to mark up, and the variant is left alone. The same is true when the shop-level **Default Tier Pricing** feature is turned off in Shop Settings. ## Finding the Settings Page 1. Open PromoSync from your Shopify admin. 2. Go to **Shop Settings**. 3. In the sidebar, click **Tier Pricing Rules**. The **Tier Pricing Rules** tab appears in the sidebar only when the **Default Tier Pricing** feature is turned on in Shop Settings. If you don't see the tab, enable the feature first. The table lists every rule, with **Name**, **Tags**, **Tiers** (count), **Priority**, **Status**, and **Created** columns. The pencil icon opens the rule for editing; the trash icon deletes it. ## Creating a Rule Click **+ Add Rule** in the top right of the table to open the **Create Tier Pricing Rule** dialog: | Field | What it does | |---|---| | **Name** | A label for you. PromoSync never shows it to customers; it only appears in the settings table. | | **Tags** | Comma-separated product tags this rule applies to. Matching is case-insensitive and exact: `t-shirt` matches `T-Shirt` but not `t-shirts`. A product matches if it carries any one of the listed tags. | | **Priority** | If a product matches more than one rule, the higher-priority rule wins. `0` is the lowest. Use higher numbers for more specific rules. | | **Is active** | Uncheck to disable a rule temporarily without deleting it. Inactive rules are skipped during matching. | | **Pricing Tiers** | The quantity-break grid (see below). One row per breakpoint. Click **+ Add Tier** to append a row; tick **Delete** to remove a row when saving. | Each row of the **Pricing Tiers** grid defines one quantity break: | Column | What it does | |---|---| | **Quantity** | The minimum order quantity this tier kicks in at (e.g. `1`, `12`, `24`, `48`). | | **Price Base** | Which supplier price to start from for this tier: `Net`, `Customer`, or `List`. PromoSync reads the matching field from the supplier's PPC response (combined with the shop's blank/decorated configuration). | | **Adjustment %** | A positive percentage adds markup, a negative percentage applies a discount. Calculated as `base * (1 + adjustment / 100)`. Each row sets its own value, so different tiers can carry different markups if you want. | | **Decoration $** | A flat per-piece dollar amount added to the calculated price for this tier. It's added unconditionally, regardless of whether the product is decorated. | A rule must have **at least two tiers** to take effect. If a rule has only one tier, PromoSync falls back to the supplier's tier array as if the rule didn't exist. The visible variant price is set from the rule's *first* (lowest-quantity) tier. Click **Create** to save. The rule takes effect at the next import, and the next time **Update Metafields** or **Update Prices** runs on a matching product. ## A Worked Example The **Update Tier Pricing Rule** dialog below shows a realistic apparel ladder: seven quantity breaks (1, 12, 24, 48, 72, 144, 288), all marked up 65% on top of the supplier's **Net** price, with a per-piece decoration charge that steps down as quantity grows (\$10.00 at quantity 1, falling to \$4.00 at quantity 288). For the **24-piece tier**, the price PromoSync writes to `psrestful.part_price_array` is: ``` price = supplier_net_price * (1 + 65/100) + 6.00 ``` If the supplier's Net price for the variant is \$8.00, that's `8.00 * 1.65 + 6.00 = $19.20` for each piece when the buyer orders 24. ## Tag and Priority Strategy A few patterns that work well: - **One tag per rule.** A rule keyed to a single distinctive tag like `t-shirt-tier` is easier to debug than a rule that lists five overlapping tags. - **Generic at low priority, specific at high priority.** A `default-pricing` rule at priority `0` covers the fallback case; layer `embroidery` or `laser-engrave-possible` rules on top at higher priorities to override for decorated products. - **Use Shopify's bulk-tag tools.** You can filter by vendor or collection in Shopify Admin and apply a tag to all selected products at once, which makes large rollouts cheap. Only **one** rule fires per product: the highest-priority active rule whose tags match. Ties are broken by most-recently-created. The rule then applies to **every variant** of that product, since rules match by product tags rather than per-variant data. ## Verifying a Rule Worked After importing a product (or running **Update Metafields** on it): 1. Open the variant in Shopify Admin and scroll to its metafields. 2. Look for `psrestful.part_price_array`. It should contain one entry per tier in your rule, with the calculated price in cents and the `quantityMin` matching your rule's quantity column. 3. The product-level `psrestful.minimum_quantity` should equal the lowest tier's quantity. 4. Or load the product page on your storefront and confirm the tier-pricing block shows the expected breakpoints. ## Editing or Disabling a Rule In the Tier Pricing Rules table: - Click the pencil icon to edit any field, including individual tier rows. Saves take effect for the next import, the next **Update Metafields** run, and the next **Update Prices** run, but they do not retroactively update products that already had the old tier array written to them. Run **Update Metafields** on the affected products to re-apply. - Uncheck **Is active** to pause a rule without deleting it. Inactive rules are ignored during matching. - Click the trash icon to permanently delete a rule. Deleting a rule does not remove tier pricing already written to variants; run **Update Metafields** on those variants to rebuild from whichever rule now matches (or from the supplier's tier array if no rule matches). ## Related - [Update Metafields](/guides/bulk-actions/update-metafields): rebuilds the metafield bundle on selected products, re-applying your current Tier Pricing Rules. - [Update Prices](/guides/bulk-actions/update-prices): recalculates the visible variant price; uses the same pricing pipeline, so the tier array is rebuilt at the same time. - [Pricing Configuration](/settings/pricing): the broader pricing settings (blank vs. decorated configuration, markup formulas) that determine which supplier price each tier starts from. - [Location Rules](/settings/location-rules): the sibling feature for default decoration locations, useful for the same suppliers that don't publish decoration data. - [Volume Pricing](https://promosync-docs.psrestful.com/settings/volume-pricing): Turn on the Quantity Price Breaks Shopify Function that automatically discounts cart lines down to the right tier price, and choose between per-variant and combined-by-product quantity grouping. # Volume Pricing **Volume Pricing** is the PromoSync feature that automatically discounts cart lines down to the matching tier price at checkout. It is powered by the [Quantity Price Breaks Shopify Function](/guides/theme-extensions/volume-pricing-function), which reads each variant's tier-price array (`psrestful.part_price_array`) and applies the correct unit price based on the quantity in the cart. The display block on the product page ([Volume Pricing Theme Extension](/guides/theme-extensions/volume-pricing)) only *shows* the table of tier prices. This setting page controls the underlying Function that actually applies the discount. ## The Two Settings The feature is configured from two fields in **Shop Settings**. **Screenshot needed:** Shop Settings page showing the **Enable Volume Pricing** checkbox and the **Mode** dropdown. Save to `/public/images/volume-pricing-settings.png`. ### Enable Volume Pricing A single checkbox. Turning it on tells PromoSync to create an **automatic discount** in your Shopify store's Discounts section, linked to the Quantity Price Breaks Function. Turning it off deletes that automatic discount so no tier-price discounting happens at checkout. You don't manage the discount in Shopify Admin directly: flip this checkbox and PromoSync calls the Discounts API to create or remove it for you. ### Mode A dropdown with two options. The mode controls how the Function groups cart quantities when it decides which tier each line qualifies for. | Mode | What it does | When to use it | |---|---|---| | **Per variant** | Each cart line's quantity decides its own tier independently. | You sell products where the customer is buying one variant at a time, or where mixing variants shouldn't grant a higher-quantity discount. | | **Combined by product** | Quantities are summed across every cart line that shares the same parent product, and each of those lines gets the tier price for the combined total. | The common case for apparel: the customer fills a cart with 4 Red, 4 Blue, and 4 Green of the same polo, hits the "12 piece" tier together, and every line gets the 12-piece price. | ### A Worked Example Suppose a polo has tier prices: 1+ at \$20, 12+ at \$15, 24+ at \$12. The customer adds 4 Red, 4 Blue, and 4 Green to the cart. | Mode | Red line price | Blue line price | Green line price | Why | |---|---|---|---|---| | **Per variant** | \$20 (qty 4) | \$20 (qty 4) | \$20 (qty 4) | Each line's own qty of 4 is below the 12 tier. | | **Combined by product** | \$15 (qty 4) | \$15 (qty 4) | \$15 (qty 4) | Combined qty across the three lines is 12, so every line gets the 12-piece price. | ## Where to Find It 1. Open PromoSync from your Shopify admin. 2. Go to **Shop Settings**. 3. Scroll to the **Volume Pricing** section. 4. Tick **Enable Volume Pricing** and choose the **Mode**. 5. **Save**. The first time you save with **Enable Volume Pricing** turned on, PromoSync creates the automatic discount in your Shopify store. From that point, switching the **Mode** updates the discount's configuration in place without recreating it. ## Prerequisites For the discount to actually fire on a line at checkout, the variant on that line must have a populated `psrestful.part_price_array` metafield. That happens automatically when: - The variant was imported from a supplier that publishes PPC tier data, or - The product carries a tag matched by an active [Tier Pricing Rule](/settings/tier-pricing-rules). Variants without tier data are simply ignored by the Function (no discount is applied to those lines). To re-populate the metafield after changing pricing rules, run [Update Metafields](/guides/bulk-actions/update-metafields) on the affected products. ## Verifying It Worked After enabling the toggle: 1. In Shopify Admin, open **Discounts**. You should see a new **automatic discount** named **Quantity Price Breaks**. **Screenshot needed:** Shopify Admin Discounts page showing the **Quantity Price Breaks** automatic discount listed and active. Save to `/public/images/volume-pricing-discount-in-admin.png`. 2. Add a tier-priced variant to your cart at a quantity that crosses a tier breakpoint. The cart and checkout should show the discount as a line-level price reduction. **Screenshot needed:** Checkout breakdown showing the Quantity Price Breaks discount applied to one or more lines. Save to `/public/images/volume-pricing-checkout-discount.png`. 3. To verify **Combined by product** mode, add multiple variants of the same product, each at a quantity below the next tier but with a combined total that crosses it. The checkout breakdown should show all of those lines getting the higher-quantity tier price. **Screenshot needed:** Checkout breakdown demonstrating combined-by-product mode (e.g. 4 Red + 4 Blue + 4 Green polos getting the 12-piece tier price on every line). Save to `/public/images/volume-pricing-combined-by-product.png`. ## Why Both Modes Exist The default **Per variant** mode is what you want when each cart line is its own thing: a unique decorated product, a custom-built bundle, or anything where the buyer is committing to a specific quantity of a specific variant. **Combined by product** is what you want for apparel and similar categories where the buyer thinks of the product as one thing (a polo) and treats colors and sizes as splits of one order. Without combined mode, a buyer who orders 4 each of three colors would end up paying the 4-piece price on every line, even though the apparel supplier (and the buyer) consider that a 12-piece order. The mode applies globally to your store. There's no per-product override, so pick the one that matches the bulk of your catalog. ## Caveats - **Display block and Function read the same data.** The product-page table and the checkout discount both come from `psrestful.part_price_array`, so they stay in sync. If you change pricing rules, run [Update Metafields](/guides/bulk-actions/update-metafields) on the affected products to refresh both at once. - **Don't edit the discount in Shopify Admin.** PromoSync owns the **Quantity Price Breaks** automatic discount. Editing its name, status, or configuration manually can desync it from the toggle. To turn it off, untick **Enable Volume Pricing** in PromoSync settings. - **The Function only discounts; it never raises prices.** If a variant's listed Shopify price is already at or below the tier price for the cart quantity, the line isn't touched. ## Related - [Quantity Price Breaks Function](/guides/theme-extensions/volume-pricing-function): the Shopify Function this setting toggles on and off. - [Volume Pricing Theme Extension](/guides/theme-extensions/volume-pricing): the product-page display block that shows the tier table to shoppers. - [Tier Pricing Rules](/settings/tier-pricing-rules): how to populate `psrestful.part_price_array` for products whose supplier doesn't publish tier data. - [Update Metafields](/guides/bulk-actions/update-metafields): how to refresh tier data on existing products after editing rules. ## Guides - [Guides Overview](https://promosync-docs.psrestful.com/guides): Step-by-step guides for importing products, syncing inventory, configuring pricing, and managing promotional products in your Shopify store. # Guides Overview This section contains step-by-step guides for common PromoSync tasks. Each guide walks you through a specific workflow from start to finish. ## Available Guides ### [First-time Setup](/guides/first-time-setup) What new merchants see when they open PromoSync for the first time, walked through the three-step onboarding wizard and what to do next. **Topics covered:** - Step 1: paste your PSRESTful API key - Step 2: add at least one notification contact - Step 3: import two sample products from HIT Promo - What to do after the wizard, by starting state - Why the wizard does not re-run after onboarding ### [Importing Products](/guides/importing-products) Learn how to search for products, apply filters, preview imports, and add products to your Shopify store. **Topics covered:** - Searching the product catalog - Using filters (supplier, brand, category) - Previewing products before import - Product search and bulk imports - Import troubleshooting ### [Connecting Existing Products](/guides/connecting-existing-products) Link products already in your Shopify store to supplier data so PromoSync can sync inventory and pricing automatically. **Topics covered:** - Why existing products need PSRESTful metafields - Exporting from PSRESTful CSV Exporter - Bulk-updating with Matrixify - Running Onboard to PromoSync (one-click Update Metafields + Link to PromoSync) ### [Bulk Actions](/guides/bulk-actions) Master the bulk operations available in PromoSync for efficient product management. **Topics covered:** - Accessing bulk actions from Shopify admin - Syncing inventory for multiple products - Updating pricing in bulk - Refreshing product data - Removing products from pricing sync ### [Link to PromoSync](/guides/link-to-promosync) Register existing Shopify products (including ones you duplicated) with PromoSync so future syncs skip redundant API calls. **Topics covered:** - Why PromoSync keeps a backend registry - What counts as a "duplicate" (Shopify product copies, Matrixify-connected products) - How `extra_id` is resolved (metafield first, then PSRESTful lookup) - Where to run the action (dashboard, bulk action, all-products dialog) ### [Decoration Listings](/guides/decoration-listings) Publish supplier setup and run charges as Shopify products so you can bill imprint costs accurately in customer orders. **Topics covered:** - What Decoration Listings creates (Setup Fee and Run Charge products) - Volume pricing via the `psrestful.part_price_array` variant metafield - Picking the right tier when building orders - When to use it (and when not to) ### [Storefront Widgets](/guides/storefront-widgets) Implement tier pricing displays, decoration options, and other storefront enhancements. **Topics covered:** - Displaying tier pricing on product pages - Showing decoration locations and methods - Star ratings and reviews integration - Custom metafield displays - Theme customization examples ### [Theme App Extensions](/guides/theme-extensions) No-code, drag-and-drop app extension blocks for Shopify product pages via Theme Customizer. **Topics covered:** - [Volume Pricing](/guides/theme-extensions/volume-pricing) — quantity-based price break tables - [Minimum Quantity](/guides/theme-extensions/minimum-quantity) — enforce per-product order minimums - [Decoration Selector](/guides/theme-extensions/decoration-selector) — location and method chooser ## Quick Reference | Task | Guide | Difficulty | |------|-------|------------| | Walk through the install wizard | [First-time Setup](/guides/first-time-setup) | Beginner | | Import first product | [Importing Products](/guides/importing-products#your-first-import) | Beginner | | Search by supplier | [Importing Products](/guides/importing-products#filtering-by-supplier) | Beginner | | Connect existing products | [Connecting Existing Products](/guides/connecting-existing-products) | Intermediate | | Bulk inventory sync | [Bulk Actions](/guides/bulk-actions#sync-inventory) | Intermediate | | Bill setup & run charges in orders | [Decoration Listings](/guides/decoration-listings) | Intermediate | | Register duplicated products with PromoSync | [Link to PromoSync](/guides/link-to-promosync) | Beginner | | Display tier pricing | [Storefront Widgets](/guides/storefront-widgets#tier-pricing-block) | Intermediate | | Custom decorations display | [Storefront Widgets](/guides/storefront-widgets#location-decorations) | Advanced | | Volume pricing (no-code) | [Theme Extensions — Volume Pricing](/guides/theme-extensions/volume-pricing) | Beginner | | Minimum quantity (no-code) | [Theme Extensions — Minimum Quantity](/guides/theme-extensions/minimum-quantity) | Beginner | | Decoration selector (no-code) | [Theme Extensions — Decoration Selector](/guides/theme-extensions/decoration-selector) | Beginner | ## Video Tutorials Coming soon: Video walkthroughs for each guide. ## Need More Help? - [Settings Documentation](/settings) - Configure PromoSync options - [Metafields Reference](/metafields) - All available metafields - [Advanced Features](/advanced) - Custom pricing and multi-location setup - [PSRESTful API Docs](https://api.psrestful.com/docs) - API reference - [First-time Setup](https://promosync-docs.psrestful.com/guides/first-time-setup): What new merchants see when they open PromoSync for the first time, walked through the three-step onboarding wizard and what to do once it completes. # First-time Setup This guide is for merchants who have just installed PromoSync from the Shopify App Store. It walks through the three-step onboarding wizard the dashboard runs on first launch and tells you what to do once it finishes. For the prerequisites (Shopify account, PSRESTful account, API key generation), see the [Installation guide](/installation). ## The Onboarding Wizard When you open PromoSync for the first time, the dashboard hides itself behind a three-step wizard. Each step gates the next, and the dashboard remembers where you left off if you close the tab. ### Step 1 of 3: Connect PSRESTful PromoSync needs a PSRESTful API key to read supplier catalogs, pricing, and inventory. 1. Open [psrestful.com/dashboard/api-keys/private/](https://psrestful.com/dashboard/api-keys/private/) in another tab 2. Click **Generate New Key** (or copy an existing one) 3. Paste it into the PromoSync wizard and submit The wizard validates the key against PSRESTful before letting you continue. If the key is rejected, double-check for stray whitespace, confirm your PSRESTful subscription is active, and confirm the key has not been revoked. ### Step 2 of 3: Notification contacts Tell PromoSync where to email async-job summaries (inventory syncs, price updates, onboarding results, and similar background jobs). 1. Enter at least one email address 2. Submit to advance You can add or remove notification contacts later from **Settings**. ### Step 3 of 3: Try it, import your first products PromoSync adds two sample products from HIT Promo to your Shopify store so you can see how a finished PromoSync listing looks before importing your full catalog. The two products vary by environment but are picked to demonstrate variants, pricing tiers, and metafields populated automatically. 1. Click **Import these samples** 2. The wizard advances to an "importing" screen that polls for completion 3. When the products land in Shopify, you'll see a celebration screen with links to the new listings 4. Click **Continue to dashboard** to exit the wizard The demo products are real supplier records, so they include live pricing tiers, variants, and metafields. Feel free to delete them after you've inspected them; the dashboard reverts to the regular Overview the next time you load it. ## What to Do After the Wizard The dashboard now shows the regular Overview, with the recently imported sample products counted. From here you'll typically pick one of the following next steps depending on your starting state: | Your starting state | Next step | |----------------------|-----------| | Fresh Shopify store, no existing products | [Import Products](/guides/importing-products) from supplier catalogs | | You already have products in Shopify (added manually or via another app) | [Connect Existing Products](/guides/connecting-existing-products) | | You want to tune pricing rules first | [Pricing Configuration](/settings/pricing) | | You want to control how often inventory syncs | [Inventory & Scheduling](/settings/inventory) | ## Re-running the Wizard The wizard only runs as long as the three gates are incomplete: missing PSRESTful key, no active notification contact, or no products in Shopify carrying PromoSync metafields. Deleting your products in Shopify after onboarding will *not* trigger the wizard again, because PromoSync's backend still has them registered. If you want to genuinely start over (e.g. for testing), [contact support](mailto:support@psrestful.com). ## Troubleshooting ### The wizard skipped Step 3 This usually means PromoSync's backend already lists products against your shop, either from a previous install or from an earlier test session. Refresh the dashboard once; if the wizard still skips Step 3, your shop is past onboarding and you can use [Import Products](/guides/importing-products) directly. ### Step 1 keeps rejecting my key - Generate a fresh key in your PSRESTful dashboard and try again - Confirm the key belongs to an account with an active subscription - Make sure you copied the **private** key (not the public one) ### I never received the demo import email - Confirm the email you entered in Step 2 is correct (check **Settings** → **Notification contacts**) - Look in spam or filtered folders - The dashboard **Overview** also shows recent activity, including the sample import; that's the most reliable confirmation - [Importing Products](https://promosync-docs.psrestful.com/guides/importing-products): Search, filter, preview, and import promotional products from 100+ suppliers into your Shopify store with PromoSync. # Importing Products This guide walks you through finding and importing promotional products from PromoStandards suppliers into your Shopify store. ## Overview PromoSync connects to 100+ promotional product suppliers through the PSRESTful API. You can search their combined catalogs, preview products, and import them directly to Shopify with your configured pricing and settings. ## Your First Import ### Step 1: Access the Import Screen 1. Open your Shopify admin 2. Navigate to **Apps** > **PromoSync** 3. Click **Import Products** in the main navigation ### Step 2: Search for Products Enter search terms in the search bar: - Product type: "t-shirt", "polo", "hoodie" - Brand: "Bella Canvas", "Gildan" - Part ID: "3001", "G500" Click **Search** or press Enter. ### Step 3: Review Results The search results show: - Product image thumbnail - Product name and part ID - Brand and supplier - Price range - Available colors/sizes ### Step 4: Preview a Product Click on any product to see: - All product images - Complete pricing tiers - Available variants (colors and sizes) - Inventory levels - Product specifications ### Step 5: Import the Product 1. Click **Import to Shopify** 2. Confirm the import settings 3. Wait for the import to complete 4. Click **View in Shopify** to see your new product ## Filtering Products ### Filtering by Supplier Import products from specific suppliers: 1. Click **Filters** on the search results page 2. Under **Supplier**, select one or more suppliers 3. Click **Apply Filters** Popular suppliers: - SanMar - S&S Activewear - Hit Promotional Products - PCNA ### Filtering by Brand Find products from specific brands: 1. Click **Filters** 2. Under **Brand**, type or select brand names 3. Click **Apply Filters** Examples: - BELLA+CANVAS - Gildan - Champion - The North Face - Nike ### Combining Filters Apply multiple filters simultaneously: ``` Supplier: SanMar Brand: The North Face Search: "softshell" ``` This finds The North Face softshell jackets available from SanMar. ## Bulk Import ### Selecting Multiple Products 1. Check the box next to each product you want to import 2. Or click **Select All** to select all visible results 3. The selection count appears at the top ### Importing Selected Products 1. With products selected, click **Import Selected** 2. Click **Confirm Import** You can navigate away - imports continue in the background. ## Import Options ### Per-Import Settings Override default settings for specific imports: | Option | Description | |--------|-------------| | **Title Format** | Override default title template | | **Pricing Strategy** | Use different pricing for this import | | **Target Collection** | Add to specific collection | | **Publish Status** | Draft or Active | ### Skip Existing Products By default, PromoSync skips products already in your store (matched by part ID). You can: - **Skip** - Don't reimport (default) - **Update** - Update existing product data - **Duplicate** - Create a new product ## Managing Import History ### Viewing Import History 1. Navigate to **PromoSync** > **History** 2. See all past imports with: - Date and time - Product count - Success/failure status - Who triggered the import ### Retrying Failed Imports If products fail to import: 1. Click on the failed import in history 2. Review the error details 3. Click **Retry Failed Products** Common errors: - `Rate limit exceeded` - Wait and retry - `Invalid image URL` - Check with supplier - `Variant limit exceeded` - Use variant grouping ## Import Performance ### Estimated Times | Products | Variants | Estimated Time | |----------|----------|----------------| | 1 | 20 | 5-10 seconds | | 10 | 200 | 1-2 minutes | | 50 | 1,000 | 5-10 minutes | | 100 | 2,000 | 10-20 minutes | Times vary based on: - Number of images - Variant complexity - Supplier response time - Shopify API limits ### Optimization Tips 1. **Import during off-peak hours** - Faster supplier responses 2. **Use filters** - Only import what you need 3. **Batch similar products** - Better caching 4. **Check inventory first** - Avoid importing out-of-stock items ## Verifying Imports ### After Import Checklist - ✓ Product appears in Shopify - ✓ All variants are present - ✓ Images loaded correctly - ✓ Pricing is accurate - ✓ Inventory levels are set - ✓ Metafields are populated ### Viewing Synced Products 1. In PromoSync, go to **Products** 2. **Synced** products will be highlighted ### Checking Sync Status Each synced product shows: - Last sync time - Sync status (success, pending, error) - Supplier connection status ## Troubleshooting ### No Search Results 1. Check your search terms - try broader terms 2. Verify your PSRESTful subscription includes the supplier 3. Check supplier availability in your region 4. Try filtering by supplier first, then searching ### Import Fails | Error | Solution | |-------|----------| | "Product already exists" | Enable update or use different SKU strategy | | "Image download failed" | Retry - temporary network issue | | "Variant limit exceeded" | Enable variant grouping | | "API rate limited" | Wait 60 seconds and retry | ### Incorrect Data If imported data doesn't match expectations: 1. Check your import settings (title format, pricing, etc.) 2. Compare with PSRESTful API directly 3. Verify supplier data in their portal 4. Contact support with specific examples ## Next Steps After importing products: 1. **[Configure storefront widgets](/guides/storefront-widgets)** - Display tier pricing and more 2. **[Set up inventory sync](/settings/inventory)** - Keep stock levels current 3. **[Review bulk actions](/guides/bulk-actions)** - Manage products efficiently - [Connecting Existing Products](https://promosync-docs.psrestful.com/guides/connecting-existing-products): Already have products in Shopify? Link them to supplier data with one click using Onboard to PromoSync so PromoSync can automatically sync inventory, pricing, and metafields. # Connecting Existing Products Already have products in your Shopify store? PromoSync can work with your existing catalog — not just freshly imported products. You just need to link them to their supplier records so that automated inventory and pricing sync can kick in. ## The Problem Products added to Shopify outside of PromoSync don't have the PSRESTful metafields that power automated sync. Without these metafields, PromoSync has no way to match your Shopify products to the correct supplier SKUs, so inventory levels and pricing won't update automatically. ## The Solution Connecting your existing products is a three-step process: ### 1. Export from PSRESTful CSV Exporter Use the PSRESTful CSV Exporter to export product data in the **Extra ID** format. This format pairs each Shopify-compatible identifier with the supplier record it should link to, so the exported file can be merged back into your store. The exported CSV looks like this: ```csv supplier_code,product_id,extra_id,name HIT,3035,4,Non-Woven Garment Bag HIT,3771,10,"12"" W X 12"" H Polyester Bag" HIT,5546,8,20 Oz. Kingston Aluminum Swiggy Bottle HIT,5786PW,9,20 Oz. Woodtone Himalayan Tumbler With Custom Window Box HIT,7190,5,10 Oz. Lacrosse Ceramic Mug HIT,MS1001,3,Mountain Standard Backroads Primaloft Jacket ``` Each row maps a product to its three PSRESTful identifiers: - **`supplier_code`** — the supplier's PromoStandards code (e.g. `HIT`) - **`product_id`** — the supplier's product identifier - **`extra_id`** — PSRESTful's stable internal ID, used to look the product up across suppliers The `name` column is the supplier's product title. You'll use it as the bridge for matching CSV rows to the products already in your store — either by eye in a spreadsheet or with a script that fuzzy-matches against your Shopify product titles. ### 2. Bulk-Update with Matrixify Use [Matrixify](https://apps.shopify.com/excel-export-import) (or a similar bulk-import app) to update your existing Shopify products with the exported CSV data. This step writes the PSRESTful metafields onto each product, linking them to the correct supplier records. For PromoSync to recognize a product, three metafields must be present on it: - **`psrestful.supplier_code`** — the supplier's PromoStandards code - **`psrestful.product_id`** — the supplier's product identifier - **`psrestful.extra_id`** — PSRESTful's stable internal ID All three values come straight from the columns in the exported CSV. #### Matching CSV rows to your Shopify products Before you can import, you need to tell Matrixify *which* Shopify product each row in the PSRESTful CSV belongs to. Matrixify identifies products by **handle** or **variant SKU**, and the handles in your store almost certainly don't match the ones PSRESTful would generate — so you can't just bolt a `Handle` column onto the PSRESTful CSV. Instead, start from your store's data and pull the supplier values into it. If you're not technical, the most reliable workflow is: 1. **Export your current Shopify catalog with Matrixify.** In Shopify admin, open Matrixify → **Export** → **Products** → **All products**. The exported file lists every product along with its handle and SKUs. 2. **Open both files side by side** in Excel, Numbers, or Google Sheets. 3. **For each row in the PSRESTful CSV, find the matching Shopify product** using the `name` column. A few distinctive words is usually enough — searching `Kingston Swiggy` will land you on row `HIT,5546`, for example. 4. **Copy the supplier values into the Shopify export.** Add three metafield columns to the Shopify file — `Metafield: psrestful.supplier_code [single_line_text_field]`, `Metafield: psrestful.product_id [single_line_text_field]`, and `Metafield: psrestful.extra_id [single_line_text_field]` — and paste the corresponding values from the PSRESTful CSV row by row. 5. **Import the updated Shopify file in merge mode.** Because the file already contains valid handles, Matrixify will update the right products without creating duplicates. For a small catalog (under ~20 products), it's often faster to skip the spreadsheet work and add the three metafields by hand: open each product in Shopify admin → **Metafields** → enter the values from the PSRESTful CSV. **You don't have to do the matching by hand.** Coding agents like [Claude Code](https://claude.com/claude-code), [Cursor](https://cursor.com), or [GitHub Copilot](https://github.com/features/copilot) can take both CSV files and produce a merged file for you — or write a short fuzzy-matching script that pairs PSRESTful rows to your Shopify products by name and copies over the three metafield values. A useful prompt: *"Given this Shopify product export and this PSRESTful CSV, match each Shopify product to the closest row in the PSRESTful CSV by product name, and add `Metafield: psrestful.supplier_code [single_line_text_field]`, `Metafield: psrestful.product_id [single_line_text_field]`, and `Metafield: psrestful.extra_id [single_line_text_field]` columns to the Shopify file. Flag any rows where the match isn't confident."* Always spot-check the result before importing. Matrixify's **merge** mode updates existing products by matching on handle or SKU — it won't create duplicates. The final step is a single PromoSync bulk action. In Shopify admin, open **Products**, select the products you just connected, click **More actions**, and choose **Sync Using PSRESTful**. A dialog appears with a single dropdown. Pick the action and click **Submit**. ### 3. Run Onboard to PromoSync Pick **Onboard to PromoSync** from the dropdown and submit. This single action runs both halves of the connection in the correct order: 1. **Update Metafields** — pulls the rest of the supplier data (pricing tiers, decoration info, variant-level `psrestful.part_id`, and other supplier-driven metafields) using the three identifiers you just imported, so each linked product matches what PromoSync would have set up on a fresh import. 2. **Link to PromoSync** — registers the now-complete products in PromoSync's backend so automated inventory and pricing syncs target them directly. See the [Link to PromoSync guide](/guides/link-to-promosync) for what the registry does under the hood. Running them inside one job guarantees the variant metafields and `psrestful.extra_id` are written **before** the registry step looks for them, which two separate bulk actions could not promise. **Smart skip.** Products in your selection that already have all three psrestful identifiers and are fully onboarded are detected at the Shopify query level and skipped, so re-running this on a mixed selection is safe and cheap. The job runs in the background and you'll receive a summary email when it completes. The dashboard **Overview** also shows an **Onboard to PromoSync** row with its status and duration. **Need to run them separately?** The original **Update Metafields** and **Link to PromoSync** bulk actions still live in the same dropdown. Use them individually if you want to refresh metafields without touching the backend registry, or vice versa. For the standard connect-existing-products flow, **Onboard to PromoSync** is the recommended one-click option. ## What Happens Next Once connected, your existing products behave exactly like products imported through PromoSync: - **Inventory** syncs automatically based on your [inventory settings](/settings/inventory). You can also force an immediate refresh with the **Update Inventory** bulk action. - **Pricing** updates follow your [custom pricing rules](/advanced/custom-pricing). - **New variants** can be pulled in on demand by running the **Add New Variants** bulk action whenever a supplier introduces additional sizes or colors. **Update Inventory** and **Add New Variants** live in the same **Sync Using PSRESTful** dialog you used in step 3: This workflow involves bulk-updating metafields across your catalog. We recommend running it on a small batch first to verify the results before processing your full inventory. ## Need Help? Connecting an existing catalog can vary depending on how your products are structured. Reach out to us and we'll walk you through the process for your specific store. - **Email:** [support@psrestful.com](mailto:support@psrestful.com) - **Website:** [psrestful.com](https://psrestful.com) - [Product Life Cycle](https://promosync-docs.psrestful.com/guides/product-life-cycle): Understanding the 4-status product life cycle in PromoSync — draft, active, closeout, and discontinued — and how it helps you manage your Shopify catalog. # Product Life Cycle PromoStandards defines only two product states: **sellable** and **closeout**. This is insufficient for real-world product management — there's no way to represent products that are incomplete (missing images or pricing) or permanently discontinued. PromoSync extends this with a full **4-status life cycle**: `draft`, `active`, `closeout`, and `discontinued`. --- ## Status Definitions | Status | Description | |--------|-------------| | **Draft** | Product is incomplete — missing images or has no pricing. Not ready to sell. | | **Active** | Product is complete and available for sale. | | **Closeout** | Product is being phased out, often at discounted prices. | | **Discontinued** | Product is permanently removed from the catalog. No longer available for sale. | --- ## Why Draft Matters When products are first ingested from suppliers, they may be incomplete. A product is automatically classified as **draft** when: - Its list price is `$0` or missing - It has no primary image Draft products are **not ready for customer-facing catalogs**. This prevents showing products with $0 prices or missing images to your Shopify storefront customers. --- ## Why Discontinued Matters **Closeout** in PromoStandards implies temporary clearance — products being sold off at discounted prices. But there's no way to mark a product as **permanently gone**. When a supplier stops making a product, you need to know so you can: - Remove it from your storefront - Stop syncing inventory and pricing for it - Avoid showing stale products to customers **Discontinued** fills this gap — it's a terminal state meaning "this product will not come back." --- ## How Status is Computed Product status is determined automatically: 1. If the supplier marks it as closeout → **Closeout** 2. If missing primary image or list price is $0/missing → **Draft** 3. Otherwise → **Active** **Discontinued** is also auto-computed in one case: when a closeout product hits zero inventory across all parts, it's automatically marked as discontinued. --- ## Life Cycle Diagram ``` ┌───────┐ ┌────────┐ ┌───────────┐ ┌──────────────┐ │ draft │──────▶│ active │──────▶│ closeout │──────▶│ discontinued │ └───────┘ └────────┘ └───────────┘ └──────────────┘ │ ▲ │ │ └────────────────────────────────────────┘ ``` - **Draft → Active**: Product becomes complete (has pricing and images) - **Active → Closeout**: Product is being phased out at discounted prices - **Active → Discontinued**: Product is permanently removed from the catalog - **Closeout → Discontinued**: Auto-computed when inventory reaches zero across all parts --- ## Where Status Appears in PromoSync ### Product Search The search form includes a **"By Status"** dropdown filter inside the **Other Filters** panel. This replaces the old "Is Closeout" boolean filter. **Filter options:** Any, Active, Draft, Closeout, Discontinued The default filter is **Active**, so you see only sale-ready products by default. Search results display **color-coded status badges** in the Status column: | Status | Badge Color | |--------|-------------| | Active | Green | | Draft | Grey | | Closeout | Yellow | | Discontinued | Red | ### Product Detail The product detail page displays the status badge next to the Product ID, so you can immediately see the product's life cycle state. ### Inventory and Pricing Sync Products with **discontinued** status are automatically **skipped** during inventory and pricing sync updates. This saves API calls and avoids updating data for products that will never be sold again. --- ## Related Documentation - [Importing Products](/guides/importing-products) — How to search, filter, and import products - [Inventory Settings](/settings/inventory) — Configure inventory sync schedules - [Pricing Settings](/settings/pricing) — Configure pricing strategies - [Product Life Cycle (PSRESTful API)](https://docs.psrestful.com/product-life-cycle) — Technical details on the API-level status field - [Bulk Actions](https://promosync-docs.psrestful.com/guides/bulk-actions): Sync inventory, update prices, manage decorations, and refresh metafields across many products at once from Shopify Admin. # Bulk Actions PromoSync integrates with Shopify's bulk actions menu. Select one or more products in Shopify Admin, open the **Sync Using PSRESTful** dialog, and pick the job you want PromoSync to run in the background. ## Accessing Bulk Actions ### From Shopify Admin 1. Go to **Products** in your Shopify admin 2. Select multiple products using checkboxes 3. Click the **`...`** button in the toolbar 4. Select **Apps** > **Bulk Product Sync** ## Available Bulk Actions PromoSync provides ten bulk operations: | Action | What it does | Email report? | |--------|--------------|:-------------:| | **[Update Inventory](/guides/bulk-actions/update-inventory)** | Pulls fresh stock levels from suppliers. | Yes | | **[Update Prices](/guides/bulk-actions/update-prices)** | Recalculates the visible Shopify variant price using your current pricing settings. | Yes | | **[Update Metafields](/guides/bulk-actions/update-metafields)** | Rewrites all PromoSync data on the selected products without re-importing them. Use this after changing a pricing formula or a metafield definition. | Yes | | **[Add New Variants](/guides/bulk-actions/add-new-variants)** | Detects new supplier colors or sizes and adds them to existing Shopify products. Never deletes variants. | Yes | | **Disable Price Updates** | Prevents automatic price syncing for the selected products. | No | | **Enable Price Updates** | Re-enables automatic price syncing. | No | | **[Apply Default Location Decorations](/guides/bulk-actions/apply-default-decorations)** | Stamps your tag-based default decoration rules onto products that match. Useful for blanks. | No | | **[Link to PromoSync](/guides/bulk-actions/link-to-promosync)** | Registers existing or duplicated Shopify products so future syncs skip redundant calls. | No | | **[Onboard to PromoSync](/guides/bulk-actions/onboard-to-promosync)** | Onboards pre-existing Shopify products (Update Metafields plus Link to PromoSync) in a single tracked job. | Yes | | **[Enrich Location Decorations](/guides/bulk-actions/enrich-location-decorations)** | Wires each decoration on a product to its Setup Fee and Run Charge product, so checkout charges the imprint correctly. | Yes | Every action above with a link has a dedicated walkthrough. The two price-toggle actions are covered in detail below. Most jobs run asynchronously and send a summary email when they finish. Configure who receives those emails in your shop settings under **Notification Contacts**. ## Update Inventory Pull fresh stock levels from suppliers and write them to the selected products' Shopify location. See the [Update Inventory walkthrough](/guides/bulk-actions/update-inventory) for prerequisites, how supplier rate limits affect a run, and the email report fields. ## Update Prices Recalculate the visible Shopify variant price using your current pricing settings. See the [Update Prices walkthrough](/guides/bulk-actions/update-prices) for prerequisites, the visible-versus-hidden price distinction, and the interaction with **Disable Price Updates**. ## Update Metafields Refresh all PromoSync data on the selected products without re-importing them. This is the action to reach for after you change a pricing formula or metafield definition and want existing products to reflect the change. See the [Update Metafields walkthrough](/guides/bulk-actions/update-metafields) for prerequisites, how variants get matched up, and why a product might be skipped. ## Add New Variants Find supplier colors and sizes your products are missing and add them, without touching the variants that are already there. See the [Add New Variants walkthrough](/guides/bulk-actions/add-new-variants) for prerequisites, color-split shop behavior, and the email report fields. ## Disable Price Updates Prevent automatic price syncing for selected products. Products will keep their current prices and won't be affected by future price sync operations. ### When to Use - You've set custom prices you want to preserve - Supplier pricing data is temporarily unreliable - Running a promotion with manual pricing ### How to Disable Price Updates 1. Select products to exclude from price syncing 2. Choose **Disable Price Updates** from the action dropdown 3. Confirm the action ## Enable Price Updates Re-enable automatic price syncing for products that previously had price updates disabled. ### When to Use - Promotion period has ended - Supplier pricing data is corrected - You want to resume automatic pricing ### How to Enable Price Updates 1. Select products to re-enable price syncing for 2. Choose **Enable Price Updates** from the action dropdown 3. Confirm the action ## Apply Default Location Decorations Apply your configured tag-based decoration rules to selected products. Use this when you sell blanks from a supplier that doesn't publish decoration data and you want to stamp your own house defaults onto matching products. See the [Apply Default Decorations walkthrough](/guides/bulk-actions/apply-default-decorations) for the rule setup, how merging works (existing data is preserved), and how to verify the result. ## Link to PromoSync Register the selected Shopify products (including ones you duplicated) with PromoSync's backend so future syncs skip redundant API calls. Previously called **Sync Duplicate Products**. See the [Link to PromoSync bulk-action walkthrough](/guides/bulk-actions/link-to-promosync) for when to pick the selected-products version over the all-products one on the dashboard, or the [Link to PromoSync main guide](/guides/link-to-promosync) for the deeper explanation of what gets registered and why. ## Onboard to PromoSync Bring pre-existing Shopify products under PromoSync's management. Behind the scenes this runs **Update Metafields** followed by **Link to PromoSync** as a single tracked job, skipping products that are already fully onboarded. See the [Onboard to PromoSync walkthrough](/guides/bulk-actions/onboard-to-promosync) for prerequisites, the recommended follow-up actions after onboarding, and the email report fields. ## Enrich Location Decorations Wire each decoration on a product to its Setup Fee and Run Charge product so checkout can charge the imprint correctly. Run this after importing decorated products, and after each time you create or refresh decoration products via [Decoration Listings](/guides/decoration-listings). See the [Enrich Location Decorations walkthrough](/guides/bulk-actions/enrich-location-decorations) for prerequisites and how to read the "unmatched decorations" section of the email. ## Bulk Action Limits ### Processing Limits | Action | Max Products | Max Variants | |--------|--------------|--------------| | Update Inventory | 500 | 10,000 | | Update Prices | 500 | 10,000 | | Update Metafields | 500 | 10,000 | | Add New Variants | 500 | 10,000 | | Disable Price Updates | 1,000 | N/A | | Enable Price Updates | 1,000 | N/A | | Apply Default Location Decorations | 500 | 10,000 | | Link to PromoSync | 500 | 10,000 | | Onboard to PromoSync | 500 | 10,000 | | Enrich Location Decorations | 500 (or all store-wide) | 10,000 | For larger operations, actions are queued and processed in batches. ### Rate Limiting Bulk actions respect Shopify and supplier rate limits: - Shopify: 40 requests/second - Suppliers: Varies by supplier - PSRESTful: Based on your plan Actions automatically throttle to stay within limits for **Shopify**. PSRESTful plans limit the number of API calls available per day/month. ## Best Practices ### 1. Start Small Test with a few products before running large bulk operations. ### 2. Schedule During Off-Peak Run large operations during low-traffic periods. ### 3. Review Results Always check the results summary for errors. ### 4. Backup Important Products Consider exporting critical products before bulk changes. ### 5. Use Filters Narrow your selection to only affected products. ## Troubleshooting ### Action Times Out For large operations: 1. Reduce the number of selected products 2. Try during off-peak hours 3. Contact support for assistance ### Partial Completion If an action partially completes: 1. Review the error summary 2. Fix issues with failed products 3. Retry only the failed products ### Unexpected Results If results don't match expectations: 1. Verify your settings before the action 2. Check for recent setting changes 3. Review the action log for details - [Update Inventory](https://promosync-docs.psrestful.com/guides/bulk-actions/update-inventory): Pull fresh stock levels from your suppliers for the selected products and push them to Shopify in one tracked job. # Update Inventory **Update Inventory** asks your suppliers for the current stock of every variant on the selected products and writes those numbers to Shopify. Use it when you want the quantity column on a product to match what the supplier can actually ship today. ## When to Use It - A supplier just sent a restock notification, or an apparel drop is about to land. - A customer reports a product is showing in stock on your storefront when it shouldn't be. - You're about to run a paid campaign and want clean inventory numbers before the traffic arrives. - After a long weekend or a holiday, when supplier stock has shifted. PromoSync also runs scheduled inventory syncs in the background for products with active syncing. **Update Inventory** is the on-demand version for when you don't want to wait for the next scheduled run. ## Before You Run It Each product needs a **supplier link**, the hidden tags PromoSync attaches at import time so it knows which supplier and supplier product the Shopify product came from. (Internally, these are the `psrestful.supplier_code` and `psrestful.product_id` metafields.) | Source of the product | Will this action work? | |---|---| | Imported through PromoSync's Product Search | Yes | | Imported through **Onboard to PromoSync** or **Link to PromoSync** | Yes | | Created by hand in Shopify, never touched by PromoSync | No, the product is skipped | The supplier must also expose an inventory service through PSRESTful. Most major suppliers do; a handful of smaller ones don't, and their products will simply be reported as skipped. ## How to Run It 1. In Shopify Admin, go to **Products**. 2. Tick the checkboxes next to the products you want fresh stock for. You can also filter by vendor or tag and "Select all" to cover a whole supplier in one go. 3. Click the **`...`** button, then **Apps** > **Bulk Product Sync**. 4. In the dropdown, pick **Update Inventory**. 5. Click **Submit**. The dialog closes after a couple of seconds. The job runs in the background and writes the new quantities to your configured Shopify location. Stock numbers are written to the Shopify location you have selected as the PromoSync sync location in your shop settings. If you stock from multiple locations, only that one location is updated. ## What Happens Next You'll get an email titled **"Inventory Update Results"** when the job finishes. It includes: - **Total attempted**, how many products PromoSync looked at. - **Updated**, how many products had at least one variant's quantity changed. - **Skipped**, products PromoSync couldn't process (no supplier link, or the supplier doesn't offer inventory). - **Failed**, products that hit an error during the supplier call or the Shopify write. Each one is listed with a direct link to it in Shopify Admin. - **Processing time**, how long the job took. The email goes to the notification contacts configured in your shop settings. ## Why Something Might Be Skipped | What the email says | What it means | What to do | |---|---|---| | "No supplier link" | The product is missing `psrestful.supplier_code` and/or `psrestful.product_id`. | Run **Onboard to PromoSync** on the product first. | | "Supplier inventory not available" | The supplier doesn't expose an inventory service. | Manage stock for that supplier's products manually, or check whether the supplier has been added to PSRESTful's inventory list. | | "Variant discontinued" | The supplier no longer carries that color/size. | Decide whether to keep the variant at 0 or remove it from Shopify. | | "Variant not found" | The supplier doesn't recognize the variant's part ID or SKU. | Check the variant's `psrestful.part_id` metafield and SKU against the supplier's catalog. | ## Common Questions ### Will this oversell or undersell what I've reserved? Update Inventory writes the supplier's available quantity to Shopify. If you reserve stock for in-progress orders inside Shopify's own inventory system, those reservations are handled by Shopify, not by PromoSync. ### What if the supplier is rate-limiting my requests? If PSRESTful hits a supplier or quota limit mid-batch, the job stops and you'll get a failure email explaining what was reached. Wait for the limit to reset (usually a few minutes to a few hours), then re-run on the products that didn't finish. ### Does this update prices too? No. Use [Update Prices](/guides/bulk-actions/update-prices) for that. ### Does this update the hidden PromoSync data behind tier pricing? No. Use [Update Metafields](/guides/bulk-actions/update-metafields) when you've changed pricing settings and want the tier pricing block on your product pages to reflect them. ### Can I schedule this instead of running it manually? PromoSync runs scheduled inventory syncs automatically for products with syncing enabled. The bulk action is the on-demand version for when you need an immediate refresh on a specific selection. - [Update Prices](https://promosync-docs.psrestful.com/guides/bulk-actions/update-prices): Recalculate the visible Shopify variant price for the selected products using your current PromoSync pricing settings. # Update Prices **Update Prices** recalculates the visible variant price in Shopify for the selected products using your current PromoSync pricing settings (markup, tier pricing, cost basis, rounding rules) and the latest cost data from the supplier. This is the action to reach for when you want the *price column in Shopify* to reflect your latest pricing setup, not just the hidden PromoSync data behind tier pricing. **Visible vs hidden price.** The price you see in Shopify's variant editor is the *visible* price; that's what this action updates. The *hidden* PromoSync data that powers the tier pricing block on your product page is a separate set of metafields, updated by [Update Metafields](/guides/bulk-actions/update-metafields). After a pricing-formula change you usually want to run both. ## When to Use It - You changed your pricing formula, markup, or cost basis in PromoSync settings and want existing products' visible prices to reflect it. - A supplier raised or lowered cost pricing and you want to pass the change through. - You're switching pricing tiers (for example, moving a customer or a sub-store to a different price list). - You imported products months ago and want to confirm the visible price still matches your current formula. ## Before You Run It ### 1. Products Need a Supplier Link Each product needs the PromoSync metafields PromoSync added at import time: `psrestful.supplier_code`, `psrestful.product_id`, and `psrestful.extra_id`. Each variant needs `psrestful.part_id`. | Source of the product | Will this action work? | |---|---| | Imported through PromoSync's Product Search | Yes | | Imported through **Onboard to PromoSync** | Yes | | Connected via [Connecting Existing Products](/guides/connecting-existing-products) and then **Link to PromoSync** | Yes | | Created by hand in Shopify, never touched by PromoSync | No, the product is skipped | If a product is missing the link, run **Onboard to PromoSync** on it first, then come back. ### 2. Price Updates Must Be Allowed on the Product PromoSync respects a per-product opt-out for price syncing. If a product has had price updates disabled (via the **Disable Price Updates** bulk action), this action will skip it on purpose. To bring it back into the formula, run **Enable Price Updates** on it first. ### 3. A Pricing Formula Should Be Configured If your shop has no pricing formula set up, PromoSync has nothing to apply and variants will be left alone. Configure your pricing rules in PromoSync shop settings before running the action. ## How to Run It 1. In Shopify Admin, go to **Products**. 2. Tick the checkboxes next to the products you want to reprice. 3. Click the **`...`** button, then **Apps** > **Bulk Product Sync**. 4. In the dropdown, pick **Update Prices**. 5. Click **Submit**. The job runs in the background. Variants are repriced in batches that respect Shopify's rate limit. ## What Happens Next You'll get an email titled **"Price Update Results"** when the job finishes. It includes: - **Total attempted**, how many products PromoSync looked at. - **Updated**, how many products had at least one variant repriced. - **Skipped**, products PromoSync couldn't process (no supplier link, price updates disabled, or no usable supplier cost data). - **Failed**, products that hit an error during the supplier call or the Shopify write. Each one is listed with a direct link to it. - **Processing time**, how long the job took. A separate admin-only error notification is sent if the job encountered any failed products, so you can investigate without having to scroll the user-facing summary. ## Why Something Might Be Skipped | What the email says | What it means | What to do | |---|---|---| | "No PSRESTful product" | The product is missing the supplier-link metafields. | Run **Onboard to PromoSync** on the product first. | | "Price updates disabled" | Someone ran **Disable Price Updates** on the product. | Run **Enable Price Updates** if you want pricing to flow again. | | "Discontinued" | The supplier marked the product as discontinued. | Decide whether to keep selling existing stock at the current price or unpublish the product. | | "Part not found" | The supplier doesn't recognize the variant's part. | Check the variant's `psrestful.part_id` metafield and SKU against the supplier's catalog. | | "No price produced" | Your pricing formula returned nothing for that variant (typically a missing cost or a rule that excludes it). | Review your pricing settings; confirm the supplier returned a cost for the variant. | ## Common Questions ### What about Compare-At prices? Compare-At pricing isn't part of this action. ### Does this update the tier pricing block on my product pages? No, that's read from hidden PromoSync metafields. Run [Update Metafields](/guides/bulk-actions/update-metafields) after a pricing-formula change so the storefront tier pricing block matches the new visible price. ### Will it overwrite a price I edited by hand? Yes. If a variant is in scope (it has the metafields and price updates are not disabled), the visible price is replaced with whatever your current formula produces. To preserve a hand-edited price, run **Disable Price Updates** on that product first. ### How do I exclude a few products from this action permanently? Run **Disable Price Updates** on them. They'll still be selectable in the bulk dialog, but **Update Prices** will skip them every time until you re-enable. ### How is this different from re-importing the product? A re-import would create a new product (or fail because the product already exists). **Update Prices** keeps the same Shopify product (same product ID, URL, handle, and any hand-edited copy) and just refreshes variant prices. ### Can I schedule this? PromoSync supports scheduled price syncs. See [Update Prices settings](/settings/update-prices) if you want pricing to refresh on a cadence instead of on demand. - [Update Metafields](https://promosync-docs.psrestful.com/guides/bulk-actions/update-metafields): Refresh all PromoSync data on your products after changing a setting (like a pricing formula) without having to re-import them. # Update Metafields **Update Metafields** refreshes all of the PromoSync data attached to your products and their variants *without* re-importing the product. That hidden PromoSync data is what powers the tier pricing block on your product pages, the minimum order quantity, the decoration selector, the cart line-item details, and anything else that reads from PromoSync-managed fields. When you change a setting in PromoSync (for example, your pricing formula), the new value doesn't apply to products you've already imported until you run this action. **What's a "metafield"?** Think of it as a hidden tag attached to a product or variant. Shopify can't show it on its own, but apps and theme code can read it. PromoSync stores everything it needs to know about a product (supplier code, decoration list, tier prices, minimum order quantity, gallery images) in these hidden tags. ## When to Use It - You changed your pricing formula in PromoSync settings, and you want the new prices to show up in the tier pricing block on existing products. - You changed a metafield definition (renamed something, added a new field) and want existing products to get the new shape. - A supplier updated their product data (changed a description, fixed a sizing chart, added a new image) and you want the changes pulled in. - You're troubleshooting a storefront feature (like tier pricing or the decoration selector) that seems to be reading stale data. This action doesn't change the visible variant price you see in Shopify's product editor. That's controlled by **Update Prices**. This action updates the *hidden* PromoSync price data that the tier pricing block reads on your product pages. ## Before You Run It Your products need a **supplier link**, a hidden tag PromoSync attaches to every product it imports so it knows which supplier product the Shopify product came from. (Internally this is the `psrestful.extra_id` metafield.) | Source of the product | Has a supplier link? | Will this action work? | |---|---|---| | Imported through PromoSync's Product Search | Yes | Yes | | Imported through "Onboard to PromoSync" or "Link to PromoSync" | Yes | Yes | | Has a PromoSync `supplier_code` and `product_id` but no `extra_id` | PromoSync can usually figure it out | Probably yes | | Created by hand in Shopify, never touched by PromoSync | No | No, the product will be skipped | If a product doesn't have a supplier link, run **Onboard to PromoSync** on it first, then come back. ## How to Run It 1. In Shopify Admin, go to **Products**. 2. Tick the checkboxes next to the products whose data you want to refresh. You can also filter and "Select all" to refresh a whole collection or supplier. 3. Click the **`...`** button, then **Apps** > **Bulk Product Sync**. 4. In the dropdown, pick **Update Metafields**. 5. Click **Submit**. The window closes after a couple of seconds. The job runs in the background and emails you when it's done. Updating metafields for your entire catalog at once is fine, but it can take an hour or more for a large store. If you only changed a setting that affects one supplier, filter by that supplier first so you're not waiting on products that don't need it. ## What Happens Next You'll get an email titled **"Metafield Update Results"**. It includes: - **Total attempted**, how many products PromoSync looked at. - **Updated**, how many products were refreshed. - **Skipped**, products PromoSync couldn't process (almost always because they don't have a supplier link). - **Failed**, products that hit an error. Each one is listed with a direct link to it in Shopify Admin. - **Processing time**, how long the job took. The email goes to the notification contacts you've set up in your shop settings. ## How Variants Get Matched Up Each Shopify variant needs to be paired with the matching variant in the supplier's catalog so the right data is written to it. PromoSync tries three ways, in order: 1. **By part ID.** Every variant PromoSync imports gets a hidden tag with the supplier's part ID. If that tag is present, the match is exact. 2. **By SKU.** If the part ID tag is missing, PromoSync checks whether the variant's SKU matches a part ID from the supplier. 3. **By color and size.** As a last resort, PromoSync matches on the color and size labels. This isn't perfect, but it handles most cases where you've edited a SKU by hand. If none of those match, that variant is left alone (the rest of the product is still updated). ## Why Something Might Be Skipped | What the email or log says | What it means | What to do | |---|---|---| | "could not determine extra_id" | No supplier link, and PromoSync can't figure out which supplier product this is. | Run **Onboard to PromoSync** on the product first. | | "No metafields to update" | The supplier returned no usable data for this product. | Open the product on the supplier's catalog and confirm it's still active. | If a variant was left alone but the rest of the product was updated, it's because none of the three matching methods (part ID, SKU, color and size) worked for that variant. The most common cause is a SKU you edited by hand to something the supplier wouldn't recognize. Either restore the original SKU, or accept that PromoSync-managed data won't be refreshed for that one variant. ## Common Questions ### Will this overwrite my custom prices? The *visible* price you see in Shopify's variant editor is not changed by this action. That's controlled by **Update Prices**. The *hidden* PromoSync price data (used by tier pricing on the product page) is overwritten with whatever your current pricing formula produces. ### Will it overwrite my custom product description or title? No. This action only writes to PromoSync's own metafields. Your product description, title, tags, and SEO settings are not touched. ### Will it touch images? No. This action does not upload or remove any product images. ### What if my supplier link is broken? The product will be skipped and listed in the email with a clear reason. Run **Onboard to PromoSync** on the skipped products to rebuild the link, then run **Update Metafields** again. ### How is this different from re-importing the product? A re-import would create a new product (or fail because the product already exists). **Update Metafields** keeps the same Shopify product (same product ID, same URL, same handle, same hand-edited title or description) and just refreshes the hidden PromoSync data. ### How is this different from Add New Variants? [Add New Variants](/guides/bulk-actions/add-new-variants) finds variants that *don't exist yet* on your Shopify product and adds them. **Update Metafields** refreshes the data on variants that *are already there*. You can run them back to back if you want both. - [Add New Variants](https://promosync-docs.psrestful.com/guides/bulk-actions/add-new-variants): Detect colors and sizes a supplier has added since you imported a product, and add them to your existing Shopify product without touching what is already there. # Add New Variants **Add New Variants** finds colors and sizes your supplier has added *after* you imported a product, and adds them to your existing Shopify product. Nothing on your product is deleted or replaced. Only the missing variants are added. If you imported a polo with 8 colors and your supplier later added Forest Green, this action adds Forest Green to your existing product with its own price, SKU, image, and PromoSync data, leaving the other 8 colors alone. ## When to Use It - A supplier added new colors or sizes to a style you already sell. - A variant you needed was unavailable at import time (out of stock, not yet released) and now it is. - You discover one of your products is missing variants that the supplier's catalog clearly shows. Don't use this to fix prices or update existing variants. Use [Update Metafields](/guides/bulk-actions/update-metafields) for that. **Add New Variants** only adds, it never touches variants that are already there. ## Before You Run It Your products need a **supplier link**, a hidden tag PromoSync attaches to every product it imports so it knows which supplier product the Shopify product came from. (Internally this is the `psrestful.extra_id` metafield, but you don't need to think about it.) | Source of the product | Has a supplier link? | Will this action work? | |---|---|---| | Imported through PromoSync's Product Search | Yes | Yes | | Imported through "Onboard to PromoSync" or "Link to PromoSync" | Yes | Yes | | Created by hand in Shopify, never touched by PromoSync | No | No, the product will be skipped | If a product doesn't have a supplier link, run **Onboard to PromoSync** on it first, then come back. ### One Extra Requirement for "Color-Split" Stores If your store splits each product into one Shopify product per color (the "by color" variant grouping setting), this action only adds variants whose color matches that specific Shopify product. So adding *Forest Green* to your "Acme Polo - Navy" product won't happen. You would get a new "Acme Polo - Forest Green" instead, but only after running an import. This action does not create new color-split products. ## How to Run It 1. In Shopify Admin, go to **Products**. 2. Tick the checkboxes next to the products you want to check for new variants. You can also use Shopify's filter and "Select all". 3. Click the **`...`** button, then **Apps** > **Bulk Product Sync**. 4. In the dropdown, pick **Add New Variants**. 5. Click **Submit**. The window closes after a couple of seconds and the job runs in the background. You can close Shopify Admin and go do something else. You'll get an email when it's done. First time? Run it on 2 or 3 products you know have missing variants, so you can verify the result before turning it loose on hundreds of products. ## What Happens Next You'll get an email titled **"Add New Variants Results"**. It includes: - **Total attempted**, how many products PromoSync looked at. - **Products with new variants**, how many products got at least one new variant added. - **Skipped**, products that had no new variants (this is normal and good). - **Failed**, products that hit an error. Each failed product is listed with a direct link to it in Shopify Admin. - **The full list of variants that were added**, with product title, color, size, and SKU. Quick to scan to confirm the additions look right. The email goes to the notification contacts you've set up in your shop settings. ## Why Something Might Be Skipped | What the email or log says | What it means | What to do | |---|---|---| | "no `extra_id` metafield" | The product doesn't have a supplier link. PromoSync never imported it. | Run **Onboard to PromoSync** on it first, then run this action again. | | "No new variants" | Your supplier hasn't added anything since you imported the product. Nothing to do. | Nothing. This is the expected outcome most of the time. | | "No color-matching new variants" (color-split stores only) | The supplier added variants, but none of them match this product's specific color. | Check whether you need to import a new color as its own product. | | "color [X] not linked to any metaobject" | The new variant's color doesn't exist as a swatch in your store yet. | Add a color swatch for that color in **Shopify Admin** > **Settings** > **Custom data** > **Metaobjects**, then run the action again. | ## Common Questions ### Will this overwrite my existing variants? No. This action only *adds* variants that don't exist yet. Your existing variants, including any prices, SKUs, or images you've customized, are not touched. ### Will it change my product images? No, your existing product-level images are left alone. The new variants get their own images from the supplier, and those new images are uploaded to your store. ### How does it know which variants are "new"? PromoSync looks at every variant on your Shopify product and checks its part ID (a unique code the supplier uses for each color/size combination). Anything in the supplier's catalog whose part ID isn't already on the product is treated as new. ### What if I select 1,000 products? The job will take longer, but it will still finish. Expect roughly an hour for 1,000 products on a typical store. The email comes when it's all done. ### Can I undo it if I don't like the result? Not as a single click. You would need to delete the new variants by hand from each Shopify product. Run on a small batch first to be sure. ### Does this update prices on existing variants? No. To refresh prices, use **Update Prices** (for the visible Shopify price) or [Update Metafields](/guides/bulk-actions/update-metafields) (for the PromoSync pricing data). - [Apply Default Decorations](https://promosync-docs.psrestful.com/guides/bulk-actions/apply-default-decorations): Stamp your tag-based default decoration rules onto selected products. Useful for suppliers that ship blanks without decoration data. # Apply Default Location Decorations Some suppliers (especially apparel suppliers selling blanks) don't tell you what kinds of decoration the product supports. So when you import a polo from one of those suppliers, the polo arrives in Shopify without any decoration data, and your storefront decoration block has nothing to show. **Apply Default Location Decorations** fixes this. You teach PromoSync your house defaults ("for every embroidered polo we sell, the default decoration location is Front Chest and the method is Embroidery"), and this action stamps those defaults onto products that match. The matching is by **product tags**. You tell PromoSync which tags a rule applies to, and any product carrying one of those tags gets the rule's decoration defaults. ## When to Use It - You imported blanks from a supplier (SanMar, S&S, Alphabroder, etc.) and the products have no decoration data. - You want every "embroidered" polo to come with a Front Chest plus Embroidery default, every "screen printed" tee to come with a Full Front plus Screen Print default, and so on. - You added a new tag-based default rule and want to apply it to products you've already imported. This action does *not* overwrite decoration data that's already on a product. If the supplier already sent decoration info, or you've already applied defaults, this action only adds anything that's missing, it never replaces what's there. ## Before You Run It ### 1. Turn On the Default Decorations Feature In PromoSync, open **Shop Settings** and turn on **Default Location Decorations**. (If this is off, the action will run but won't change anything.) Once it's on, a new tab called **Location Rules** appears in the shop settings sidebar. ### 2. Create at Least One Rule Open **Shop Settings** > **Location Rules** and click **Add Rule**. See the [Location Rules settings page](/settings/location-rules) for the form screenshots, JSON field reference, and tag/priority strategy. The fields, in brief: | Field | What it does | |---|---| | **Name** | A label for you. PromoSync never shows it to customers. | | **Tags** | Comma-separated list of product tags this rule applies to. Matching is case-insensitive and exact ("polo" matches "Polo" but not "polo-shirt"). | | **Priority** | If a product matches more than one rule, the higher-priority rule wins. Higher numbers win ties. | | **Is active** | Uncheck to disable a rule without deleting it. | | **Location decorations** | The decoration data itself, as a JSON list of locations and decorations. The form's **Show JSON example** button pastes a working example you can edit. | Save the rule. Repeat for each tag-and-decoration combination you want to support. ### 3. Make Sure Your Products Have the Right Tags The rule only fires when a product's tags include at least one tag from the rule. So if your rule's tags are `polo, embroidered`, make sure your polo products are tagged with `polo` or `embroidered`. You can add tags during import, edit them by hand in Shopify, or use Shopify's bulk-tag tools. ## How to Run It 1. In Shopify Admin, go to **Products**. 2. Tick the checkboxes next to the products you want to apply defaults to. Tip: filter by the tag that matches your rule, then "Select all". 3. Click the **`...`** button, then **Apps** > **Bulk Product Sync**. 4. In the dropdown, pick **Apply Default Location Decorations**. 5. Click **Submit**. The job runs in the background. This action does not send an email summary. To verify it worked, open one of the products in Shopify Admin and scroll to its metafields. You should see `psrestful.location_decorations` populated with the rule's data. Or just check your storefront: load the product page and confirm the decoration block shows the expected locations. ## How Merging Works This is the part that confuses people, so a clear example. Say the product already has this decoration data on it (from the supplier, or from a previous run): - **Front Chest**, Embroidery And the matching rule says: - **Front Chest**, Embroidery, Screen Print - **Back**, Embroidery After running the action, the product will have: - **Front Chest**, Embroidery (kept from existing data), Screen Print (added from the rule) - **Back**, Embroidery (added from the rule) The plain-English rules: 1. **If a location is already on the product, it stays.** PromoSync never removes a location you already have. 2. **If a location is in the rule but not on the product, it's added.** 3. **Inside a location, the same rule applies to each decoration method.** Existing methods stay, new methods from the rule are added. ## Why Something Might Be Skipped | What's happening | What it means | What to do | |---|---|---| | The product's tags don't match any rule. | No rule fires, so there's nothing to apply. | Check the product's tags and confirm at least one matches a rule's tag list. | | You have rules, but **Default Location Decorations** is turned off in shop settings. | The action checks the toggle and silently does nothing when it's off. | Turn the toggle on in **Shop Settings**. | | The product is already complete. | Every location in the rule is already on the product. | No action needed, this is the expected outcome on re-runs. | ## Common Questions ### What format does the "Location decorations" field use? JSON, a list of locations, each with its own list of decorations. The rule form includes a working example you can copy and edit. The shape matches what the storefront decoration block expects to read. ### Can a product match more than one rule? Only one rule fires per product, the highest-priority active rule whose tags match. Ties are broken by which rule was created most recently. ### What if I want a rule to apply to *every* product, regardless of tags? Add a tag like `all` to every product (Shopify's bulk-edit makes this fast) and put `all` in the rule's tags. There is no built-in "apply to everything" option. ### Will this break the supplier's own decoration data? No. If the supplier already sent decoration data for a location, that data is kept as-is. The rule only fills in locations the supplier left out. ### Does this work for new imports automatically? Some suppliers' import flows can apply default rules automatically as part of the import. This action is what you run when you've already imported products and need to apply (or re-apply) the defaults after the fact. ### Why isn't there an email summary? This action is fast and usually run on a small batch right after creating or changing a rule. If you want to confirm it ran, open one of the affected products and check its decoration metafield. ### I applied defaults but checkout isn't charging Setup Fee. Decoration *data* alone isn't enough. Checkout needs each decoration to be wired to the matching Setup Fee and Run Charge products in your store. See [Enrich Location Decorations](/guides/bulk-actions/enrich-location-decorations). - [Link to PromoSync](https://promosync-docs.psrestful.com/guides/bulk-actions/link-to-promosync): Register the selected Shopify products with PromoSync's backend so future syncs skip redundant API calls. # Link to PromoSync (Bulk Action) The **Link to PromoSync** bulk action registers the **products you select** in Shopify's products list with PromoSync's backend. Once registered, every subsequent sync (inventory, prices, metafields) can target each product directly instead of rediscovering it from the catalog. This action was previously called **Sync Duplicate Products**. Looking for the version that processes your **whole catalog** in one go? Open **Apps** > **PromoSync** > **Quick Sync** and use the **Link to PromoSync** card there. The bulk action documented here only touches the products you select in Shopify's product list. ## When to Use This Version - Right after **duplicating a single product** in Shopify (Products > ... > Duplicate). The copy inherits PSRESTful metafields but has a new Shopify ID PromoSync doesn't know about yet. - After [Connecting Existing Products](/guides/connecting-existing-products) writes PSRESTful metafields onto a known batch. - Whenever you want to register a specific selection without scanning the rest of your catalog. For routine maintenance, periodic safety nets, or bulk Matrixify imports across the whole store, use the **Quick Sync** card on the PromoSync dashboard instead. ## How to Run It 1. In Shopify Admin, go to **Products**. 2. Tick the products you want to register. 3. Click the **`...`** button, then **Apps** > **Bulk Product Sync**. 4. In the dropdown, pick **Link to PromoSync**. 5. Click **Submit**. The job runs in the background. Products that are already in PromoSync's registry are quietly ignored, so re-running is safe and cheap. ## Read This Next For the full explanation of what gets registered, how `extra_id` is resolved, troubleshooting for "could not determine extra_id" errors, and when each trigger is the right one, see the main guide: [Link to PromoSync](/guides/link-to-promosync) ## Related - [Connecting Existing Products](/guides/connecting-existing-products): the workflow for bringing pre-existing Shopify products under PromoSync's automation. - [Onboard to PromoSync](/guides/bulk-actions/onboard-to-promosync): runs **Update Metafields** plus **Link to PromoSync** in one tracked job. - [Bulk Actions](/guides/bulk-actions): overview of every PromoSync bulk action. - [Onboard to PromoSync](https://promosync-docs.psrestful.com/guides/bulk-actions/onboard-to-promosync): Bring pre-existing Shopify products under PromoSync's management by populating their PSRESTful metafields and registering them with the backend, in one tracked job. # Onboard to PromoSync **Onboard to PromoSync** takes pre-existing Shopify products that weren't imported through PromoSync and brings them under PromoSync's management in one tracked job. Internally it runs [Update Metafields](/guides/bulk-actions/update-metafields) and then [Link to PromoSync](/guides/bulk-actions/link-to-promosync) on each candidate, so the product ends up with the hidden PromoSync data filled in *and* registered in the backend. After onboarding, the products are ready for the rest of the PromoSync workflow: **Update Inventory**, **Update Prices**, **Add New Variants**, the storefront tier pricing block, and scheduled syncs. Looking for the version that scans your **whole catalog** for unboarded products? Open **Apps** > **PromoSync** > **Quick Sync** and use the **Onboard to PromoSync** card there. The bulk action documented here only processes the products you select in Shopify's product list. ## When to Use It - You created products by hand in Shopify (or imported them through another tool like Matrixify) and now want PromoSync to manage them. - You bulk-imported a CSV of products before realising they need PromoSync coverage. - After [Connecting Existing Products](/guides/connecting-existing-products) attaches PSRESTful metafields, but only the supplier code and product ID are present (not the `extra_id` or per-variant `part_id`). ## Before You Run It Each product needs, at minimum, the two identifying metafields so PromoSync knows which supplier product to onboard it as: - `psrestful.supplier_code` (product-level) - `psrestful.product_id` (product-level) If those are present, PromoSync can resolve the rest (`psrestful.extra_id` at the product level and `psrestful.part_id` on each variant) during onboarding. If a product doesn't have these two metafields, see [Connecting Existing Products](/guides/connecting-existing-products) for the workflow that attaches them. Products that already have **all three** of the product-level metafields (supplier_code, product_id, and extra_id) are treated as fully onboarded and are filtered out of this job. Running the action again is therefore safe and cheap; only the candidates that still need work are touched. ## How to Run It 1. In Shopify Admin, go to **Products**. 2. Tick the products you want to onboard. You can filter by tag or vendor first and "Select all" to cover a whole supplier in one go. 3. Click the **`...`** button, then **Apps** > **Bulk Product Sync**. 4. In the dropdown, pick **Onboard to PromoSync**. 5. Click **Submit**. The job runs in the background as a single tracked job, even though it does two things under the hood. ## What Happens Next You'll get an email when the job finishes, summarising: - **Candidates found**, how many of the selected products were actually missing onboarding (the rest were filtered out as already-onboarded). - **Metafields updated**, how many candidates had their hidden PromoSync data populated. - **Linked**, how many candidates were registered in the PromoSync backend. - **Failed**, candidates that errored in either phase, with direct links to them in Shopify Admin. If a candidate fails the metafield phase, it still moves into the link phase; both steps are reported independently so you can see exactly where each one stopped. ## Why Something Might Be Skipped | What the email says | What it means | What to do | |---|---|---| | "Already onboarded" (filtered out) | The product already has supplier_code, product_id, and extra_id metafields. | No action needed. | | "Missing supplier_code or product_id" | PromoSync doesn't know which supplier product this is. | Use [Connecting Existing Products](/guides/connecting-existing-products) to attach the metafields, then re-run. | | "Could not determine extra_id" | The supplier code and product ID are set, but PSRESTful couldn't find a matching product. | Check for typos in the metafield values, or confirm the supplier still carries the product. | | "Backend registration failed" | The metafield phase succeeded but the link phase couldn't register the product. | Re-run; if it persists, the backend may be temporarily unreachable. | ## Common Questions ### Is this just "Update Metafields plus Link to PromoSync"? That's what runs under the hood, yes, but as a single tracked job with one email and shared filtering, so you don't have to run them separately and you don't pay for products that are already onboarded. ### What if I already have some products fully imported and others not? Mix freely. Onboard to PromoSync filters out the fully-onboarded ones up front and only touches the candidates that need work. ### Does this set prices or import images? It populates PromoSync's metafields, which include the data the storefront tier pricing block reads. It does not change the *visible* variant price in Shopify (use [Update Prices](/guides/bulk-actions/update-prices) for that) and it does not upload images. ### What's the right order of operations after onboarding? 1. **Onboard to PromoSync** to get the metafields and the registry entry in place. 2. **Update Inventory** to populate fresh stock numbers. 3. **Update Prices** if you want the visible Shopify price to match your formula. 4. **Apply Default Location Decorations** and **Enrich Location Decorations** if you sell decorated products. ### How is this different from re-importing the product? A re-import would create a new product (or fail because the product already exists). Onboard to PromoSync keeps the same Shopify product (same ID, URL, handle, and any hand-edited copy) and fills in the missing PromoSync data around it. - [Enrich Location Decorations](https://promosync-docs.psrestful.com/guides/bulk-actions/enrich-location-decorations): Wire each decoration on a product to its matching Setup Fee and Run Charge product so checkout charges the imprint correctly. # Enrich Location Decorations When a customer adds a decorated product to their cart, your store needs to also charge them for the decoration: a **Setup Fee** and a **Run Charge**, usually as separate line items at checkout. Each of those fees is its own Shopify product behind the scenes ("PCNA, Embroidery, Setup Fee", "PCNA, Embroidery, Run Charge", etc.). They're created once per supplier and reused across every product that offers that decoration. The decoration data on a regular product is just a list of names ("Front Chest, Embroidery"). For checkout to actually charge the right fees, each entry in that list needs to point at the matching Setup Fee and Run Charge products. **Enrich Location Decorations** wires those pointers up. You run it once after importing a batch of products that have decoration data, and any time the underlying Setup Fee or Run Charge products have been recreated. ## When to Use It - You just imported products that have decoration data, and the storefront decoration block shows the decoration list but checkout doesn't charge Setup Fee or Run Charge. - You ran [Decoration Listings](/guides/decoration-listings) for a new supplier, and now you need to wire your existing products up to the new fee products. - Decoration data has changed on the supplier side (new decoration method added, old one renamed), and your products' decoration list is out of sync with the fee products. Run this *after* [Decoration Listings](/guides/decoration-listings). If the Setup Fee and Run Charge products don't exist yet, enrichment has nothing to point at. ## Before You Run It ### 1. Create the Decoration Fee Products for Each Supplier In PromoSync, open the dashboard and click the **Decoration Listings** card. Pick the supplier and click **Create Decoration Products**. This is a one-time step per supplier. It creates one Setup Fee product and one Run Charge product per decoration method the supplier offers. You only need to repeat this when: - You add a new supplier - The supplier adds new decoration methods you want to support See the full walkthrough in [Decoration Listings](/guides/decoration-listings). ### 2. Make Sure Your Products Have Decoration Data The action only does work on products that already have a `psrestful.location_decorations` metafield. Products imported through PromoSync from a supplier that publishes decoration data will have this automatically. Products you've stamped with [Apply Default Location Decorations](/guides/bulk-actions/apply-default-decorations) will also have it. Products without decoration data are skipped silently. ## How to Run It You have two ways to scope the job. ### Option A, Run on Specific Products 1. In Shopify Admin, go to **Products**. 2. Tick the checkboxes next to the products you want to enrich. 3. Click the **`...`** button, then **Apps** > **Bulk Product Sync**. 4. In the dropdown, pick **Enrich Location Decorations**. 5. Click **Submit**. ### Option B, Run on Every Product with Decoration Data If you leave the selection empty, PromoSync walks every product in your store that has a `psrestful.location_decorations` metafield. This is the right choice after you've created decoration products for a new supplier and want everything wired up. Store-wide enrichment can take a while on large catalogs (the job has a 30-minute window before it has to break itself up). For very large stores, run by supplier instead: filter products by the supplier's tag, select all, then run the action. ## What Happens Next You'll get an email titled **"Location Decorations Enrichment Results"**. It includes: - **Products updated**, how many products had their decoration list wired up to fee products. - **Products skipped**, products that didn't need any changes (either no decoration data, or already wired correctly). - **Decorations enriched**, total number of individual decoration entries that got Setup Fee and Run Charge pointers added. - **Decorations unmatched**, decorations on your products that PromoSync couldn't find a matching Setup Fee or Run Charge product for. **This is the section to actually read.** Each row tells you the product, the supplier, and the decoration name that didn't match. Common cause: the matching Create Decorations step hasn't been run for that supplier. The email goes to the notification contacts you've set up in your shop settings. ## How Matching Works For each decoration on each product, PromoSync looks for a Setup Fee and Run Charge product in your store that satisfies two conditions: 1. The fee product has the same supplier code as the product you're enriching. 2. The fee product's handle starts with the same root as the decoration name (e.g. decoration "Laser Engrave" matches handles `laser-engrave-setup-fee` and `laser-engrave-run-charge`). If both conditions match, the fee product's ID is written into the decoration entry. If not, the decoration shows up in the "unmatched" section of the email. ## Why Something Might Be Skipped or Unmatched | What the email or log shows | What it means | What to do | |---|---|---| | Product skipped: "no `psrestful.location_decorations` metafield" | The product has no decoration data to enrich. | Either this product doesn't offer decorations (correct), or it needs default decorations applied first. See [Apply Default Location Decorations](/guides/bulk-actions/apply-default-decorations). | | Product skipped: "location_decorations is not a list" | The decoration data is malformed JSON (rare, usually from a hand-edited metafield). | Open the product's metafields in Shopify Admin and fix or clear the value. | | Decoration unmatched: shows a decoration name and supplier code | No Setup Fee or Run Charge product exists in your store for that supplier and decoration name combination. | Open **Decoration Listings** for that supplier and run **Create Decoration Products**. Then run **Enrich Location Decorations** again. | | Product updated 0 times even though selected | Every decoration was already wired correctly, so there was nothing to change. | No action needed. | ## Common Questions ### Will this break the decoration data already on my products? No. The action only adds Setup Fee and Run Charge pointers to existing decorations. Decoration names, locations, max imprint colors, and other data are left exactly as they were. ### What if a decoration shows up as "unmatched" even though I ran Create Decorations? Two common causes: 1. The decoration name on the product doesn't quite match the fee product's handle (e.g. the supplier renamed "Embroidery" to "Embroidery 1-Color" after you created fees). Re-run **Create Decoration Products** for that supplier. 2. The fee product was deleted by hand from Shopify. Re-run **Create Decoration Products** to recreate it. ### Do I need to run this every time I import new products? Yes, after each import of products with decoration data. The fastest workflow is: import a batch, then run **Enrich Location Decorations** on the same batch. ### Do I need to run this for products with no decorations? No. Those products are skipped automatically. ### Does this change the visible product page in any way? No. The enrichment is invisible to customers. They only notice the difference at checkout, when Setup Fee and Run Charge line items now appear correctly. ### What if I've changed my decoration prices? Edit the Setup Fee or Run Charge product price in Shopify Admin like any other product. You do not need to re-run this action, it only wires up *which* fee product to use, not the price on the fee. - [Decoration Listings](https://promosync-docs.psrestful.com/guides/decoration-listings): Publish supplier setup and run charges, with volume pricing, as Shopify products so you can bill imprint costs accurately in customer orders. # Decoration Listings Decoration Listings publishes a supplier's Possible Decorations (Setup and Run charges) as real Shopify products, with volume pricing tiers as variants. Add them to a cart line alongside the decorated item and the order total reflects the true imprint cost, without running into Shopify's per-product variant limits. ## Why It Exists PromoStandards suppliers describe decoration costs as two pieces: - A **Setup Fee**, charged once per imprint - A **Run Charge**, charged per piece, often with tiered (volume) pricing Shopify does not model these as product attributes. If you tried to encode every decoration option as a variant on the parent product, you would blow past the variant limit on any product with more than a few colors or sizes. Decoration Listings sidesteps that by creating standalone charge products you can attach to the order. ## What Gets Created For the supplier you select, PromoSync creates one product per decoration name, twice over: | Product | Shopify `productType` | Variants | |---------|----------------------|----------| | Setup Fee | `Setup Fee` | Single variant | | Run Charge | `Run Charge` | Single variant | Both products are created with: - **Status:** Active - **Vendor:** the supplier's name - **Product metafields:** `psrestful.charge_type` (`Setup` or `Run Charge`) and `psrestful.supplier_code` Charge products are identified by `productType` (`Setup Fee` or `Run Charge`), not by tags. ### Volume Pricing on Run Charges Run Charge variants carry the `psrestful.part_price_array` (Part Price Array) variant metafield with the supplier's tiered pricing, the same shape PromoSync uses on regular products: ```json [ {"quantityMin": 1, "price": 225}, {"quantityMin": 50, "price": 175}, {"quantityMin": 100, "price": 125} ] ``` Prices are in cents. Storefront widgets and other PromoSync tools read the tiers from this metafield, so the run charge stays a single variant in Shopify regardless of how many price breaks the supplier publishes. ## Creating the Products 1. Open your Shopify admin, then go to **Apps** > **PromoSync** 2. From the main menu, click **Decoration Listings** 3. Pick a supplier from the dropdown. Only suppliers that publish Possible Decorations data are listed. 4. Click **Create Decoration Products** The job runs in the background. You will receive a summary email when it completes, and the new products will appear in your Shopify catalog with the product type and vendor described above. Run the action once per supplier you work with. Re-running it for the same supplier will refresh the existing decoration products with the latest data. ## Using the Charge Products in Orders Once the decoration products exist, you can add them to a cart or draft order alongside the decorated item: 1. Add the customer's chosen apparel/product 2. Add the matching **Setup Fee** product (one per imprint) 3. Add the **Run Charge** product, then set its line price using the tier that matches the order quantity in `part_price_array` Shopify totals the line items normally, so taxes, discounts, and reporting all work without custom code. The Run Charge tier is not applied automatically based on the apparel line quantity. Whoever builds the order (your team, a draft-order automation, or a storefront flow) needs to read `psrestful.part_price_array` and set the correct unit price. Volume pricing is published so the right number is available, not so it is selected for you. ## When to Use It - You quote and invoice decorated apparel and need accurate order totals - You sell through draft orders, quotes, or B2B flows that benefit from itemized charges - You want a single, supplier-aware source for setup and run pricing across staff and tools ## When Not to Use It - You only display decoration options on the storefront (without billing for them). The [Decoration Selector](/guides/theme-extensions/decoration-selector) and `location_decorations` metafield are a better fit. - The supplier does not expose Possible Decorations data. In that case the supplier will not appear in the dropdown. ## How It Fits With Other Features - [Location Rules](/settings/location-rules) assigns decoration locations and methods to your decorated products. Decoration Listings complements this by giving you billable charge products to match. - [Storefront Widgets](/guides/storefront-widgets) and [Theme App Extensions](/guides/theme-extensions) can read the same `psrestful.part_price_array` metafield on Run Charge variants if you want to surface tier pricing on the storefront. ## Troubleshooting ### The supplier I want is not in the dropdown PromoSync only lists suppliers that publish Possible Decorations data via PromoStandards. If a supplier is missing, they likely do not expose decoration pricing through the API. Confirm with the supplier or check the [PromoStandards Service Coverage Matrix](https://api.psrestful.com/docs). ### Run Charge product has no price tiers in `part_price_array` The Run Charge product is created with a single default variant either way, but if the supplier publishes no tier data the `part_price_array` metafield will be empty. Verify the supplier's pricing data in PSRESTful and re-run the action. ### Re-running the action created duplicates Decoration products are matched by `handle` (a slug of the title). If a previous run created products with a different naming convention, you may end up with duplicates. Delete the older versions in Shopify or contact support to reconcile. - [Link to PromoSync](https://promosync-docs.psrestful.com/guides/link-to-promosync): Register existing Shopify products (including ones you duplicated) with PromoSync so future syncs skip redundant Shopify and PSRESTful API calls. # Link to PromoSync **Link to PromoSync** scans the products in your Shopify store that carry PSRESTful metafields, finds the ones PromoSync's backend doesn't yet know about, and registers them. Once a product is registered, every subsequent sync (inventory, prices, metafields) can target it directly instead of rediscovering it from scratch. This action was previously called **Sync Duplicate Products**. ## Why It Exists PromoSync keeps a backend registry that maps each Shopify product to its supplier record (the `extra_id` in PSRESTful). The registry is what lets routine syncs ask: > "Which Shopify products belong to supplier X, and what supplier SKUs do they map to?" If a product carries the right metafields but isn't in the registry, PromoSync has to: 1. Re-scan your Shopify catalog to find it, and 2. Re-resolve its supplier record from PSRESTful. Both are expensive: Shopify API calls count against your store's rate limit, and PSRESTful calls count against your plan's daily/monthly quota. Link to PromoSync registers the missing products once, so the slow path is avoided on every future sync. ## What Counts As a "Duplicate" Any Shopify product that has the PSRESTful identifying metafields but is not in PromoSync's backend registry. The most common ways this happens: - **You duplicated a product in Shopify** (Products → ... → Duplicate). The copy inherits all the metafields from the original, including `psrestful.supplier_code`, `psrestful.product_id`, and `psrestful.extra_id`, but it has a brand-new Shopify product ID that PromoSync has never seen. - **You connected existing products via the [Connecting Existing Products](/guides/connecting-existing-products) workflow.** After Matrixify writes the metafields onto products PromoSync didn't import, those products still need to be registered. - **A previous import partially failed**, leaving products in Shopify that the backend never recorded. In every case, the fix is the same: run Link to PromoSync. ## What Gets Registered For each candidate product, PromoSync needs an `extra_id` (PSRESTful's stable internal ID) before it can register it. The action resolves `extra_id` in this order: 1. Read the `psrestful.extra_id` metafield directly, if present. 2. Otherwise, look it up from PSRESTful using `psrestful.supplier_code` + `psrestful.product_id`. If neither path returns an `extra_id`, the product is skipped and logged. Everything else is grouped into batches and registered in the PromoSync backend. Products that PromoSync already has in its registry are quietly ignored. The action is idempotent: running it twice is safe and cheap on the second run. ## How to Run It You can trigger Link to PromoSync in three places. ### From the PromoSync Dashboard (all products) 1. In Shopify admin, go to **Apps** > **PromoSync**. 2. In the **Quick Sync** section, find the **Link to PromoSync** card. 3. Click **Run Now** and confirm. This processes the entire catalog. Use it as a periodic safety net or after a bulk Matrixify import. ### From Shopify's Products List (selected products) 1. In Shopify admin, open **Products**. 2. Select the products you want to register. 3. Click **More actions** > **Sync Using PSRESTful**. 4. Pick **Link to PromoSync** from the dropdown and click **Submit**. Use this right after duplicating a product in Shopify, or to register a specific batch you just connected. ### From the All-Products Bulk Dialog (filtered or all) The **Sync Products (Filter/All)** dialog (also under **More actions**) lets you target products by vendor or pick "all matching" without paginating through Shopify's UI. Choose **Link to PromoSync** from the action dropdown. ## When to Run It | Scenario | Recommended trigger | |----------|--------------------| | Just duplicated a product in Shopify | Selected-products bulk action | | Just finished the [Connecting Existing Products](/guides/connecting-existing-products) workflow | Selected-products bulk action | | Suspect the backend is out of sync after partial failures | Dashboard **Run Now** (all products) | | Routine maintenance | Dashboard **Run Now** every few weeks, or after large catalog changes | ## What You'll See The job runs in the background and sends a summary email when it finishes. The summary reports how many products were already registered, how many were newly registered, and how many were skipped because no `extra_id` could be resolved. There is no schedule for this action: it always runs on demand. ## Troubleshooting ### Products were skipped with "could not determine extra_id" The product has `psrestful.supplier_code` and `psrestful.product_id` metafields but no `psrestful.extra_id`, and PSRESTful's resolver couldn't find a match for that (supplier, product) pair. Possible causes: - The `supplier_code` or `product_id` values are typos. - The supplier discontinued the product and it's no longer in PSRESTful. - The supplier's data is temporarily unavailable. Fix the metafield values (or remove the dead products) and re-run. ### Nothing happens after I run it If every Shopify product with PSRESTful metafields is already in PromoSync's backend, the action exits without doing anything. That's the success case. Check your most recent sync results to confirm everything still works. ### My duplicated products still aren't syncing After Link to PromoSync registers them, run the relevant follow-up sync (Update Inventory, Update Prices, or Update Metafields) to populate the new copy with current data. ## Related - [Connecting Existing Products](/guides/connecting-existing-products) — the broader workflow for bringing pre-existing Shopify products under PromoSync's automation. - [Bulk Actions](/guides/bulk-actions) — overview of every PromoSync bulk action available from Shopify's products list. - [Storefront Widgets](https://promosync-docs.psrestful.com/guides/storefront-widgets): Build tier pricing tables, decoration selectors, and other storefront widgets using PromoSync metafields and Shopify Liquid templates. # Storefront Widgets PromoSync populates rich metafield data that you can display on your storefront. This guide shows how to implement common widgets using Liquid templates. **Looking for the no-code approach?** PromoSync offers drag-and-drop app extension blocks that require no theme code changes. See [Theme App Extensions](/guides/theme-extensions). ## Overview PromoSync stores product data in Shopify metafields under the `psrestful` namespace. These metafields can be accessed in your theme's Liquid templates to create dynamic displays. ## Tier Pricing Block Display quantity-based pricing tiers on product pages. Tier pricing is stored at the **variant level** in `part_price_array`. ### The Data Structure ```json // variant.metafields.psrestful.part_price_array [ {"quantityMin": 24, "price": 1250}, {"quantityMin": 48, "price": 1100}, {"quantityMin": 144, "price": 950}, {"quantityMin": 288, "price": 825} ] ``` **Note:** Prices are stored as integers in cents for fast and exact calculations (e.g., `1250` = `$12.50`). ### Basic Implementation Add to your product template (`sections/main-product.liquid` or similar): ```liquid {% raw %}{% assign tiers = product.selected_or_first_available_variant.metafields.psrestful.part_price_array.value %} {% if tiers and tiers.size > 0 %}

Volume Pricing

{% assign base_price = tiers.first.price %} {% for tier in tiers %} {% assign savings = base_price | minus: tier.price | times: 100 | divided_by: base_price %} {% endfor %}
Quantity Price Each You Save
{{ tier.quantityMin }}+ {{ tier.price | divided_by: 100.0 | money }} {% if savings > 0 %}{{ savings }}%{% else %}-{% endif %}
{% endif %}{% endraw %} ``` ### Styling the Tier Pricing Table ```css .tier-pricing-widget { margin: 20px 0; padding: 15px; background: #f9f9f9; border-radius: 8px; } .tier-pricing-widget h3 { margin: 0 0 15px 0; font-size: 16px; font-weight: 600; } .tier-pricing-table { width: 100%; border-collapse: collapse; } .tier-pricing-table th, .tier-pricing-table td { padding: 10px; text-align: left; border-bottom: 1px solid #eee; } .tier-pricing-table th { font-weight: 600; font-size: 12px; text-transform: uppercase; color: #666; } .tier-pricing-table td:last-child { color: #2a9d3a; font-weight: 500; } ``` ### Compact Tier Display For a more compact display: ```liquid {% raw %}{% assign tiers = product.selected_or_first_available_variant.metafields.psrestful.part_price_array.value %} {% if tiers and tiers.size > 0 %} {% for tier in tiers %} {{ tier.quantityMin }}+ {{ tier.price | divided_by: 100.0 | money }} {% endfor %} {% endif %}{% endraw %} ``` ## Location Decorations Block Display available decoration methods and locations. ### The Data Structure ```json // product.metafields.psrestful.location_decorations [ { "locationId": 70, "decorations": [ { "default": false, "decorationId": 950, "priceIncludes": false, "decorationName": "Embroidery", "maxImprintColors": 99 }, { "default": true, "decorationId": 951, "priceIncludes": false, "decorationName": "Screen Print", "maxImprintColors": 6 } ], "locationName": "FRONT", "locationRank": 2, "maxDecoration": 0, "minDecoration": 0, "defaultLocation": false, "decorationsIncluded": 0 }, { "locationId": 71, "decorations": [ { "default": false, "decorationId": 950, "priceIncludes": false, "decorationName": "Embroidery", "maxImprintColors": 99 } ], "locationName": "LEFT CHEST", "locationRank": 1, "maxDecoration": 0, "minDecoration": 0, "defaultLocation": true, "decorationsIncluded": 0 } ] ``` ### Implementation ```liquid {% raw %}{% assign locations = product.metafields.psrestful.location_decorations.value %} {% if locations and locations.size > 0 %}

Decoration Options

{% for location in locations %}

{{ location.locationName }}{% if location.defaultLocation %} (Default){% endif %}

{% endfor %} {% endif %}{% endraw %} ``` ### Styling Decorations ```css .decoration-options { margin: 20px 0; } .decoration-locations { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; } .decoration-location { padding: 15px; background: #f5f5f5; border-radius: 8px; } .decoration-location h4 { margin: 0 0 10px 0; font-size: 14px; font-weight: 600; } .location-size { font-size: 12px; color: #666; margin-bottom: 10px; } .decoration-methods { list-style: none; padding: 0; margin: 0; } .decoration-methods li { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #e0e0e0; } .decoration-methods li:last-child { border-bottom: none; } .setup-charge.free { color: #2a9d3a; } ``` ## Product Specifications Block Display detailed product specifications. ### The Data Structure ```json // product.metafields.psrestful.specifications { "value": { "material": "100% Airlume combed and ring-spun cotton", "weight": "4.2 oz", "fit": "Unisex retail fit", "features": [ "Side-seamed", "Tear-away label", "Shoulder-to-shoulder taping" ], "compliance": ["CPSIA Certified", "WRAP Certified"] } } ``` ### Implementation ```liquid {% raw %}{% assign specs = product.metafields.psrestful.specifications.value %} {% if specs %}

Specifications

{% if specs.material %}
Material
{{ specs.material }}
{% endif %} {% if specs.weight %}
Weight
{{ specs.weight }}
{% endif %} {% if specs.fit %}
Fit
{{ specs.fit }}
{% endif %}
{% if specs.features.size > 0 %}

Features

{% endif %} {% if specs.compliance.size > 0 %} {% for cert in specs.compliance %} {{ cert }} {% endfor %} {% endif %} {% endif %}{% endraw %} ``` ## Supplier Information Block Display supplier and brand information. ```liquid {% raw %}{% assign supplier = product.metafields.psrestful.supplier.value %} {% assign brand = product.metafields.psrestful.brand.value %} {% if brand %} {{ brand }} {% endif %} {% if supplier %} from {{ supplier }} {% endif %} ``` ## Inventory Status Widget Show inventory availability with more detail. ### The Data Structure ```json // product.metafields.psrestful.inventory_data { "value": { "totalAvailable": 1250, "lastSync": "2024-01-20T14:30:00Z", "warehouses": [ {"location": "Dallas", "qty": 500}, {"location": "Phoenix", "qty": 450}, {"location": "Atlanta", "qty": 300} ] } } ``` ### Implementation ```liquid {% raw %}{% assign inv = product.metafields.psrestful.inventory_data.value %} {% if inv %} {% if inv.totalAvailable > 100 %} In Stock {{ inv.totalAvailable }} available {% elsif inv.totalAvailable > 0 %} Low Stock Only {{ inv.totalAvailable }} left {% else %} Out of Stock {% endif %} {% if inv.warehouses.size > 1 %}
View warehouse availability
{% endif %} {% endif %}{% endraw %} ``` ## Theme App Extension (Online Store 2.0) For Online Store 2.0 themes, create a theme app extension block. ### Block Schema Create `blocks/tier-pricing.liquid`: ```liquid {% raw %}{% assign tiers = product.selected_or_first_available_variant.metafields.psrestful.part_price_array.value %} {% if tiers and tiers.size > 0 %} {% if block.settings.show_title %}

{{ block.settings.title }}

{% endif %} {% for tier in tiers %} {% endfor %}
{{ block.settings.quantity_label }} {{ block.settings.price_label }}
{{ tier.quantityMin }}+ {{ tier.price | divided_by: 100.0 | money }}
{% endif %} {% schema %} { "name": "Tier Pricing", "target": "section", "settings": [ { "type": "checkbox", "id": "show_title", "label": "Show title", "default": true }, { "type": "text", "id": "title", "label": "Title", "default": "Volume Pricing" }, { "type": "text", "id": "quantity_label", "label": "Quantity column label", "default": "Quantity" }, { "type": "text", "id": "price_label", "label": "Price column label", "default": "Price Each" } ] } {% endschema %}{% endraw %} ``` ## JavaScript Enhancements ### Dynamic Price Calculator Add a quantity-based price calculator: ```javascript // tier-pricing-calculator.js class TierPricingCalculator { constructor(element) { this.element = element; this.tiers = JSON.parse(element.dataset.tiers); this.quantityInput = element.querySelector('[data-quantity-input]'); this.priceDisplay = element.querySelector('[data-price-display]'); this.totalDisplay = element.querySelector('[data-total-display]'); this.bindEvents(); } bindEvents() { this.quantityInput.addEventListener('input', () => this.calculate()); } calculate() { const quantity = parseInt(this.quantityInput.value) || 0; const tier = this.findTier(quantity); if (tier) { // Prices are in cents, convert to dollars const priceInDollars = tier.price / 100; const total = priceInDollars * quantity; this.priceDisplay.textContent = this.formatMoney(priceInDollars); this.totalDisplay.textContent = this.formatMoney(total); } } findTier(quantity) { for (let i = this.tiers.length - 1; i >= 0; i--) { if (quantity >= this.tiers[i].quantityMin) { return this.tiers[i]; } } return this.tiers[0]; } formatMoney(dollars) { return '$' + dollars.toFixed(2); } } // Initialize document.querySelectorAll('[data-tier-calculator]').forEach(el => { new TierPricingCalculator(el); }); ``` ### Usage in Template ```liquid {% raw %}{% assign tiers = product.selected_or_first_available_variant.metafields.psrestful.part_price_array.value %} {% if tiers %} Price: Total: {% endif %}{% endraw %} ``` **Note:** The JavaScript calculator receives prices in cents and should convert to dollars for display. ## Best Practices ### 1. Check for Data Existence Always check if metafield data exists before rendering: ```liquid {% raw %}{% assign tiers = variant.metafields.psrestful.part_price_array.value %} {% if tiers and tiers.size > 0 %} {% endif %}{% endraw %} ``` ### 2. Provide Fallbacks Show graceful fallbacks when data is missing. ### 3. Keep It Fast Avoid complex calculations in Liquid - use JavaScript if needed. ### 4. Mobile-First Design Ensure widgets work well on mobile devices. ### 5. Test with Multiple Products Test widgets with products that have varying amounts of data. ## Troubleshooting ### Widget Not Showing 1. Verify the product has the metafield data 2. Check the metafield namespace (`psrestful`) 3. Ensure the product was synced recently 4. Check for Liquid syntax errors ### Data Not Updating 1. Trigger an inventory sync 2. Check last sync timestamp 3. Verify supplier connection ### Styling Issues 1. Check for CSS conflicts with your theme 2. Use specific selectors to avoid conflicts 3. Test in theme preview mode - [Variant Media Gallery](https://promosync-docs.psrestful.com/guides/variant-media-gallery): Render color-matched, multi-image galleries per Shopify variant using PromoSync's psrestful.gallery metafield. Liquid examples, plan eligibility, and theme integration. # Per-Variant Media Gallery **For merchants:** when the supplier provides multiple photos for a color, PromoSync attaches them all to the matching variant (front, back, side, lifestyle), so shoppers see every angle of the color they picked without leaving the variant. Coverage depends on what the supplier publishes; some products and some colors only ship with a single image. Available on the Trial, Business, and Enterprise plans. The rest of this page is for Shopify theme developers who will render the gallery on the storefront. ## Overview The per-variant media gallery is a curated, ordered list of color-matched images attached to each Shopify product variant. It lives in a single metafield (`psrestful.gallery`) and contains every angle (front, back, side, lifestyle) for the variant's specific color. The first item in the list is the variant's featured image; the rest are additional views of the same color. The feature exists because Shopify's native data model only allows one image per variant. Merchants who sell apparel and other color-keyed products want shoppers to see every angle of the selected color, not just a single hero shot. By writing a list of file references at the variant level, themes can render a complete, color-aware gallery that swaps in lockstep with the variant picker. The images come from the supplier's PromoStandards media feed, surfaced through the [PSRESTful API](https://docs.psrestful.com). At import time, PromoSync picks the color-matched images for each variant using supplier-specific association strategies (a color cascade for SanMar and S&S Activewear, partId-only matching for HIT, and a default cascade for other suppliers). The matched files are then uploaded to Shopify, and their GIDs are attached to the variant. ## Plan eligibility The `psrestful.gallery` variant metafield is populated only on the following PSRESTful plans: | Plan | Variant gallery populated? | Native `variant.featured_image` | |------------|---------------------------|---------------------------------| | Free | No | Yes | | Standard | No | Yes | | Business | Yes | Yes | | Enterprise | Yes | Yes | The gallery is also available during the Trial, so you can evaluate the multi-image experience before committing to a Business or Enterprise plan. On non-eligible plans, variants still receive a single featured image through Shopify's native `variant.featured_image`, so storefronts see no regression; they just don't get the multi-image gallery. Themes that follow the [fallback pattern](#fallback-to-variantfeatured_image) below work correctly on every plan. To upgrade and unlock the gallery, see [`{{upgrade_url}}`](#). ## Data model The gallery is stored as a single metafield on the variant: | Property | Value | |---------------|-----------------------------------------------------------------------| | **Owner** | `PRODUCTVARIANT` | | **Namespace** | `psrestful` | | **Key** | `gallery` | | **Type** | `list.file_reference` | | **Value** | Ordered list of Shopify File references (MediaImage, since the gallery only contains images) | Each item in the list is a Shopify File reference. PromoSync only writes image files, so in practice every entry dereferences to a `MediaImage`. The **first item is always the variant's featured image**, and subsequent items are additional views for the same color, in the canonical PromoStandards order (primary → front → rear → left/right → inside/outside → other). The featured image is duplicated into the gallery on purpose. It lets themes render a single ordered gallery without having to special-case the featured image (no need to prepend `variant.featured_image` separately, no risk of rendering it twice). ### Related: product-level media ordering On products that have a per-variant gallery, product-level media in the Shopify Admin is also color-grouped, with every photo of one color sitting together (Black, Black, Black, Blue, Blue, Blue, …). Within each color, images are sorted primary → front → rear → left/right → inside/outside → other. PromoSync enforces this ordering with a `productReorderMedia` mutation that runs immediately after the initial `productSet`. ## Liquid integration The gallery metafield is `list.file_reference`, so each item in `metafield.value` is a **File** object, not a `MediaImage` directly. To render an image, read the file's `preview_image` property. Available per-file properties: | Property | Description | |---------------------|----------------------------------------| | `.preview_image` | The underlying image (use `image_url`) | | `.alt` | Alt text | | `.media_type` | `image`, `video`, `model`, etc. | ### Basic gallery render with fallback This is the recommended baseline. It reads `psrestful.gallery`, falls back to `variant.featured_image` on plans where the metafield is empty, and uses Shopify's image filters with responsive widths and lazy loading. ```liquid {% raw %}{% assign variant = product.selected_or_first_available_variant %} {% assign gallery = variant.metafields.psrestful.gallery.value %} {% if gallery and gallery.size > 0 %} {% for file in gallery %} {% assign image = file.preview_image %} {% endfor %} {% elsif variant.featured_image %} {# Fallback for shops without the gallery metafield (Free / Standard / Premium / etc.) #} {% endif %} ``` ### Fallback to `variant.featured_image` The fallback in the example above is the only thing required to keep free-tier and other non-eligible shops working. Because PromoSync still sets `variant.image` on every plan, the single-image experience is unchanged for those stores; your theme just renders one figure instead of many. ### Swapping the gallery on variant change When a shopper picks a new variant, the gallery has to swap to the new variant's color. There are two common patterns. #### (a) Section Rendering API This is the most robust option and requires no metafield-aware JavaScript. Shopify re-renders the section server-side with the new variant selected. ```javascript // Fired by Dawn-style themes when the picker changes the variant. document.addEventListener('variant:change', async (event) => { const variantId = event.detail.variant.id; const sectionId = 'main-product'; // your section's id const url = `${window.location.pathname}?variant=${variantId}§ion_id=${sectionId}`; const html = await fetch(url).then(r => r.text()); const fresh = new DOMParser() .parseFromString(html, 'text/html') .querySelector('.variant-gallery'); document.querySelector('.variant-gallery').replaceWith(fresh); }); ``` Because the request includes `?variant=ID`, `product.selected_or_first_available_variant` resolves to the new variant on the server, and the Liquid above renders that variant's gallery directly. #### (b) Client-side JS using preloaded metafields If you want to avoid the network round-trip, expose the metafield to JavaScript and swap the DOM in place. You'll need to make the metafield available in the page, either by serializing it into a `{% endraw %} ``` ```javascript const galleries = JSON.parse( document.getElementById('variant-galleries').textContent ); document.addEventListener('variant:change', (event) => { const items = galleries[event.detail.variant.id] || []; const container = document.querySelector('.variant-gallery'); container.innerHTML = items.map(item => ` `).join(''); }); ``` The Section Rendering approach is easier to maintain (one source of truth in Liquid). The client-side approach is faster on swap but duplicates rendering logic between Liquid and JS. ## Defining the metafield in Theme Customizer For the metafield to be usable as `metafield.value` (auto-dereferenced to file objects), it must be registered as a **metafield definition** in Shopify Admin under **Settings → Custom data → Variants**. Without a definition, `.value` returns the raw JSON of GIDs and `.preview_image` won't work. PromoSync registers this definition automatically on app install via `bin/create_metafields.py`. Merchants can verify it exists by visiting: **Settings → Custom data → Variants → "Variant Gallery"** The expected definition shape: | Field | Value | |-----------------|-----------------------------| | **Name** | Variant Gallery | | **Namespace** | `psrestful` | | **Key** | `gallery` | | **Type** | List of file references | | **Validation** | Image files only | If the definition is missing (most often because PromoSync was installed before this feature shipped and the merchant hasn't re-triggered the metafield setup), the metafield values may still be written, but `.value` won't dereference to file objects in Liquid. Have the merchant re-run the metafield setup from the PromoSync app or contact support at [`{{support_url}}`](#). ## Behavior details worth knowing A few non-obvious rules are applied at import time. Knowing them will save you from chasing "missing image" reports that aren't actually bugs. - **Swatches are filtered out.** PromoStandards class type `1004` (color swatches) never appear in the gallery. Use the variant's `color_array` metafield if you need to render a swatch UI. - **Low-resolution images are filtered out.** Any image below the shop's configured **Minimum Image Resolution** (set in PromoSync shop settings) is dropped at the source. This includes SanMar's 300×450 "shared primary" filler image, which would otherwise pollute every variant gallery. - **`by_color` shops skip the gallery.** Shops configured with `variant_grouping_strategy='by_color'` create one Shopify product per color (e.g. Hit-a-Double-style stores). The metafield is not written on these shops because the product itself is already color-curated. Use Shopify's standard `product.media` instead. - **"Import first image only" disables extras.** When the merchant enables **Import first image only** in import settings, the gallery collapses to just the featured image. The metafield is still written (so your theme code keeps working), but it will contain a single item. ## Troubleshooting ### Gallery is empty in the theme but I'm on Business or Enterprise 1. Verify the metafield definition exists at **Settings → Custom data → Variants → "Variant Gallery"**. Without a definition, `.value` returns raw JSON instead of file objects, and the `{% raw %}{% if gallery and gallery.size > 0 %}{% endraw %}` guard will treat it as falsy. 2. Re-import the affected product. The gallery metafield is populated **at import time only**; it is not retroactively backfilled on variants that were imported before the feature was enabled or before the plan was upgraded. ### Gallery only shows one image Either: - The shop's plan does not include the gallery feature (see [Plan eligibility](#plan-eligibility)). The theme is correctly falling back to `variant.featured_image`. Upgrade at [`{{upgrade_url}}`](#). - The supplier's media feed had no color-matched extras for that variant (common for accessory items, single-angle products, or smaller suppliers). - The shop has **Import first image only** turned on in import settings. ### Images out of order in Shopify Admin but correct on the storefront The `productReorderMedia` mutation that color-groups product-level media runs asynchronously after import. The Admin Media tab can take up to a minute to settle. The variant gallery itself (`psrestful.gallery`) is written in order in the same mutation that creates the metafield, so the storefront order is correct immediately. If the Admin order is still wrong after a few minutes, contact support at [`{{support_url}}`](#) with the product handle. ### Gallery and featured image are both empty If `variant.featured_image` is `nil` and the gallery metafield is also empty, the supplier's media feed simply didn't publish any image for that color of that product. This is unrelated to the plan or the metafield definition — PromoSync only attaches images the supplier actually provides. It shows up most often on niche, discontinued, or brand-new SKUs. Verify by opening the product on PSRESTful and checking the [Media Content](https://docs.psrestful.com/standards/media-content-1.0.1) response for that part. If the supplier later adds images, re-importing the product will pick them up. - [Theme App Extensions](https://promosync-docs.psrestful.com/guides/theme-extensions): Add volume pricing, minimum quantity enforcement, and decoration selectors to your Shopify product pages with drag-and-drop app blocks — no code required. # Theme App Extensions PromoSync includes **theme app extension blocks** that you can add to your Shopify product pages using the Theme Customizer — no code changes required. These blocks work on any Online Store 2.0 theme (Dawn and compatible). ## The PromoSync Pricing Engine app embed Before adding any visible block, enable the **PromoSync Pricing Engine** app embed. This is a theme-wide embed (not a block on a specific section) that wires up the client-side JavaScript the visible blocks rely on — minimum-quantity enforcement on the quantity input and live tier-price recalculation when the shopper changes the variant or quantity. To enable it, open the Theme Customizer, click the **App embeds** icon in the left sidebar, and switch **PromoSync Pricing Engine** on. You only enable this once per theme. After that, the visible blocks below can be added and removed freely from individual templates. ## Available Blocks ### [Volume Pricing (Display)](/guides/theme-extensions/volume-pricing) Display quantity-based price breaks in a table or horizontal layout. Reads from the `psrestful.part_price_array` variant metafield and updates automatically on variant change. Display-only. ### [Volume Pricing Function](/guides/theme-extensions/volume-pricing-function) The Shopify Function that actually discounts cart lines down to the matching tier price at checkout. Reads the same `psrestful.part_price_array` metafield as the display block. Activated via the [Volume Pricing settings](/settings/volume-pricing) toggle, with per-variant and combined-by-product modes. ### [Minimum Quantity](/guides/theme-extensions/minimum-quantity) Enforce per-product minimum order quantities on the quantity input. Optionally display a "Minimum order: N units" notice. Reads from the `psrestful.minimum_quantity` product metafield. Client-side enforcement only. ### [Decoration Selector](/guides/theme-extensions/decoration-selector) Let shoppers choose a decoration location and method via collapsible sections or cascading dropdowns. Selections are saved as cart line item properties. Reads from the `psrestful.location_decorations` product metafield. ## How to Add a Block All three blocks follow the same general setup: 1. Go to **Online Store > Themes > Customize** 2. Navigate to a **Product page** template 3. Click **Add block** and switch to the **Apps** tab — every PromoSync block appears there 4. Pick the block you want (Min Quantity Notice, PromoSync Decorations, PromoSync Tier Pricing, Volume Pricing Note) 5. Configure the block settings and drag it to your preferred position on the page 6. Click **Save** Each block only renders on products that have the relevant metafield data — they hide automatically when no data is available. **Prefer writing Liquid code?** See the [Storefront Widgets](/guides/storefront-widgets) guide for manual Liquid implementations of tier pricing and decoration displays. - [Volume Pricing (Display)](https://promosync-docs.psrestful.com/guides/theme-extensions/volume-pricing): Show quantity-based price breaks on your product pages with a drag-and-drop app block. No code or Liquid changes required. # Volume Pricing Theme Extension **Looking for the Liquid approach?** See [Storefront Widgets — Tier Pricing Block](/guides/storefront-widgets#tier-pricing-block) for a manual Liquid implementation. ## Overview The **PromoSync Tier Pricing** block displays quantity-based price breaks on product pages. It reads per-variant pricing data from the `psrestful.part_price_array` metafield and renders an interactive table that updates automatically when the customer switches variants. Two layout modes are available: - **Table (vertical)** — Traditional rows: one row per price break with Quantity, Price, and optional Unit columns. Supports Standard, Striped, and Compact table styles. - **Horizontal rows** — Compact two-row layout: quantities across the top, prices below. Ideal for products with many price breaks. Scrolls horizontally when needed. Both layouts update dynamically on variant change without a page reload. --- ## How to Add the Block 1. In your Shopify admin, go to **Online Store > Themes > Customize** 2. Navigate to a **Product page** template 3. Click **Add block** > search for **PromoSync Tier Pricing** 4. Configure the settings (see below) 5. Drag the block to your preferred position (typically below the price or above Add to Cart) 6. **Save** No theme code changes required. --- ## Settings Reference | Setting | ID | Type | Default | Notes | |---|---|---|---|---| | Layout | `layout` | select | `table` | `table` (vertical) or `horizontal` (two-row) | | Show heading | `show_header` | checkbox | `true` | Displays "Volume Pricing" heading above the table | | Show unit column | `show_unit` | checkbox | `true` | **Table layout:** adds a Unit column. **Horizontal layout:** shows unit as suffix on heading, e.g. "Volume Pricing (EA)" | | Unit label | `unit_label` | text | `EA` | The unit label text (e.g. "EA", "PC", "Each") | | Table style | `table_style` | select | `standard` | `standard`, `striped`, or `compact`. **Applies to table layout only** | --- ## Layout Comparison ### Table (vertical) Best for: fewer price breaks (2-5 tiers), when you want a traditional readable format. ``` Volume Pricing ┌──────────┬─────────┬──────┐ │ Quantity │ Price │ Unit │ ├──────────┼─────────┼──────┤ │ 24+ │ $12.50 │ EA │ │ 48+ │ $11.00 │ EA │ │ 100+ │ $9.50 │ EA │ │ 250+ │ $8.00 │ EA │ └──────────┴─────────┴──────┘ ``` Table style options: | Style | Description | |---|---| | Standard | Clean borders, no row shading | | Striped | Alternating row background for readability | | Compact | Reduced padding for tighter vertical space | ### Horizontal rows Best for: many price breaks (5+ tiers), compact product layouts, mobile-friendly display. ``` Volume Pricing (EA) ┌────────┬────────┬────────┬────────┬────────┐ │ 24+ │ 48+ │ 100+ │ 250+ │ 500+ │ ├────────┼────────┼────────┼────────┼────────┤ │ $12.50 │ $11.00 │ $9.50 │ $8.00 │ $7.25 │ └────────┴────────┴────────┴────────┴────────┘ ``` When price breaks exceed the available width, the table scrolls horizontally. --- ## Data Format The block reads `variant.metafields.psrestful.part_price_array`, which must be a JSON array of price break objects: ```json [ {"quantityMin": 24, "price": 1250}, {"quantityMin": 48, "price": 1100}, {"quantityMin": 100, "price": 950}, {"quantityMin": 250, "price": 800} ] ``` - `quantityMin` — Minimum quantity to qualify for this tier - `price` — Price per unit in **cents** (e.g. `1250` = $12.50) Each variant can have different price breaks. The block automatically switches data when the customer selects a different variant. --- ## How Variant Switching Works 1. A hidden `