Sell Shopify Products on Your ExpressionEngine Site.
INSTALLATION
- Copy the entire `cartly` folder to your `system/user/addons` folder.
- Copy the `themes/user/cartly` folder to your `themes/user/` folder.
- On your EE backend, navigate to `Developer > Addons` (yoursite.com/admin.php?/cp/addons).
- Scroll to `Third Party Add-Ons`.
- Find `Cartly` and click `Install`.
- Navigate to `Cartly > Connect` and enter your Shopify credentials.
- Run your first sync from `Cartly > Sync`, and enjoy!
SETTINGS
Connection (Cartly > Connect)
- Shop Domain: Your Shopify store domain (e.g. `your-store.myshopify.com`).
- Storefront Access Token: Found in your Shopify admin under `Apps > Develop apps > Storefront API`.
- Admin API Access Token: Optional. Required for webhook registration.
- API Version: Shopify API version to use (e.g. `2025-01`).
- Webhook Secret: Used to verify incoming webhooks from Shopify.
Display (Cartly > Settings)
- Products Per Page: Number of products to show per page in the storefront tag.
- Show Sale Badge: Display a "Sale" badge on discounted products.
- Show Sold Out Badge: Display a "Sold Out" badge on unavailable products.
- Show Vendor: Display the product vendor.
- Show Product Type: Display the product type.
Cart
- Cart Style: Choose between a slide-in `Drawer` or a `Modal` for the cart.
- Enable Direct Checkout: Skip the cart and redirect directly to Shopify checkout when a product is added.
Appearance
- Primary Color: The primary brand color used for buttons and accents.
- Add to Cart Button Text: Customize the text on buy buttons.
- Checkout Button Text: Customize the text on the checkout button.
Currency
- Currency Code: Your store's currency code (e.g. `USD`).
- Currency Symbol: Your store's currency symbol (e.g. `$`).
Advanced
- Cache Duration: How long (in minutes) to cache product data. Set to `0` to disable.
- Auto-inject CSS/JS: Automatically add Cartly assets to all templates via the extension hook.
USAGE
Adding Assets to Your Template
Cartly requires its CSS and JavaScript to be loaded on any page that uses Cartly tags. Add these to your template layout:
<!-- In your <head> -->
{exp:cartly:head}
<!-- Before </body> -->
{exp:cartly:scripts}
The `{exp:cartly:scripts}` tag injects the Cartly configuration (action URLs, CSRF token, settings) and loads the cart and search JavaScript files.
Displaying Products
To display a list of products, use the `{exp:cartly:products}` tag pair. It accepts all filtering and sorting parameters:
{exp:cartly:products collection="summer" vendor="Nike" tag="featured" sort="price_asc" limit="12"}
<div class="product" data-product-id="{shopify_product_id}">
<img src="{featured_image}" alt="{title}">
<h3>{title}</h3>
<p>{min_price}{if on_sale} <s>{compare_at_price}</s>{/if}</p>
{if sold_out}<span>Sold Out</span>{/if}
<p>{vendor} | {product_type}</p>
</div>
{/exp:cartly:products}
Product Parameters
- collection: Filter by collection handle
- vendor: Filter by vendor name
- tag: Filter by product tag
- type: Filter by product type
- sort: `title_asc`, `title_desc`, `price_asc`, `price_desc`, `created_asc`, `created_desc`
- limit: Number of products to return (default: `12`)
- offset: Number of products to skip
Product Variables
Each product in the loop provides these variables:
- `{shopify_product_id}` - Shopify global ID
- `{title}` - Product title
- `{handle}` - URL-safe handle
- `{description}` - Product description (HTML)
- `{vendor}` - Vendor name
- `{product_type}` - Product type
- `{featured_image}` - First product image URL
- `{min_price}` - Formatted minimum price (e.g. `$29.99`)
- `{max_price}` - Formatted maximum price
- `{raw_min_price}` - Unformatted minimum price (e.g. `29.99`)
- `{raw_max_price}` - Unformatted maximum price
- `{compare_at_price}` - Formatted compare-at price
- `{on_sale}` - Boolean, `true` if product is on sale
- `{sold_out}` - Boolean, `true` if product is unavailable
- `{available}` - Boolean, `true` if product is available
- `{count}` - Current iteration count
- `{total_results}` - Total number of results
Variant Loop
Inside the products tag pair, you can loop through variants:
{variants}
<option value="{variant_id}">{variant_title} - {variant_price}</option>
{/variants}
Variant variables: `{variant_id}`, `{variant_title}`, `{variant_price}`, `{variant_sku}`, `{variant_available}`, `{option1}`, `{option2}`, `{option3}`
Image Loop
Inside the products tag pair, you can loop through all product images:
{images}
<img src="{image_url}" alt="{image_alt}" width="{image_width}" height="{image_height}">
{/images}
Single Product
To display a single product by its handle:
{exp:cartly:product handle="my-product"}
<h1>{title}</h1>
<p>{description}</p>
<p>{min_price}</p>
{variants}<option value="{variant_id}">{variant_title}</option>{/variants}
{images}<img src="{image_url}">{/images}
{/exp:cartly:product}
Single-Value Product Tags
For quick access to individual product fields without a tag pair:
{exp:cartly:product_title handle="my-product"}
{exp:cartly:product_description handle="my-product"}
{exp:cartly:product_pricing handle="my-product"}
The `product_pricing` tag outputs a formatted `<span>` with the price range and compare-at price.
Product Images Tag
{exp:cartly:product_images handle="my-product"}
<img src="{image_url}" alt="{image_alt}">
{/exp:cartly:product_images}
Buy Button
Renders a complete add-to-cart component with variant selector, quantity input, and buy button:
{exp:cartly:product_buy_button handle="my-product" button_text="Add to Cart"}
This outputs a fully interactive component that is enhanced by the Cartly JavaScript. When clicked, it adds the selected variant and quantity to the Shopify cart and opens the cart drawer.
Displaying Collections
To list all collections:
{exp:cartly:collections limit="10" sort="title_asc"}
<h3>{title}</h3>
<img src="{image}" alt="{title}">
<p>{description}</p>
{/exp:cartly:collections}
Collection Variables
- `{collection_id}` - Shopify collection ID
- `{title}` - Collection title
- `{handle}` - URL-safe handle
- `{description}` - Collection description (HTML)
- `{image}` - Collection image URL
Single Collection with Products
{exp:cartly:collection handle="summer" limit="12" sort="price_asc"}
<h2>{collection_title}</h2>
<p>{collection_description}</p>
{products}
<div class="product">
<img src="{featured_image}" alt="{title}">
<h3>{title}</h3>
<p>{min_price}</p>
</div>
{/products}
{/exp:cartly:collection}
Collection Parameters
- handle: Collection handle (required)
- limit: Max products to show (default: `50`)
- sort: `position`, `title_asc`, `title_desc`, `price_asc`, `price_desc`
Cart
Add a floating cart icon with an item count badge:
{exp:cartly:cart_icon}
Add the cart drawer container (include once in your layout, typically before `</body>`):
{exp:cartly:cart}
The cart is fully managed by JavaScript. When a user adds an item, the cart drawer slides open displaying all line items, quantities, subtotal, and a checkout button. Clicking checkout redirects to the Shopify checkout URL.
Cart state is persisted in `localStorage` via the Shopify cart ID, so it survives page refreshes and navigation.
Search
Add a live AJAX search input:
{exp:cartly:search}
This renders a search input that queries your synced products as the user types (debounced, minimum 2 characters). Results appear in a dropdown with product image, title, price, and vendor.
Full Storefront
For a complete shop page with filtering and sorting, use the storefront tag:
{exp:cartly:storefront show_vendors="yes" show_types="yes" show_price="yes" sort="title_asc" limit="24"}
Storefront Parameters
- show_vendors: Show vendor filter sidebar (`yes`/`no`)
- show_types: Show product type filter sidebar (`yes`/`no`)
- show_price: Show price range filter (`yes`/`no`)
- sort: Default sort order
- limit: Number of products to display
The storefront renders a filter sidebar and product grid. Filtering and sorting are handled client-side via JavaScript for instant results.
SYNCING
Cartly syncs products and collections from Shopify to your local EE database for fast template rendering. You can trigger a sync in two ways:
Manual Sync
Navigate to `Cartly > Sync` in the control panel and click Sync Now. This fetches all products and collections via the Shopify Storefront API with cursor-based pagination (250 per page).
Webhook Sync
If you've configured an Admin API Access Token and Webhook Secret, Cartly can receive real-time updates from Shopify. The following webhook topics are supported:
- `products/create`
- `products/update`
- `products/delete`
- `collections/create`
- `collections/update`
- `collections/delete`
The webhook endpoint URL is your site's action URL for the `syncWebhook` action.
Sync Log
All sync operations are logged with status, product/collection counts, and timestamps. View the history at `Cartly > Sync`.
CONTROL PANEL
Cartly adds a full control panel section with the following pages:
- Dashboard: Overview with product/collection counts, last sync time, and recent sync history.
- Connect: Shopify credentials form with connection testing.
- Settings: Display, cart, appearance, currency, and advanced settings.
- Sync: Manual sync trigger and sync history log.
- Products: Browse all synced products with images, pricing, vendor, and status.
- Collections: Browse all synced collections.
Clicking a product or collection shows its full detail page with variants, images, tags, and Shopify metadata.
DATABASE
Cartly creates 6 database tables on installation:
- `cartly_settings` - Key-value settings per site
- `cartly_products` - Synced product data (title, handle, pricing, images, etc.)
- `cartly_variants` - Product variants (price, SKU, options, inventory)
- `cartly_collections` - Synced collection data
- `cartly_collection_products` - Collection-to-product pivot table with position
- `cartly_sync_log` - Sync operation history
All tables are removed on uninstall.
THEMING
Cartly uses CSS custom properties for easy theming. The primary color is set from the control panel, or you can override any variable in your own CSS:
:root {
--cartly-primary: #5C6AC4;
--cartly-primary-hover: #4959bd;
--cartly-text: #333333;
--cartly-text-light: #666666;
--cartly-border: #e0e0e0;
--cartly-bg: #ffffff;
--cartly-bg-light: #f9f9f9;
--cartly-danger: #e74c3c;
--cartly-success: #27ae60;
--cartly-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
--cartly-radius: 6px;
--cartly-transition: 0.3s ease;
}
SECURITY
Shopify Credentials
Storefront Access Tokens are public tokens designed for client-side use. They only grant read access to products, collections, and cart operations. They do not expose any sensitive store data.
Admin API Access Tokens should be kept private and are only used server-side for webhook registration. They are stored in the database, never exposed to the frontend.
Webhook Verification
All incoming Shopify webhooks are verified against the Webhook Secret using HMAC-SHA256. Requests with invalid signatures are rejected with a `401` response.
CSRF Protection
Cart mutations (add, update, remove) and CP form submissions require a valid CSRF token. Read-only endpoints (get cart, search products) and the webhook endpoint are CSRF-exempt.
SUPPORT
We want to make sure you have what you need on this. Email [email protected] for help.