Cartly

Sell Shopify Products on Your ExpressionEngine Site

Buy Now

Sell Shopify Products on Your ExpressionEngine Site.

INSTALLATION

  1. Copy the entire `cartly` folder to your `system/user/addons` folder.
  2. Copy the `themes/user/cartly` folder to your `themes/user/` folder.
  3. On your EE backend, navigate to `Developer > Addons` (yoursite.com/admin.php?/cp/addons).
  4. Scroll to `Third Party Add-Ons`.
  5. Find `Cartly` and click `Install`.
  6. Navigate to `Cartly > Connect` and enter your Shopify credentials.
  7. 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.