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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions