Skip to content

cart/add README example uses variant.id, which returns the Craft element ID instead of the Shopify variant ID (v7) #220

Description

@rjgux

Summary

The README's "add products to the cart" example posts variant.id to the cart/add endpoint:

<form action="{{ craft.shopify.store.getUrl('cart/add') }}" method="post">
  <select name="id">
    {% for variant in product.getVariants() %}
      <option value="{{ variant.id }}">{{ variant.title }}</option>
    {% endfor %}
  </select>
  {{ hiddenInput('qty', 1) }}
  <button>Add to Cart</button>
</form>

In v7, variant.id is the Craft element/row ID, not a Shopify identifier. Submitting this form sends e.g. id=7271 to the store, and Shopify responds:

Cart Error: Cannot find variant

Root cause

In v7 a variant is a craft\shopify\models\Variant (@since 7.0.0), where:

public ?int $id = null;           // Craft row/element ID, e.g. 7271
public ?string $shopifyId = null; // Shopify GID, e.g. gid://shopify/ProductVariant/55602221973830

So variant.id is never a Shopify identifier. The Shopify ID is on variant.shopifyId, but it's a GID (gid://shopify/ProductVariant/<numeric>), whereas the cart/add endpoint expects the bare numeric variant ID. The plugin itself strips this prefix elsewhere — see src/fieldlayoutelements/VariantsField.php:

str_replace('gid://shopify/ProductVariant/', '', $variant->shopifyId)

This confirms shopifyId is a GID and there is no first-class numeric accessor on Variant.

Product vs. Variant inconsistency (related)

Products and variants are stored differently, and expose shopifyId differently:

Storage Numeric ID exposed? GID exposed?
Product shopify_products table shopifyId (numeric) shopifyGid
Variant shopify_data (type = ProductVariant) ❌ none shopifyId (the GID)

So product.shopifyId is a clean numeric, but variant.shopifyId is a GID — same property name, different shape — and variants have no numeric equivalent.

Suggested fixes (either/both)

  1. Docs: update the README cart/add example to use the numeric portion of the variant's Shopify ID:

    <option value="{{ variant.shopifyId|split('/')|last }}">{{ variant.title }}</option>
  2. API (preferred): make Variant consistent with the Product element — expose a numeric shopifyId (and a shopifyGid for the GID) — so consumers don't have to string-split a GID for a documented, common workflow.

Environment

  • craftcms/shopify 7.1.2
  • Craft CMS 5.10.x
  • PHP 8.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions