Most BC implementations have a problem with safety stock. Either the value is zero (so nothing protects against stockouts), or it’s a number someone wrote during a training course three years ago and nobody updates. Neither is the right answer. Safety stock is a statistical quantity — it has a defensible value derived from how variable your demand is, how variable your supplier’s lead times are, and what service level you’ve decided to commit to. This post walks through the theory, then shows you a free, open-source BC extension that does the calculation properly using actual data from your Item Ledger Entries and Purchase Receipts.
If you only want the code: github.com/GmsoftLtd/bc-safety-stock.
What safety stock actually is

Safety stock is the buffer inventory you hold to absorb variability you can’t predict. It exists because two things wobble:
- Demand wobbles — Tuesdays you sell 50 units, Wednesdays you sell 80, sometimes nothing for a week, sometimes a rush of 200
- Lead times wobble — your supplier’s quoted 14 days actually arrives between day 10 and day 21 depending on transport, customs, weather, factory load
If both demand and supply were perfectly stable, you’d need zero safety stock — you’d order exactly the right quantity to arrive exactly when stock runs out. In the real world, both wobble. Safety stock is the explicit price you pay (in tied-up capital) to absorb that wobble without losing sales.
The question is: how much wobble is each item subject to, and how much of it do you want to hedge? That’s where the math comes in.
The Z-score method
Safety stock theory borrows heavily from statistical process control. The core insight: if you treat daily demand as a normally distributed random variable (with some mean and some standard deviation), you can compute how many standard deviations of buffer you need to cover any chosen probability of not stocking out.
That number of standard deviations is the Z-score, and it maps directly to a target service level:
| Service Level | Z-Score | Meaning |
|---|---|---|
| 70% | 0.52 | 30% chance of stockout per cycle (almost no protection) |
| 80% | 0.84 | One stockout in five replenishment cycles |
| 90% | 1.28 | One in ten |
| 95% | 1.65 | One in twenty (industry standard for non-critical items) |
| 97.5% | 1.96 | One in forty |
| 99% | 2.33 | One in a hundred |
| 99.9% | 3.09 | One in a thousand (mission-critical: pharma, aerospace) |
Pick your service level deliberately. Higher = more stock, more tied-up cash, fewer stockouts. There’s no universally right number — for a fast-moving B-class item, 95% is fine. For a finished-goods SKU your top customer is angry about, you’re going to commit to 99%.
Three calculation methods
The Z-score is just one piece. The other piece is what you multiply it by. There are three increasingly sophisticated formulas:
1. Basic (rule-of-thumb)
Safety Stock = Average Daily Demand × Safety Days
Where “safety days” is something like 7 or 14 — a flat buffer. Cheap and wrong. It treats all items identically regardless of how variable they actually are. Useful as a placeholder for new items with no history.
2. Demand variability only
Safety Stock = Z × σdemand × √(Lead Time)
This is the formula in most introductory operations textbooks. It assumes lead time is fixed and only demand varies. Defensible if your supplier is reliable to the day; almost never true.
3. Full variance (demand + lead time)
Safety Stock = Z × √( LT × σ²demand + D² × σ²LT )
Where:
- Z = service level Z-score
- LT = average lead time in days
- σdemand = standard deviation of daily demand
- D = average daily demand
- σLT = standard deviation of lead time
This is the formula my free extension uses. It accounts for both sources of wobble.
The intuition: if your supplier is also unreliable (some days 10 LT, some days 21 LT), you need additional buffer to cover the demand that happens during the extra lead time when shipments are late. That’s the D² × σ²LT term.
For an item with average daily demand of 20, demand stddev of 8, average lead time of 14 days, lead time stddev of 3 days, at 95% service level:
Safety Stock = 1.65 × √( 14 × 8² + 20² × 3² )
= 1.65 × √( 896 + 3600 )
= 1.65 × √4496
= 1.65 × 67.05
≈ 111 units
That 111 units is the level you should never drop below if you want to honour your 95% service commitment. Reorder point becomes Avg Daily Demand × Lead Time + Safety Stock = 20 × 14 + 111 = 391 units.
What Business Central does out of the box
BC has a field on the Item table called Safety Stock Quantity. It’s used by the Planning Worksheet to trigger replenishment when projected inventory drops below that level. The field is fully respected by MRP/MPS calculations.
There is no out-of-the-box code to compute it. BC trusts you to fill in the number yourself. In practice, what happens:
- New implementations leave it at zero (no buffer at all)
- Someone copies a value from the old system, which was probably also wrong
- A planner manually edits it for “important” items and forgets the rest
- Nobody recalculates as demand patterns change
This is the gap the free extension fills.
The extension
bc-safety-stock on GitHub — MIT licensed, runtime 14.0 (BC v27+), free forever.
What it adds:
| Object | Type | Purpose |
|---|---|---|
| Safety Stock Setup | Table + Page | Defaults: service level, history window, min observations |
| Safety Stock Calculator | Codeunit | Core math (the full-variance formula above) |
| Safety Stock Calculation Log | Table + Page | Every calculation logged for audit |
| Item Card extension | Page extension | “Calculate Safety Stock” action on single item |
| Item List extension | Page extension | “Calculate Safety Stock (Bulk)” action on filtered/selected items |
| Safety Stock Job Queue Run | Codeunit | Schedule recurring recalculation via Job Queue |
Item Card action — single item
On any Item Card, click Calculate Safety Stock (in the Actions tab). The extension:
- Pulls Item Ledger Entries of type
Salefor the last 180 days (configurable) - Aggregates them by date to get daily demand series
- Computes mean and standard deviation
- Pulls Purchase Receipts for the same item, comparing Order Date to Posting Date
- Computes mean and stddev of actual realised lead times
- Applies the full-variance formula at the configured service level (default 95%)
- Shows you the calculated number and asks “apply?”

If you confirm, it writes to Item."Safety Stock Quantity" and logs the calculation. If you cancel, nothing changes — useful for what-if analysis.
If lead time history is missing (new supplier, recently added item), it falls back to the existing Item."Lead Time Calculation" field on the item.
Item List action — bulk
Open the Item List, filter (e.g. by Item Category Code = FERT, or by Vendor No. = your top supplier), and click Calculate Safety Stock (Bulk) in the Functions menu.
The extension iterates through the filtered set, calculates each, applies the value, and logs every entry to the log table. A progress dialog shows count. Items that fail the minimum-observation threshold or are blocked get a log entry with a “Skipped” result code — no silent failures.
This is the right pattern for periodic recalculation. Once a month, filter by ABC class A items, recalc. Once a quarter, run all inventory items.
Job Queue option — scheduled
For production environments where you want this to happen automatically, the included Safety Stock Job Queue Run codeunit (object ID 50101) can be scheduled like any other Job Queue Entry.
Example: monthly recalc of all FERT items at 99% service level:
- Object Type: Codeunit
- Object ID: 50101
- Parameter String:
FILTER=Item Category Code:FERT;SERVICELEVEL=99 - Recurring: Monthly
Without parameters, all inventory items get recalculated at the setup default service level.
Choosing your service level — practical guide
This is where I see the most consultant-side judgement calls. A framework:
- A-class items (top 20% by value, ~80% of revenue) — start at 99%. These are the items where stockouts cost you customer trust and revenue.
- B-class items (next 30%) — 95% is the sweet spot. Defensible buffer, not overcommitted capital.
- C-class items (bottom 50%, often only 5% of revenue) — 85-90%. Sometimes lower. These are commodities; stockouts annoy but don’t kill.
- Items with safety regulations (food, pharma, aerospace) — what the regulation says. Override the formula.
- Make-to-order items — skip safety stock entirely. Push the buffer onto raw materials, not finished goods.
- New items, <10 sales observations — the extension skips these (insufficient data). Use a category default until enough history accumulates.
Run the calculation, then sanity-check the result. If safety stock for a slow mover comes out as 800 units, demand is too lumpy for normal-distribution methods — consider Croston’s method instead (out of scope for this extension; planned for a future version).
When NOT to use this
The Z-score method assumes near-normal demand distribution. It breaks down for:
- Intermittent demand — long gaps between sales, then bursts. Z-score wildly overestimates. Use Croston’s method or category defaults.
- Heavy seasonality — averaging across summer and winter for a swimsuit item gives garbage. Run the calculation during the relevant season window, not annually.
- Configurable / make-to-order items — there is no inventory to buffer.
- New items — no history, no statistics. Apply a category-level default.
- End-of-life items — declining demand. Statistical methods over-stock these. Phase out manually.
The extension’s Insufficient Demand Data result code catches new-item cases. The others need consultant judgement.
Installation
Two paths:
Developer / sandbox (recommended for first try):
git clone https://github.com/GmsoftLtd/bc-safety-stock- Open in VS Code with the AL Language extension
- Confirm app.json ID range (50100–50199) doesn’t conflict with your tenant
- Press F5 (publish + launch)
Production tenant:
- Wait for the first release on the GitHub Releases page (or build the .app file yourself)
- Upload via Extension Management → Manage → Upload Extension
- Open Search → Safety Stock Setup, configure defaults
- Test on a single item before running bulk
The extension only modifies one BC field (Item.Safety Stock Quantity) and inserts into its own log table. It’s non-invasive and safe to uninstall.
What’s next
This is the first piece of a series. Coming soon:
- EOQ (Economic Order Quantity) — companion calculation, classic Wilson formula, decides order quantity
- Reorder Point — when to actually trigger the order
- Combined pillar post — choosing the right replenishment formula for each item class
Each will get its own deep-dive post and a focused repo.
Resources
- Code: github.com/GmsoftLtd/bc-safety-stock
- License: MIT
- My consultancy: GMSOFT Limited
- Issues, PRs welcome — particularly for Croston’s method, seasonal adjustment, per-location calculation
If your BC implementation has the “all zeros” safety stock problem, install this in a sandbox, run it against a real item, look at the calculated number. That’s usually enough to convince a planner that the manual approach was leaving real money on the table.
Questions, edge cases, or a costing variance you can’t explain? Get in touch.

Director and Founder of GMSOFT, a Microsoft Dynamics partner serving the UK, Greece, and Italy. Microsoft Dynamics 365 Community Super User, Season 1 2026 — recognised for top-tier community contribution on Business Central manufacturing topics. Independent BC consultant with 15 years on Business Central and NAV (and 30 in ERP overall). Founder of two BC user groups: Business Central Greece on LinkedIn and Dynamics Business Central User Group Greece (Microsoft Community). Publisher of 3 apps on Microsoft AppSource (ABC Classification Manager, Vendor Dispute, BOM Architect) and open-source AL on GitHub (GmsoftLtd).
Discover more from Inside Business Central
Subscribe to get the latest posts sent to your email.
