Ürün başına minimum ve maksimum adet

Her ürün için minimum ve maksimum sipariş adedini iki ürün metafield’ından okuyup uygulayan Shopify doğrulama Function’ı; test edilmiş JavaScript.

Function API
Cart and Checkout Validation
Hedef (target)
cart.validations.generate.run
Plan
Herkese açık uygulamada her plan; özel uygulamada Shopify Plus
Uygulama yetkisi
write_validations
API sürümü
2026-10
Son kontrol

Kısaca

Bu doğrulama Function’ı, ürün başına minimum ve maksimum adet uygular; ürünün tüm varyantlarını birlikte sayar. Sınırlar, mağaza sahibinin yönetim panelinden düzenlediği iki tam sayı (Integer) ürün metafield’ından gelir. Cart and Checkout Validation API’nin cart.validations.generate.run hedefini kullanır (sürüm 2026-10) ve herkese açık bir uygulamada her planda çalışır.

Nasıl kullanılır

  1. Yönetim panelinde Integer tipinde iki ürün metafield tanımı oluşturun: custom.min_quantity ve custom.max_quantity.
  2. Shopify CLI ile bir uygulama oluşturun ve uzantıyı ekleyin: shopify app generate extension --template cart_checkout_validation, dil olarak JavaScript.
  3. Oluşturulan shopify.extension.toml dosyasını ve src/ içindeki iki dosyayı aşağıdakilerle değiştirin, ardından testleri shopify app function run ile çalıştırın.
  4. shopify app deploy ile deploy edin ve doğrulamayı validationCreate ile etkinleştirin.
  5. Sınır gereken ürünlerde değerleri doldurun; değeri boş ürünlerde sınır yoktur.

Kod

shopify.extension.toml
api_version = "2026-10"

[[extensions]]
name = "Minimum and maximum quantity per product"
handle = "min-max-quantity-per-product"
type = "function"

  [[extensions.targeting]]
  target = "cart.validations.generate.run"
  input_query = "src/cart_validations_generate_run.graphql"
  export = "cart-validations-generate-run"

  [extensions.build]
  command = ""
  path = "dist/function.wasm"
src/cart_validations_generate_run.graphql
query CartValidationsGenerateRunInput {
  buyerJourney {
    step
  }
  cart {
    lines {
      quantity
      merchandise {
        __typename
        ... on ProductVariant {
          product {
            id
            title
            minQuantity: metafield(namespace: "custom", key: "min_quantity") {
              value
            }
            maxQuantity: metafield(namespace: "custom", key: "max_quantity") {
              value
            }
          }
        }
      }
    }
  }
}
src/cart_validations_generate_run.js
// @ts-check

/**
 * @typedef {import("../generated/api").CartValidationsGenerateRunInput} CartValidationsGenerateRunInput
 * @typedef {import("../generated/api").CartValidationsGenerateRunResult} CartValidationsGenerateRunResult
 */

/** Errors about the cart as a whole show at the top of checkout and on the cart. */
const CART_TARGET = "$.cart";

/** @param {{value: string} | null | undefined} metafield */
function readLimit(metafield) {
  const limit = Number.parseInt(metafield?.value ?? "", 10);
  return Number.isInteger(limit) && limit > 0 ? limit : null;
}

/**
 * @param {CartValidationsGenerateRunInput} input
 * @returns {CartValidationsGenerateRunResult}
 */
export function cartValidationsGenerateRun(input) {
  // While the cart is being built, a shopper adding one unit at a time would be
  // stopped by the minimum before reaching it; enforce it from checkout on.
  const checkMinimum = input.buyerJourney?.step !== "CART_INTERACTION";
  // Limits are per product, so add up all variants (sizes, colors) of it.
  /** @type {Map<string, {title: string, quantity: number, min: number | null, max: number | null}>} */
  const products = new Map();
  for (const line of input.cart.lines) {
    if (line.merchandise.__typename !== "ProductVariant") continue;
    const { product } = line.merchandise;
    const entry = products.get(product.id) ?? {
      title: product.title,
      quantity: 0,
      min: readLimit(product.minQuantity),
      max: readLimit(product.maxQuantity),
    };
    entry.quantity += line.quantity;
    products.set(product.id, entry);
  }

  const errors = [];
  for (const { title, quantity, min, max } of products.values()) {
    if (checkMinimum && min !== null && quantity < min) {
      errors.push({ message: `${title}: the minimum order quantity is ${min}.`, target: CART_TARGET });
    } else if (max !== null && quantity > max) {
      errors.push({ message: `${title}: you can buy at most ${max}.`, target: CART_TARGET });
    }
  }

  return { operations: errors.length > 0 ? [{ validationAdd: { errors } }] : [] };
}
Test girdisi ve beklenen çıktı
tests/above-maximum.input.json
{
  "cart": {
    "lines": [
      {
        "quantity": 3,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": { "id": "gid://shopify/Product/1234567892", "title": "Limited Sneaker", "minQuantity": null, "maxQuantity": { "value": "2" } }
        }
      }
    ]
  }
}
tests/above-maximum.output.json
{
  "operations": [
    { "validationAdd": { "errors": [{ "message": "Limited Sneaker: you can buy at most 2.", "target": "$.cart" }] } }
  ]
}
tests/below-minimum-across-variants.input.json
{
  "cart": {
    "lines": [
      {
        "quantity": 1,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": { "id": "gid://shopify/Product/1234567890", "title": "Linen Napkin", "minQuantity": { "value": "4" }, "maxQuantity": null }
        }
      },
      {
        "quantity": 2,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": { "id": "gid://shopify/Product/1234567890", "title": "Linen Napkin", "minQuantity": { "value": "4" }, "maxQuantity": null }
        }
      }
    ]
  }
}
tests/below-minimum-across-variants.output.json
{
  "operations": [
    { "validationAdd": { "errors": [{ "message": "Linen Napkin: the minimum order quantity is 4.", "target": "$.cart" }] } }
  ]
}
tests/below-minimum-while-building-cart.input.json
{
  "buyerJourney": {
    "step": "CART_INTERACTION"
  },
  "cart": {
    "lines": [
      {
        "quantity": 1,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": {
            "id": "gid://shopify/Product/1234567890",
            "title": "Linen Napkin",
            "minQuantity": {
              "value": "4"
            },
            "maxQuantity": null
          }
        }
      },
      {
        "quantity": 2,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": {
            "id": "gid://shopify/Product/1234567890",
            "title": "Linen Napkin",
            "minQuantity": {
              "value": "4"
            },
            "maxQuantity": null
          }
        }
      }
    ]
  }
}
tests/below-minimum-while-building-cart.output.json
{
  "operations": []
}
tests/within-limits.input.json
{
  "cart": {
    "lines": [
      {
        "quantity": 4,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": {
            "id": "gid://shopify/Product/1234567890",
            "title": "Linen Napkin",
            "minQuantity": { "value": "4" },
            "maxQuantity": { "value": "24" }
          }
        }
      },
      {
        "quantity": 1,
        "merchandise": {
          "__typename": "ProductVariant",
          "product": { "id": "gid://shopify/Product/1234567891", "title": "Gift Card Sleeve", "minQuantity": null, "maxQuantity": null }
        }
      }
    ]
  }
}
tests/within-limits.output.json
{ "operations": [] }

Lintel tarafından yazıldı. MIT lisansı: kendi mağazanızda da müşteri işlerinizde de ücretsiz kullanabilirsiniz.

Sınırlar ve özel durumlar

  • Adetler ürün bazında toplanır; 2 küçük ve 2 büyük beden 4 sayılır.
  • Hatalar sepet sayfasında ve checkout’ta görünür. Storefront API de onları Cart nesnesinde döndürür.
  • Mesajlar kodda İngilizce; birden fazla dilde gerekiyorsa input sorgusunda localization.language’ı okuyun.
  • Her metafield okuması, bir Function input’unun kullanabileceği 30 sorgu maliyeti puanından 3’ünü harcar; ürün başına iki metafield sorun değildir.

Sık sorulan sorular

Shopify’da bir ürün için minimum sipariş adedi nasıl belirlenir?
Bu örnekteki gibi minimumu bir ürün metafield’ından okuyup altında checkout’u engelleyen bir doğrulama Function’ı kullanın. Herkese açık bir uygulamada her planda çalışır.
Bir müşterinin bir üründen kaç adet alacağını sınırlayabilir miyim?
Sipariş başına evet: custom.max_quantity’yi ayarlayın. Birden fazla siparişe yayılan sınır için müşterinin ürün bazında geçmiş siparişleri gerekir; Function input’unda bu bilgi yok.
Temadaki adet seçici bu sınırlara uyar mı?
Hayır. Function, sepete maksimumdan fazla ürün eklenmesini durdurur; minimumu ise checkout’tan itibaren kontrol eder, böylece müşteri sepetini minimuma kadar doldurabilir. Daha akıcı bir deneyim için aynı metafield’ları temada okuyup adet alanına min ve max değerlerini verin.
  • Function API sürümü 2026-10 için yazıldı ve gösterilen tarihte Shopify’ın başvuru belgeleriyle karşılaştırıldı. Shopify her çeyrekte yeni bir sürüm yayımlar.
  • Function içeren özel (custom) uygulamalar yalnızca Shopify Plus mağazalarında çalışır. Aynı Function herkese açık bir uygulamada, sayfada aksi yazmıyorsa her planda çalışır.
  • Her örnek test verileriyle gelir ve yayımlamadan önce bu testleri çalıştırırız. Kodu değiştirdikten sonra testleri tekrar çalıştırın: shopify app function run.
  • MIT lisansı. Kodu kendi mağazanızda da müşteri işlerinizde de kullanabilirsiniz.

Mağazanız için kurulmasını, test edilmesini ve bakımını isterseniz: Shopify Functions geliştirme.

Geliştirmek istediğiniz bir şey mi var? Gelin, birlikte konuşalım.

Aynı anda sınırlı sayıda proje alıyoruz.

Gencer KarakayaShopify mühendisi

Proje özetinizi bir mühendis okur; size e-postayla yanıt veririz.