Keep pre-orders in a separate shipment
A Shopify fulfillment constraints Function that sends tagged pre-order items from a pre-order location and in-stock items from the others.
- Function API
- Fulfillment Constraints
- Target
- cart.fulfillment-constraints.generate.run
- Plan
- Any plan in a public app; Shopify Plus in a custom app
- App scope
- write_fulfillment_constraint_rules
- API version
- 2026-10
- Last checked
In short
This fulfillment constraints Function keeps pre-order items apart from in-stock items: products with your pre-order tag must ship from a dedicated location, and everything else from your other locations, so they don’t wait in one package. It uses the cart.fulfillment-constraints.generate.run target of the Fulfillment Constraints API, version 2026-10, on any plan in a public app.
How to use it
- Create a location for pre-order stock and tag pre-order products, for example pre-order.
- Create an app with Shopify CLI 4.9.0 or later, add the extension (shopify app generate extension --template fulfillment_constraints) and replace its files with the files below.
- Run the fixtures with shopify app function run --input tests/mixed-cart.input.json, then deploy.
- Activate it with fulfillmentConstraintRuleCreate, deliveryMethodTypes: [SHIPPING], and set {"preorderTags": ["pre-order"], "preorderLocationId": "gid://shopify/Location/1234567890"} in its metafields.
Code
shopify.extension.tomlapi_version = "2026-10"
[[extensions]]
name = "Keep pre-orders in a separate shipment"
handle = "pre-orders-separate-shipment"
type = "function"
[[extensions.targeting]]
target = "cart.fulfillment-constraints.generate.run"
input_query = "src/cart_fulfillment_constraints_generate_run.graphql"
input_variables = { namespace = "$app", key = "function-configuration" }
export = "cart-fulfillment-constraints-generate-run"
[extensions.build]
command = ""
path = "dist/function.wasm"
src/cart_fulfillment_constraints_generate_run.graphqlquery CartFulfillmentConstraintsGenerateRunInput($preorderTags: [String!]) {
cart {
deliverableLines {
id
merchandise {
__typename
... on ProductVariant {
product {
hasAnyTag(tags: $preorderTags)
}
}
}
}
}
locations {
id
}
fulfillmentConstraintRule {
metafield(namespace: "$app", key: "function-configuration") {
jsonValue
}
}
}
src/cart_fulfillment_constraints_generate_run.js// @ts-check
/**
* @typedef {import("../generated/api").CartFulfillmentConstraintsGenerateRunInput} CartFulfillmentConstraintsGenerateRunInput
* @typedef {import("../generated/api").CartFulfillmentConstraintsGenerateRunResult} CartFulfillmentConstraintsGenerateRunResult
*/
/**
* Settings stored as JSON in the rule's metafield. `preorderTags` in the same
* JSON feeds the query variable (see input_variables in the toml), e.g.
* { "preorderTags": ["pre-order"], "preorderLocationId": "gid://shopify/Location/1234567890" }.
*/
const DEFAULT_CONFIG = {
// The location that holds (or will receive) pre-order stock.
preorderLocationId: /** @type {string | null} */ (null),
};
/** @type {CartFulfillmentConstraintsGenerateRunResult} */
const NO_CONSTRAINTS = { operations: [] };
/**
* @param {CartFulfillmentConstraintsGenerateRunInput} input
* @returns {CartFulfillmentConstraintsGenerateRunResult}
*/
export function cartFulfillmentConstraintsGenerateRun(input) {
const config = { ...DEFAULT_CONFIG, ...input.fulfillmentConstraintRule?.metafield?.jsonValue };
const preorderLocationId = config.preorderLocationId;
if (!preorderLocationId) return NO_CONSTRAINTS;
const isPreorder = (/** @type {(typeof input.cart.deliverableLines)[number]} */ line) =>
line.merchandise.__typename === "ProductVariant" && line.merchandise.product.hasAnyTag;
const preorderLineIds = input.cart.deliverableLines.filter(isPreorder).map((line) => line.id);
const inStockLineIds = input.cart.deliverableLines.filter((line) => !isPreorder(line)).map((line) => line.id);
if (preorderLineIds.length === 0) return NO_CONSTRAINTS;
const operations = [
{ deliverableLinesMustFulfillFromAdd: { deliverableLineIds: preorderLineIds, locationIds: [preorderLocationId] } },
];
// Keeping the in-stock items away from the pre-order location is what
// splits the order: they can no longer wait in the same package.
const otherLocationIds = input.locations.map((location) => location.id).filter((id) => id !== preorderLocationId);
if (inStockLineIds.length > 0 && otherLocationIds.length > 0) {
operations.push({
deliverableLinesMustFulfillFromAdd: { deliverableLineIds: inStockLineIds, locationIds: otherLocationIds },
});
}
return { operations };
}
Test input and expected output
tests/mixed-cart.input.json{
"cart": {
"deliverableLines": [
{ "id": "gid://shopify/DeliverableCartLine/1", "merchandise": { "__typename": "ProductVariant", "product": { "hasAnyTag": false } } },
{ "id": "gid://shopify/DeliverableCartLine/2", "merchandise": { "__typename": "ProductVariant", "product": { "hasAnyTag": true } } }
]
},
"locations": [
{ "id": "gid://shopify/Location/1111111111" },
{ "id": "gid://shopify/Location/2222222222" },
{ "id": "gid://shopify/Location/3333333333" }
],
"fulfillmentConstraintRule": {
"metafield": {
"jsonValue": { "preorderTags": ["pre-order"], "preorderLocationId": "gid://shopify/Location/3333333333" }
}
}
}
tests/mixed-cart.output.json{
"operations": [
{
"deliverableLinesMustFulfillFromAdd": {
"deliverableLineIds": ["gid://shopify/DeliverableCartLine/2"],
"locationIds": ["gid://shopify/Location/3333333333"]
}
},
{
"deliverableLinesMustFulfillFromAdd": {
"deliverableLineIds": ["gid://shopify/DeliverableCartLine/1"],
"locationIds": ["gid://shopify/Location/1111111111", "gid://shopify/Location/2222222222"]
}
}
]
}
tests/no-location-configured.input.json{
"cart": {
"deliverableLines": [
{ "id": "gid://shopify/DeliverableCartLine/1", "merchandise": { "__typename": "ProductVariant", "product": { "hasAnyTag": true } } }
]
},
"locations": [{ "id": "gid://shopify/Location/1111111111" }],
"fulfillmentConstraintRule": { "metafield": null }
}
tests/no-location-configured.output.json{ "operations": [] }
tests/only-in-stock-items.input.json{
"cart": {
"deliverableLines": [
{ "id": "gid://shopify/DeliverableCartLine/1", "merchandise": { "__typename": "ProductVariant", "product": { "hasAnyTag": false } } }
]
},
"locations": [
{ "id": "gid://shopify/Location/1111111111" },
{ "id": "gid://shopify/Location/2222222222" },
{ "id": "gid://shopify/Location/3333333333" }
],
"fulfillmentConstraintRule": {
"metafield": {
"jsonValue": { "preorderTags": ["pre-order"], "preorderLocationId": "gid://shopify/Location/3333333333" }
}
}
}
tests/only-in-stock-items.output.json{ "operations": [] }
Written by Lintel. MIT license: free to use in your own store and in client work.
Limits and edge cases
- If a constrained item isn’t stocked at an allowed location, checkout shows no shipping rates and can’t be completed. Keep pre-order inventory at the pre-order location.
- Mutually exclusive constraints also remove all shipping rates; test carts with every product type.
- The rule shows under Settings › Shipping and delivery › Order routing with the Function’s title and description.
- A store can activate up to 25 fulfillment constraint Functions.
Questions
- How do I ship pre-orders separately from in-stock items on Shopify?
- Stock pre-orders at their own location and use a fulfillment constraints Function that ties pre-order lines to that location and other lines to the rest, as this example does.
- Does this also apply to local pickup?
- Only to the delivery method types you pass to fulfillmentConstraintRuleCreate. This example uses SHIPPING, so pickup orders aren’t constrained.
- Can I detect pre-orders without tags?
- Deliverable lines don’t expose selling plans, so this example uses a product tag. A variant metafield works too if you prefer.
Shopify documentation
- Written for Function API version 2026-10 and checked against Shopify’s reference on the date shown. Shopify releases a new version every quarter.
- Custom apps that contain Functions only run on Shopify Plus stores; a public app with the same Function runs on any plan unless the page says otherwise.
- Every example ships with test fixtures, and we run them before publishing. Run them again after you change the code: shopify app function run.
- MIT license. Use the code in your own store or in client work.
Need this built, tested and maintained for your store? See Shopify Functions development.
