Seeking review of a Shopify Functions Fulfillment Constraint implementation

I’m a Shopify Plus partner working on a fulfillment routing problem for a client and would appreciate a review of our proposed solution before we build it out.


The Problem

Our client sells both refrigerated and standard products for home delivery. They have two boutique fulfillment locations (Saint-Bruno and Sainte-Thérèse). When a customer orders a mix of refrigerated and standard items, Shopify splits the fulfillment: refrigerated items go to the boutique, standard items go to the warehouse.

This split causes the order to land incorrectly in their Odoo ERP and go unprocessed. The intended behavior: when any refrigerated item is in the cart, the entire order must route to one of the two boutiques. If neither boutique can fulfill the full order, checkout should be blocked. Pick-up orders should be unaffected.


Our Proposed Solution

We plan to use the Fulfillment Constraints Function API with a mustFulfillFrom constraint. The function detects a “refrigerated” product tag and forces all cart lines to route to one of the two boutique locations. The FulfillmentConstraintRule will be registered with deliveryMethodTypes: [SHIPPING] to exclude pick-up orders.

run.graphql

query RunInput {
  cart {
    deliverableLines {
      id
      merchandise {
        __typename
        ... on ProductVariant {
          id
          product { hasAnyTag(tags: ["refrigerated"]) }
        }
      }
    }
  }
  locations(names: ["Saint-Bruno", "Sainte-Thérèse"]) {
    id
    name
  }
}

run.js

// @ts-check
import { no_fulfillment_constraints_result } from '../generated/api';

export function run(input) {
  const { deliverableLines } = input.cart;
  const { locations } = input;

  const hasRefrigeratedItem = deliverableLines.some(
    (line) =>
      line.merchandise.__typename === 'ProductVariant' &&
      line.merchandise.product.hasAnyTag
  );

  if (!hasRefrigeratedItem) return no_fulfillment_constraints_result();

  const allLineIds = deliverableLines.map((line) => line.id);
  const validLocationIds = locations.map((loc) => loc.id);

  if (validLocationIds.length === 0) return no_fulfillment_constraints_result();

  return {
    operations: [{
      mustFulfillFrom: {
        deliverableLineIds: allLineIds,
        locationIds: validLocationIds,
      }
    }]
  };
}

Rule registration

mutation {
  fulfillmentConstraintRuleCreate(
    fulfillmentConstraintRule: {
      functionId: "<function-id>"
      deliveryMethodTypes: [SHIPPING]
    }
  ) {
    fulfillmentConstraintRule { id }
    userErrors { field message }
  }
}

Questions

  1. Untracked inventory - Refrigerated products have inventory tracking disabled in Shopify (dispatched manually via Odoo). When mustFulfillFrom is applied, does Shopify treat untracked inventory as always available at the specified locations, or does it block checkout?

  2. Delivery method scoping - Registering with deliveryMethodTypes: [SHIPPING] - does this fully exclude local pick-up orders from the constraint?

  3. hasAnyTag on duplicate variant products - Refrigerated products have two variants (online SKU and POS SKU) on the same parent product. If only the online variant is in the cart, will hasAnyTag correctly read the tags from the parent product?

  4. Fail-safe behavior - If neither boutique location is returned by the input query (name mismatch or deactivated location), the function returns no_fulfillment_constraints_result(). Does this mean the order routes normally rather than throwing a checkout error?

  5. Correct API for this use case - Is the Fulfillment Constraints API the right tool here, or should we be looking at the Order Routing Location Rule API instead?

Any feedback or experience with similar implementations is appreciated.

Hey @Michael_Marcelino - thanks for reaching out.

I think the Fulfillment Constraints Function API would be a great fit for your use case here. I reviewed your proposed code and have some notes that should save you some debugging time.

  1. Rather than mustFulfillFrom, you can use deliverableLinesMustFulfillFromAdd. In your return block, you could try something like this:
return {
    operations: [{
      deliverableLinesMustFulfillFromAdd: {
        deliverableLineIds: allLineIds,
        locationIds: validLocationIds,
      }
    }]
  }
  1. no_fulfillment_constraints_result isn’t an export from the generated API. Here, you can just define a constant instead - const NO_CHANGES = { operations: [] }

  2. The function should be exported as cartFulfillmentConstraintsGenerateRun, not run. The function will work with any query name in JS, but following the convention avoids confusion.

  3. fulfillmentConstraintRuleCreate takes flat arguments, not a nested input object. Also, functionId has been deprecated in favor of functionHandle. Something like this should work here:

mutation {
  fulfillmentConstraintRuleCreate(
    functionHandle: "<function-handle>"
    deliveryMethodTypes: [SHIPPING]
  ) {
    fulfillmentConstraintRule { id }
    userErrors { field message }
  }
}

Regarding your questions:

  1. The docs say “if the cart item isn’t stocked at the specified location, then checkout won’t return any shipping rates and completing checkout will be blocked.” Even with tracking disabled, Shopify needs to see the variant as available at that location. Make sure the refrigerated variants are configured as stocked at Saint-Bruno and Sainte-Thérèse in Shopify’s location settings, even if you’re not tracking inventory quantity. The corresponding GraphQL field for this is inventoryLevel.isActive
  2. Yes, registering with deliveryMethodTypes: [SHIPPING] means the function only fires for shipping orders. Local pickup (PICK_UP) orders won’t trigger it as those have a different deliveryMethodType
  3. hasAnyTag runs on the product object (the parent), not the variant. Since both your online and POS SKU variants share the same parent product if I’m understanding correctly, the tag check will work correctly regardless of which variant is in the cart.
  4. Yes, if locations(names: […]) returns an empty array, your function returns { operations: [ ] }, which means no constraints are applied and the order routes normally. It won’t throw a checkout error. That said, this also means the split-fulfillment problem you’re trying to prevent would silently come back if someone renames or deactivates a location. One thing worth considering: the locations field also accepts identifiers (location GIDs) instead of names, which would be more resilient to a store admin changing a location name.
  5. The Fulfillment Constraints API looks like a good fit for your use case here. Order Routing Location Rule runs after the order is placed and only influences location ranking during fulfillment. It can’t block checkout or force all items to the same group of locations. Since you need to prevent the split before the order hits your ERP, Fulfillment Constraints is likely the one you want.

Hope that helps with the build. Let me know if anything comes up during testing, or if you have any other questions.