This is the code companion to the no-app fractional inventory solution: the two Google Apps Scripts that keep a true-decimal stock ledger in Google Sheets while Shopify's integer inventory stays safely rounded down. Published in 2024, still the working pattern in 2026 — Shopify inventory remains whole-numbers-only.
Triggers on edit. Watches the order export tab, copies the order across, and subtracts each line item's decimal quantity from the matching SKU in "Precise Inventory".
function onEditScriptA(e) {
var ss = e.source;
var editedSheet = ss.getActiveSheet();
var editedRange = e.range;
// Check if the edited sheet is "Export from Shopify - Orders"
if (editedSheet.getName() === "Export from Shopify - Orders") {
var editedRow = editedRange.getRow();
var lastColumn = editedSheet.getLastColumn();
var orderData = editedSheet.getRange(editedRow, 1, 1, lastColumn).getValues()[0];
// Get the "Import to Shopify - Products" sheet
var importSheet = ss.getSheetByName("Import to Shopify - Products");
var importLastRow = importSheet.getLastRow();
// Copy the order to "Import to Shopify - Products"
importSheet.appendRow(orderData);
// Adjust corresponding inventory in the "Precise Inventory" sheet
var preciseInventorySheet = ss.getSheetByName("Precise Inventory");
var skuColumnIndex = 1; // Assuming SKU is in the first column
// Loop through line items and adjust inventory
for (var i = 2; i <= lastColumn; i++) {
var sku = orderData[i];
var quantity = orderData[i];
// Find the row in "Precise Inventory" corresponding to the SKU
var skuRange = preciseInventorySheet.getRange(2, skuColumnIndex, importLastRow - 1, 1);
var skuValues = skuRange.getValues();
for (var j = 0; j < skuValues.length; j++) {
if (skuValues[j][0] === sku) {
preciseInventorySheet.getRange(j + 2, 2).setValue(
preciseInventorySheet.getRange(j + 2, 2).getValue() - quantity
);
break;
}
}
}
}
}
Also triggers on edit. Matches SKUs between "Precise Inventory" and "Import to Shopify", inserting the revised values into the column Matrixify imports.
function onEditScriptB(e) {
var ss = e.source;
var editedSheet = ss.getActiveSheet();
// Check if the edited sheet is "Import to Shopify - Products"
if (editedSheet.getName() === "Import to Shopify - Products") {
var lastRow = editedSheet.getLastRow();
var lastColumn = editedSheet.getLastColumn();
var preciseInventorySheet = ss.getSheetByName("Precise Inventory");
var preciseInventoryRange = preciseInventorySheet.getRange("A2:B");
var preciseInventoryValues = preciseInventoryRange.getValues();
// Loop through each row in "Import to Shopify - Products"
for (var i = 2; i <= lastRow; i++) {
var sku = editedSheet.getRange(i, 1).getValue();
var quantity = editedSheet.getRange(i, lastColumn).getValue();
for (var j = 0; j < preciseInventoryValues.length; j++) {
if (preciseInventoryValues[j][0] === sku) {
preciseInventorySheet.getRange(j + 2, 2).setValue(quantity);
break;
}
}
}
}
}
FLOOR(). Rounding 56.5 up to 57 is how you sell half a yard you don't have.orders/paid webhook in, decimal ledger in Postgres, inventory set through the GraphQL Admin API seconds later. No triggers, no interval, no spreadsheet to babysit. Worth it at high volume or multi-location; overkill below that.The sheet works. When it stops being enough, the same logic moves into a small webhook-driven app. Email Tom with your volume and location count.
Email Tom →