Free operator sheet · Stays in your browser

Recipe Cost
Card.

Drop in ingredients, AP prices, and yields. Walk away with plate cost, target price at your food-cost goal, and a printable card for the binder.

Pack: Operations & Margin Cadence: Per recipe Private — runs in your browser

Fill it in

Recipe Cost Card

Type your numbers — the math runs in your browser. Print it, save it as a CSV, or save it to your Workshop.

Worked example Roasted half-chicken, 4-plate batch See what a Tuesday-morning fill-in looks like
Whole chicken — 1.5 lb each, $3.20/lb, 72% yield$4.80 AP / plate
Olive oil — 0.5 floz, $0.18/floz, 100% yield$0.09
Herb sachet (rosemary, thyme) — 0.25 oz, $1.40/oz, 90% yield$0.39
Pan-roasted potatoes — 6 oz, $0.12/oz, 88% yield$0.82
Lemon-shallot pan jus — 1.5 floz, $0.28/floz, 100% yield$0.42
Plate cost (yield-adjusted)$6.52
At 30% target food cost, target price$21.73
Charm price (rounded up to .95)$21.95

The yield slider is the lever — winter chickens come in heavier-boned and the 72% drops to 68%. At 68% the plate cost lands at $6.91, and the same target moves to $23.95. The decision isn't whether to raise the menu price — the decision is when. Try the yield slider now and see what the price would need to be.

Composite-typical numbers — not a real shop. Use the rhythm, not the figures.

Recipe
Ingredients
Ingredient Qty Unit AP $/unit Yield % Line cost
Total ingredient cost $0.00
Cost per plate $0.00
Plate cost $0.00
Target price (at your food-cost goal) $0.00
Suggested charm price $0.00
Try a scenario — what if vendor prices, yield, or your target shifts?

Slide one or more, the plate cost and target price re-run on top of your typed numbers. Yield drop is one-sided — kitchens lose yield in winter, on holidays, with new line cooks.

Read the long version AP vs EP, why the food-cost target is a policy decision (not a math one), the four lies in dish-level costing, and when to actually raise a menu price. The yield slider — Why dish pricing is a guess until you do this math →

+ (Math.round(v * 100) / 100).toFixed(2); } function charm(v) { // Round target price up to nearest .95 charm endpoint. if (v <= 0) return 0; var whole = Math.floor(v); var frac = v - whole; if (frac <= 0.45) return whole + 0.95; if (frac <= 0.95) return whole + 0.95; return whole + 1 + 0.95; } function lineCost(qty, unit, ap) { // ap is already $ per the user's chosen unit (e.g., $/lb, // $/oz, $/cup). Line cost = qty * ap. Yield is applied // after; yield reduces effective EP qty available for the // recipe — equivalently, raises the unit cost. return n(qty) * n(ap); } // D8 · Read what-if slider state. AP drift is multiplicative, // yield drop is additive on the typed yield (one-sided), target // delta is additive in percentage points. function whatif() { var f = document.getElementById('sheet-fields'); if (!f || !f.whatif_ap) return { ap: 0, yld: 0, target: 0 }; return { ap: n(f.whatif_ap.value), yld: n(f.whatif_yield.value), target: n(f.whatif_target.value), }; } function recalc() { var form = document.getElementById('sheet-fields'); if (!form) return; var w = whatif(); var apMult = 1 + w.ap / 100; var total = 0; for (var i = 1; i <= 5; i++) { var qty = n(form['ing_qty_' + i].value); var rawAp = n(form['ing_ap_' + i].value); var ap = rawAp * apMult; // Effective yield: typed minus the (one-sided) drop, floor 1. var rawYld = Math.max(1, n(form['ing_yield_' + i].value)); var yld = Math.max(1, Math.min(100, rawYld + w.yld)); // Yield % expresses what's usable after trim. Effective // unit cost goes UP by the inverse: $/EP = ($/AP) / (yield/100) var line = (qty * ap) / (yld / 100); total += isFinite(line) ? line : 0; var out = form['ing_cost_' + i]; if (out) out.value = qty && ap ? fmt$(line) : '—'; } var plates = Math.max(1, n(form.plates_yielded.value)); var perPlate = total / plates; var rawPct = Math.max(1, Math.min(60, n(form.target_food_cost_pct.value))); var pct = Math.max(1, Math.min(60, rawPct + w.target)) / 100; var target = pct > 0 ? perPlate / pct : 0; form.total_ingredient_cost.value = fmt$(total); form.cost_per_plate.value = fmt$(perPlate); // Update what-if output labels. if (form.whatif_ap_out) form.whatif_ap_out.value = (w.ap >= 0 ? '+' : '') + w.ap.toFixed(0) + '%'; if (form.whatif_yield_out) form.whatif_yield_out.value = w.yld.toFixed(1) + 'pt'; if (form.whatif_target_out) form.whatif_target_out.value = (w.target >= 0 ? '+' : '') + w.target.toFixed(2) + 'pt'; var pc = document.querySelector('[data-output="plate_cost"]'); if (pc) pc.textContent = fmt$(perPlate); var tp = document.querySelector('[data-output="target_price"]'); if (tp) tp.textContent = fmt$(target); var cp = document.querySelector('[data-output="charm_price"]'); if (cp) cp.textContent = fmt$(charm(target)); } // D8 · Reset scenario sliders. document.addEventListener('click', function (ev) { if (ev.target && ev.target.matches && ev.target.matches('[data-whatif-reset]')) { ev.preventDefault(); var f = document.getElementById('sheet-fields'); if (!f) return; if (f.whatif_ap) f.whatif_ap.value = 0; if (f.whatif_yield) f.whatif_yield.value = 0; if (f.whatif_target) f.whatif_target.value = 0; f.dispatchEvent(new Event('input', { bubbles: true })); } }); // Per-sheet collect: shape rows for CSV export — recipe header, // then ingredients table, then summary outputs. Mirrors what // an operator would paste into Sheets/Excel. function collect() { var f = document.getElementById('sheet-fields'); if (!f) return [['Field', 'Value']]; var rows = []; rows.push(['Recipe', f.recipe_name.value || '']); rows.push(['Plates yielded', f.plates_yielded.value || '']); rows.push(['Target food-cost %', f.target_food_cost_pct.value || '']); rows.push([]); rows.push(['Ingredient', 'Qty', 'Unit', 'AP $/unit', 'Yield %', 'Line cost']); for (var i = 1; i <= 5; i++) { var name = f['ing_name_' + i].value; var qty = f['ing_qty_' + i].value; if (!name && !qty) continue; rows.push([ name || '', qty || '', f['ing_unit_' + i].value || '', f['ing_ap_' + i].value || '', f['ing_yield_'+ i].value || '', (f['ing_cost_'+ i].value || '').replace(/^—$/, ''), ]); } rows.push([]); rows.push(['Total ingredient cost', f.total_ingredient_cost.value]); rows.push(['Cost per plate', f.cost_per_plate.value]); var tp = document.querySelector('[data-output="target_price"]'); var cp = document.querySelector('[data-output="charm_price"]'); if (tp) rows.push(['Target price', tp.textContent]); if (cp) rows.push(['Charm price', cp.textContent]); return rows; } if (window.SheetPage) { window.SheetPage.register({ slug: 'recipe-cost-card', collect: collect, recalc: recalc, }); } })();

Keyboard: ⌘P print · ⌘S download CSV · ⌘↵ save to Workshop

When to use it

Pull this sheet out when —

  • You are about to add a new dish and need a price that holds the food-cost target.
  • A protein price jumped at receiving and you want to know whether the dish still pays.
  • Your kitchen lead is asking why the lamb portion needs to drop a quarter-ounce.
Common mistakes

What operators get wrong

  • Using the AP price as the cost — the kitchen pays the EP cost after trim and yield.
  • Forgetting the garnish, the bread, the pickles, the sauce. They add up.
  • Pricing on cost-plus-50% without checking what the dish across the street goes for.
Pairs with

The tools, terms, and articles this sheet sits next to.

Free, no signup. Your numbers never leave this page.