Diagnostic Inventory and Stock

Out of stock products remain addable to cart

Stock hits zero, the product page still shows a live buy button, and a customer orders something you cannot ship. It looks like a bug in the storefront. It is not. Shopware only disables purchasing when a product has the closeout flag set, and if that flag was never turned on, the product stays purchasable no matter how far stock drops. Here is why that happens and a small script that finds every product currently in that state.

Python and Node.js Admin API Safe by default (dry run)
The short answer

Whether a Shopware 6 product's buy button is active comes from the computed available flag, not directly from stock or availableStock. StockUpdater recalculates available from stock, minPurchase, and isCloseout, and it only flips available to false when stock runs out if isCloseout (labeled Clearance sale in the admin) is set to true on that product. If isCloseout is false, Shopware always treats the product as available, however low stock goes. On top of that, availableStock is stock minus open, unfulfilled order line reservations, and it is only kept fresh by order placed subscribers and the message queue, so a stalled worker can leave it stale and positive even after real stock hits zero. Search for products where available is true, stock or availableStock is zero or less, and isCloseout is false. Do not patch stock values directly. Report the list, and the only safe automated write is a DRY_RUN-gated PATCH that sets isCloseout to true. Full code, tests, and sources are below.

The problem in plain words

A shopper sees a product page with an active Add to cart button and orders three units of something that has zero units in the warehouse. Support gets the complaint, someone checks the product in the admin, and stock really does read zero. The natural assumption is that Shopware is supposed to gray out the button automatically once stock runs out. It does not, not by itself.

Shopware does not read stock at the point of sale. It reads a separate computed field called available, a plain boolean that gets recalculated by StockUpdater whenever stock, purchase minimums, or the closeout setting change. That recalculation only ever turns available to false because of exhausted stock when the product's isCloseout field, labeled Clearance sale in the admin product page, is turned on. Leave that toggle off, as most products do by default, and available stays true forever, regardless of how deep into negative territory the stock number goes.

Stock hits zero stock <= 0 StockUpdater runs recalculates available isCloseout true? no (default) available stays true yes: available flips false
Stock reaching zero only changes available when isCloseout is already set to true. Without it, the buy button never turns off on its own.

Why it happens

This is not a storefront rendering bug and not a caching problem. It is how StockUpdater is designed to behave, on purpose, for a specific reason: many merchants want to keep selling a product that shows zero stock, for example when new stock is inbound and backorders are fine. A few concrete ways stores end up with this issue:

The confusing part for a lot of teams is that everything looks correctly configured. Stock is genuinely zero, the product is active, nothing is throwing an error. The one field nobody thought to check is isCloseout. See the citations at the end for the forum threads other merchants have hit and the architecture decision record that documents the intended behavior.

The key insight

stock is just a number. available is the decision, and Shopware only lets zero stock drive that decision when isCloseout says the product is allowed to sell out. So the fix is never to chase stock or availableStock, both of which are derived values StockUpdater recalculates on its own. The fix is to find products where Shopware currently reports available: true while inventory is actually exhausted and isCloseout was never enabled, and let a human decide whether that product should now respect its stock.

The fix, as a flow

We search for the exact combination that causes this: available true, stock or available stock at zero or below, and isCloseout false. Every hit is a real false positive, a product Shopware is currently letting customers buy despite having nothing left. We report each one, and only PATCH isCloseout to true when explicitly told to write, never touching stock or availableStock themselves.

Search products available=true, stock<=0 Read isCloseout per product hit isCloseout false? no, skip (already protected) Not a false positive yes, falsely purchasable Report, DRY_RUN-gated PATCH isCloseout
Only products where available is true, stock is exhausted, and isCloseout is off get reported. The only write, gated by DRY_RUN, sets isCloseout, never stock or availableStock directly.

Build it step by step

1

Get an Admin API access token

Create an integration in Shopware under Settings, System, Integrations, and note its access key id and secret key. Exchange them for a bearer token with POST /api/oauth/token using grant_type: client_credentials. Keep the shop URL and credentials in environment variables, never in the file.

setup (shell)
pip install requests

export SHOPWARE_URL="https://yourstore.example.com"
export SHOPWARE_CLIENT_ID="SWIAXXXXXXXXXXXXXXXXXXXXXX"
export SHOPWARE_CLIENT_SECRET="..."
export DRY_RUN="true"   # start safe, change to false to write
setup (shell)
// Node 18+ has fetch built in, no dependencies needed

export SHOPWARE_URL="https://yourstore.example.com"
export SHOPWARE_CLIENT_ID="SWIAXXXXXXXXXXXXXXXXXXXXXX"
export SHOPWARE_CLIENT_SECRET="..."
export DRY_RUN="true"   // start safe, change to false to write
2

Authenticate and call the Admin API

A small helper gets a bearer token once, then sends every request with Authorization: Bearer <token> and Accept: application/json. We reuse it for the product search and for the optional isCloseout patch.

step2.py
import os, requests

SHOPWARE_URL = os.environ["SHOPWARE_URL"].rstrip("/")
CLIENT_ID = os.environ["SHOPWARE_CLIENT_ID"]
CLIENT_SECRET = os.environ["SHOPWARE_CLIENT_SECRET"]

def get_token():
    r = requests.post(
        f"{SHOPWARE_URL}/api/oauth/token",
        json={"grant_type": "client_credentials", "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET},
        timeout=30,
    )
    r.raise_for_status()
    return r.json()["access_token"]

def api_patch(path, token, body):
    r = requests.patch(
        f"{SHOPWARE_URL}{path}",
        headers={"Authorization": f"Bearer {token}", "Accept": "application/json", "Content-Type": "application/json"},
        json=body,
        timeout=30,
    )
    r.raise_for_status()
    return r.json() if r.text else {}
step2.js
const SHOPWARE_URL = (process.env.SHOPWARE_URL || "").replace(/\/$/, "");
const CLIENT_ID = process.env.SHOPWARE_CLIENT_ID;
const CLIENT_SECRET = process.env.SHOPWARE_CLIENT_SECRET;

async function getToken() {
  const res = await fetch(`${SHOPWARE_URL}/api/oauth/token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ grant_type: "client_credentials", client_id: CLIENT_ID, client_secret: CLIENT_SECRET }),
  });
  if (!res.ok) throw new Error(`Shopware auth ${res.status}`);
  const body = await res.json();
  return body.access_token;
}

async function apiPatch(path, token, body) {
  const res = await fetch(`${SHOPWARE_URL}${path}`, {
    method: "PATCH",
    headers: { Authorization: `Bearer ${token}`, Accept: "application/json", "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`Shopware ${res.status} on ${path}`);
  const text = await res.text();
  return text ? JSON.parse(text) : {};
}
3

Search for products that look purchasable but have no stock

Ask /api/search/product for products where available is true and either stock or availableStock is zero or below, and read back isCloseout so the next step can tell which of those hits are the real problem.

step3.py
FIELDS = ["id", "productNumber", "stock", "availableStock", "isCloseout", "available"]

def search_candidates(token, stock_field):
    body = {
        "page": 1,
        "limit": 100,
        "filter": [
            {"type": "equals", "field": "available", "value": True},
            {"type": "range", "field": stock_field, "parameters": {"lte": 0}},
        ],
        "associations": {},
        "total-count-mode": 1,
    }
    r = requests.post(
        f"{SHOPWARE_URL}/api/search/product",
        headers={"Authorization": f"Bearer {token}", "Accept": "application/json", "Content-Type": "application/json"},
        json=body,
        timeout=30,
    )
    r.raise_for_status()
    return r.json()["data"]
step3.js
const FIELDS = ["id", "productNumber", "stock", "availableStock", "isCloseout", "available"];

async function searchCandidates(token, stockField) {
  const body = {
    page: 1,
    limit: 100,
    filter: [
      { type: "equals", field: "available", value: true },
      { type: "range", field: stockField, parameters: { lte: 0 } },
    ],
    associations: {},
    "total-count-mode": 1,
  };
  const res = await fetch(`${SHOPWARE_URL}/api/search/product`, {
    method: "POST",
    headers: { Authorization: `Bearer ${token}`, Accept: "application/json", "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`Shopware ${res.status} on product search`);
  const data = await res.json();
  return data.data;
}
4

Decide, with one pure function

Keep the decision separate from any network call. Given a product's stock, availableStock, isCloseout, and available, return true only when Shopware currently reports the item purchasable, stock is exhausted, and the closeout flag that would normally stop this was never turned on. Nothing here talks to the network, so it is trivial to test.

decide.py
def is_falsely_purchasable(product):
    return (
        product.get("available") is True
        and product.get("isCloseout") is False
        and (product.get("stock", 0) <= 0 or product.get("availableStock", 0) <= 0)
    )
decide.js
export function isFalselyPurchasable(product) {
  return (
    product.available === true &&
    product.isCloseout === false &&
    (product.stock <= 0 || product.availableStock <= 0)
  );
}
5

Report first, patch isCloseout only when asked

For every product the pure function flags, emit a review record with its id, product number, stock, available stock, closeout flag, and available flag. Never write to stock or availableStock directly, both are derived and recalculated by StockUpdater, so a manual write can desync them from real inventory or from open orders. The only safe automated write is idempotent: PATCH /api/product/{id} with {"isCloseout": true}, which only tells Shopware to respect the existing zero stock going forward.

apply.py
def enable_closeout(token, product_id):
    # Idempotent, non-destructive: tells Shopware to respect existing stock=0
    # on its next recalculation. Never patch stock or availableStock directly.
    return api_patch(f"/api/product/{product_id}", token, {"isCloseout": True})
apply.js
async function enableCloseout(token, productId) {
  // Idempotent, non-destructive: tells Shopware to respect existing stock=0
  // on its next recalculation. Never patch stock or availableStock directly.
  return apiPatch(`/api/product/${productId}`, token, { isCloseout: true });
}
6

Wire it together with a dry run guard

The loop searches both the stock and availableStock range filters, de-duplicates by product id, runs the pure classifier, and logs every falsely purchasable product it finds. It only calls the isCloseout patch when DRY_RUN is off, and it never touches stock numbers themselves. Run it on a schedule, or on demand right after you notice the first complaint.

Run it safe

Never patch stock or availableStock. They are recalculated by StockUpdater from real inventory and open order reservations, and a manual write can desync them further. Start with DRY_RUN=true, review the reported products, and confirm each one really should stop selling before switching to isCloseout writes.

The full code

Here is the complete script in one file for each language. It authenticates, searches for products that are marked available despite exhausted stock, classifies each with the pure function, reports every hit, and only patches isCloseout when DRY_RUN is explicitly turned off.

View this code on GitHub Full runnable folder with tests in the shopware-fixes repo.

find_falsely_purchasable.py
"""Find Shopware 6 products that stay purchasable after stock hits zero.

Shopware decides whether the buy button is active from the computed available
flag, not directly from stock or availableStock. StockUpdater only flips
available to false when stock runs out if isCloseout (Clearance sale in the
admin) is set to true. Without it, available stays true no matter how low
stock goes, so the product remains addable to cart.

This searches for products where available is true and stock or
availableStock is zero or below, then keeps only the ones where isCloseout is
false, since those are the true positives: Shopware currently reports the
item purchasable, inventory is exhausted, and the flag that would normally
stop this was never turned on.

Never patches stock or availableStock directly, both are derived and
reservation-driven values StockUpdater recalculates on its own. The only
write, gated by DRY_RUN, is an idempotent PATCH of isCloseout to true, which
only tells Shopware to respect the existing stock on its next recalculation.
Run on demand or on a schedule. Safe to run again and again.
"""
import os
import logging
import requests

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("find_falsely_purchasable")

SHOPWARE_URL = os.environ["SHOPWARE_URL"].rstrip("/")
CLIENT_ID = os.environ["SHOPWARE_CLIENT_ID"]
CLIENT_SECRET = os.environ["SHOPWARE_CLIENT_SECRET"]
DRY_RUN = os.environ.get("DRY_RUN", "true").lower() == "true"

FIELDS = ["id", "productNumber", "stock", "availableStock", "isCloseout", "available"]


def is_falsely_purchasable(product):
    """Pure decision. No I/O. Takes the four product fields and returns a verdict."""
    return (
        product.get("available") is True
        and product.get("isCloseout") is False
        and (product.get("stock", 0) <= 0 or product.get("availableStock", 0) <= 0)
    )


def get_token():
    r = requests.post(
        f"{SHOPWARE_URL}/api/oauth/token",
        json={"grant_type": "client_credentials", "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET},
        timeout=30,
    )
    r.raise_for_status()
    return r.json()["access_token"]


def api_patch(path, token, body):
    r = requests.patch(
        f"{SHOPWARE_URL}{path}",
        headers={"Authorization": f"Bearer {token}", "Accept": "application/json", "Content-Type": "application/json"},
        json=body,
        timeout=30,
    )
    r.raise_for_status()
    return r.json() if r.text else {}


def search_candidates(token, stock_field):
    body = {
        "page": 1,
        "limit": 100,
        "filter": [
            {"type": "equals", "field": "available", "value": True},
            {"type": "range", "field": stock_field, "parameters": {"lte": 0}},
        ],
        "associations": {},
        "total-count-mode": 1,
    }
    r = requests.post(
        f"{SHOPWARE_URL}/api/search/product",
        headers={"Authorization": f"Bearer {token}", "Accept": "application/json", "Content-Type": "application/json"},
        json=body,
        timeout=30,
    )
    r.raise_for_status()
    return r.json()["data"]


def collect_candidates(token):
    seen = {}
    for stock_field in ("availableStock", "stock"):
        for product in search_candidates(token, stock_field):
            seen[product["id"]] = product
    return list(seen.values())


def enable_closeout(token, product_id):
    # Idempotent, non-destructive: tells Shopware to respect existing stock=0
    # on its next recalculation. Never patch stock or availableStock directly.
    return api_patch(f"/api/product/{product_id}", token, {"isCloseout": True})


def run():
    token = get_token()
    flagged = 0
    for product in collect_candidates(token):
        if not is_falsely_purchasable(product):
            continue
        log.warning(
            "Product %s (%s): available=%s stock=%s availableStock=%s isCloseout=%s. %s",
            product.get("productNumber"), product["id"], product.get("available"),
            product.get("stock"), product.get("availableStock"), product.get("isCloseout"),
            "would set isCloseout=true" if DRY_RUN else "setting isCloseout=true",
        )
        if not DRY_RUN:
            enable_closeout(token, product["id"])
        flagged += 1
    log.info("Done. %d falsely purchasable product(s) %s.", flagged, "to fix" if DRY_RUN else "fixed")


if __name__ == "__main__":
    run()
find-falsely-purchasable.js
/**
 * Find Shopware 6 products that stay purchasable after stock hits zero.
 *
 * Shopware decides whether the buy button is active from the computed available
 * flag, not directly from stock or availableStock. StockUpdater only flips
 * available to false when stock runs out if isCloseout (Clearance sale in the
 * admin) is set to true. Without it, available stays true no matter how low
 * stock goes, so the product remains addable to cart.
 *
 * This searches for products where available is true and stock or
 * availableStock is zero or below, then keeps only the ones where isCloseout is
 * false, since those are the true positives: Shopware currently reports the
 * item purchasable, inventory is exhausted, and the flag that would normally
 * stop this was never turned on.
 *
 * Never patches stock or availableStock directly, both are derived and
 * reservation-driven values StockUpdater recalculates on its own. The only
 * write, gated by DRY_RUN, is an idempotent PATCH of isCloseout to true, which
 * only tells Shopware to respect the existing stock on its next recalculation.
 * Run on demand or on a schedule. Safe to run again and again.
 *
 * Guide: https://www.allanninal.dev/shopware/out-of-stock-still-purchasable/
 */
import { pathToFileURL } from "node:url";

const SHOPWARE_URL = (process.env.SHOPWARE_URL || "https://example.test").replace(/\/$/, "");
const CLIENT_ID = process.env.SHOPWARE_CLIENT_ID || "dummy-client-id";
const CLIENT_SECRET = process.env.SHOPWARE_CLIENT_SECRET || "dummy-secret";
const DRY_RUN = (process.env.DRY_RUN || "true").toLowerCase() === "true";

export function isFalselyPurchasable(product) {
  return (
    product.available === true &&
    product.isCloseout === false &&
    (product.stock <= 0 || product.availableStock <= 0)
  );
}

async function getToken() {
  const res = await fetch(`${SHOPWARE_URL}/api/oauth/token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ grant_type: "client_credentials", client_id: CLIENT_ID, client_secret: CLIENT_SECRET }),
  });
  if (!res.ok) throw new Error(`Shopware auth ${res.status}`);
  const body = await res.json();
  return body.access_token;
}

async function apiPatch(path, token, body) {
  const res = await fetch(`${SHOPWARE_URL}${path}`, {
    method: "PATCH",
    headers: { Authorization: `Bearer ${token}`, Accept: "application/json", "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`Shopware ${res.status} on ${path}`);
  const text = await res.text();
  return text ? JSON.parse(text) : {};
}

async function searchCandidates(token, stockField) {
  const body = {
    page: 1,
    limit: 100,
    filter: [
      { type: "equals", field: "available", value: true },
      { type: "range", field: stockField, parameters: { lte: 0 } },
    ],
    associations: {},
    "total-count-mode": 1,
  };
  const res = await fetch(`${SHOPWARE_URL}/api/search/product`, {
    method: "POST",
    headers: { Authorization: `Bearer ${token}`, Accept: "application/json", "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`Shopware ${res.status} on product search`);
  const data = await res.json();
  return data.data;
}

async function collectCandidates(token) {
  const seen = new Map();
  for (const stockField of ["availableStock", "stock"]) {
    const products = await searchCandidates(token, stockField);
    for (const product of products) seen.set(product.id, product);
  }
  return Array.from(seen.values());
}

async function enableCloseout(token, productId) {
  // Idempotent, non-destructive: tells Shopware to respect existing stock=0
  // on its next recalculation. Never patch stock or availableStock directly.
  return apiPatch(`/api/product/${productId}`, token, { isCloseout: true });
}

export async function run() {
  const token = await getToken();
  let flagged = 0;
  for (const product of await collectCandidates(token)) {
    if (!isFalselyPurchasable(product)) continue;
    console.warn(
      `Product ${product.productNumber} (${product.id}): available=${product.available} stock=${product.stock} availableStock=${product.availableStock} isCloseout=${product.isCloseout}. ${DRY_RUN ? "would set isCloseout=true" : "setting isCloseout=true"}`
    );
    if (!DRY_RUN) await enableCloseout(token, product.id);
    flagged++;
  }
  console.log(`Done. ${flagged} falsely purchasable product(s) ${DRY_RUN ? "to fix" : "fixed"}.`);
}

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
  run().catch((err) => { console.error(err); process.exit(1); });
}

Add a test

The classifier is the part most worth testing, because it decides which products get reported and, eventually, patched. Because is_falsely_purchasable is pure and takes a plain object, the test needs no network and no live Shopware store.

test_stock_purchasable.py
from find_falsely_purchasable import is_falsely_purchasable


def product(**over):
    base = {"stock": 0, "availableStock": 0, "isCloseout": False, "available": True}
    base.update(over)
    return base


def test_falsely_purchasable_when_out_of_stock_and_not_closeout():
    assert is_falsely_purchasable(product()) is True


def test_not_flagged_when_closeout_is_enabled():
    assert is_falsely_purchasable(product(isCloseout=True)) is False


def test_not_flagged_when_not_available():
    assert is_falsely_purchasable(product(available=False)) is False


def test_not_flagged_when_stock_and_available_stock_are_positive():
    assert is_falsely_purchasable(product(stock=5, availableStock=5)) is False


def test_flagged_when_only_available_stock_is_exhausted():
    assert is_falsely_purchasable(product(stock=5, availableStock=0)) is True


def test_flagged_when_only_stock_is_exhausted():
    assert is_falsely_purchasable(product(stock=0, availableStock=5)) is True


def test_flagged_when_stock_is_negative():
    assert is_falsely_purchasable(product(stock=-2, availableStock=-2)) is True
purchasable.test.js
import { test } from "node:test";
import assert from "node:assert/strict";
import { isFalselyPurchasable } from "./find-falsely-purchasable.js";

const product = (over = {}) => ({ stock: 0, availableStock: 0, isCloseout: false, available: true, ...over });

test("falsely purchasable when out of stock and not closeout", () => {
  assert.equal(isFalselyPurchasable(product()), true);
});

test("not flagged when closeout is enabled", () => {
  assert.equal(isFalselyPurchasable(product({ isCloseout: true })), false);
});

test("not flagged when not available", () => {
  assert.equal(isFalselyPurchasable(product({ available: false })), false);
});

test("not flagged when stock and available stock are positive", () => {
  assert.equal(isFalselyPurchasable(product({ stock: 5, availableStock: 5 })), false);
});

test("flagged when only available stock is exhausted", () => {
  assert.equal(isFalselyPurchasable(product({ stock: 5, availableStock: 0 })), true);
});

test("flagged when only stock is exhausted", () => {
  assert.equal(isFalselyPurchasable(product({ stock: 0, availableStock: 5 })), true);
});

test("flagged when stock is negative", () => {
  assert.equal(isFalselyPurchasable(product({ stock: -2, availableStock: -2 })), true);
});

Case studies

Migrated catalog

The import that carried stock but not the closeout flag

A store migrated a few thousand products from an older platform. The importer mapped stock correctly for every item, but the source system had no direct equivalent of isCloseout, so the field was left at its default of false across the entire catalog. Stock levels behaved normally for months, until the first products naturally sold down to zero and customers kept ordering them anyway.

Running the search in dry run surfaced every product where available was true against exhausted stock and isCloseout was false, which turned out to be exactly the products that had sold out since the migration. The merchandising team reviewed the list, confirmed each one should now respect its own stock, and let the script patch isCloseout to true for that batch. New arrivals coming in with the flag correctly set from then on.

Stalled queue

The availableStock that stayed positive after real stock hit zero

A store's message queue worker had silently stopped consuming jobs after a deployment, so order-placed events that should have decremented availableStock were piling up unprocessed. Real stock had already dropped to zero from a flash sale, but availableStock stayed stuck at a stale positive number from before the queue backed up, and the product kept accepting orders it could not fulfill.

Searching on both stock and availableStock caught the product either way. The team restarted messenger:consume, confirmed the scheduled task tied to stock recalculation was running again, and used the reported list to set isCloseout on the affected product as a stopgap while the backlog cleared.

What good looks like

After this runs, a zero-stock product that is still showing an active buy button stops being a surprise found through a customer complaint. Every product where available is true against exhausted stock and isCloseout is false gets reported by id, product number, and its four decision fields, and the only write ever made is a safe, idempotent isCloseout patch that lets Shopware's own StockUpdater do the rest on its next recalculation.

FAQ

Why can customers still add an out of stock product to the cart in Shopware 6?

Shopware decides whether the buy button is active from the computed available flag, not directly from stock or availableStock. StockUpdater only flips available to false when stock reaches zero if the product also has isCloseout (labeled Clearance sale in the admin) set to true. If isCloseout is false, Shopware treats the product as available no matter how low stock goes.

Does setting stock to zero automatically stop a product from being purchased?

No. Stock reaching zero only matters when isCloseout is true on that product. Without it, StockUpdater recalculates available as true regardless of stock, so the product keeps showing an active buy button and stays in search and listing results as purchasable.

Is it safe to fix this by writing to stock or availableStock directly?

No. Both stock and availableStock are derived and reservation driven values that StockUpdater recalculates from real inventory and open order line reservations. Forcing them by hand can desync the product from real stock levels or from orders that are still open. The safe, idempotent fix is a PATCH to isCloseout, which only tells Shopware to respect the existing stock the next time it recalculates.

Related field notes

Citations

On the problem:

  1. Out of stock can be added to cart (Shopware Community Forum). forum.shopware.com/t/out-of-stock-can-be-added-to-cart/93953
  2. Produkt mit Bestand 0 trotzdem verfugbar (Shopware Community Forum). forum.shopware.com/t/produkt-mit-bestand-0-trotzdem-verfuegbar/86879
  3. Stock storage refactoring (shopware/shopware Discussion #3172). github.com/shopware/shopware/discussions/3172

On the solution:

  1. Available stock improvements (ADR 2022-03-25). developer.shopware.com/docs/resources/references/adr/2022-03-25-available-stock.html
  2. Stock Configuration, Shopware Documentation. developer.shopware.com/docs/guides/hosting/configurations/shopware/stock.html
  3. Stock Manipulation API (ADR 2023-05-15). developer.shopware.com/docs/resources/references/adr/2023-05-15-stock-api.html

Stuck on a tricky one?

If you have a problem in Shopware orders, payments, inventory, or the message queue that you would rather hand off, this is the kind of work I do. Message me and we can work through it together.

Contact me on LinkedIn

Did this stop a phantom sale?

If this saved you from shipping an order you never had stock for, you can buy me a coffee. It is the best way to keep these field notes free and growing.

Buy me a coffee on Ko-fi

Back to all Shopware field notes