1

I'm trying to programmatically sync the sub-collections of a Shopify product as a metafield on each of its variants using the Shopify REST Admin API and Node.js (Axios).

Here's what I’m doing:

  1. Fetch all variants for a given product

  2. Fetch custom_collections and smart_collections this product belongs to

  3. Build a comma-separated list of collection titles

  4. For each variant:

    • Check if a metafield with a given namespace/key exists

    • If it exists, update it

    • If not, create it

Here's a condensed version of my script:

const axios = require('axios');

const SHOPIFY_API_URL        = process.env.SHOPIFY_ADMIN_API_URL.replace(/\/+$/, '');
const SHOPIFY_TOKEN          = process.env.SHOPIFY_ADMIN_ACCESS_TOKEN;
const METAFIELD_NAMESPACE    = process.env.SHOPIFY_METAFIELD_NAMESPACE || 'custom';
const METAFIELD_KEY          = process.env.SHOPIFY_METAFIELD_KEY       || 'sub_collections';
const METAFIELD_TYPE         = 'single_line_text_field';

async function syncSubCollections(productId) {
  if (!productId) throw new Error('syncSubCollections: missing productId');

  // 1) fetch product variants
  const { data: prod } = await axios.get(
    `${SHOPIFY_API_URL}/products/${productId}.json?fields=variants`,
    { headers: { 'X-Shopify-Access-Token': SHOPIFY_TOKEN } }
  );
  const variantIds = prod.product.variants.map(v => v.id);

  // 2) fetch the two REST endpoints for collections associated with the product
  const [customRes, smartRes] = await Promise.all([
    axios.get(
      `${SHOPIFY_API_URL}/custom_collections.json?product_id=${productId}`,
      { headers: { 'X-Shopify-Access-Token': SHOPIFY_TOKEN } }
    ),
    axios.get(
      `${SHOPIFY_API_URL}/smart_collections.json?product_id=${productId}`,
      { headers: { 'X-Shopify-Access-Token': SHOPIFY_TOKEN } }
    ),
  ]);

  // 3) build a comma-separated list of collection titles
  const collections = [
    ...customRes.data.custom_collections.map(c => c.title),
    ...smartRes.data.smart_collections.map(c => c.title),
  ];
  const value = collections.join(', ');

  // 4) upsert the metafield on each variant
  await Promise.all(variantIds.map(async variantId => {
    // list existing metafields
    const { data: mfList } = await axios.get(
      `${SHOPIFY_API_URL}/variants/${variantId}/metafields.json`,
      { headers: { 'X-Shopify-Access-Token': SHOPIFY_TOKEN } }
    );
    const existing = mfList.metafields.find(mf =>
      mf.namespace === METAFIELD_NAMESPACE && mf.key === METAFIELD_KEY
    );

    if (existing) {
      // update
      await axios.put(
        `${SHOPIFY_API_URL}/metafields/${existing.id}.json`,
        { metafield: { id: existing.id, value, type: METAFIELD_TYPE } },
        { headers: {
            'X-Shopify-Access-Token': SHOPIFY_TOKEN,
            'Content-Type': 'application/json'
          }
        }
      );
    } else {
      // create
      await axios.post(
        `${SHOPIFY_API_URL}/metafields.json`,
        { metafield: {
            namespace:     METAFIELD_NAMESPACE,
            key:           METAFIELD_KEY,
            owner_resource:'variant',
            owner_id:      variantId,
            type:          METAFIELD_TYPE,
            value
          }
        },
        { headers: {
            'X-Shopify-Access-Token': SHOPIFY_TOKEN,
            'Content-Type': 'application/json'
          }
        }
      );
    }
  }));

  // return what we synced
  return { productId, collections };
}

module.exports = { syncSubCollections };

The metafields don't get updated properly — sometimes they don't appear at all or the value does not persist. No clear error is returned.

Questions:

  • Is this the correct way to upsert metafields for each variant?
  • Am I using the right REST endpoints for updating vs creating variant metafields?
  • Is there a better way (GraphQL or bulk mutation) to do this efficiently for multiple variants?

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.