Warehouse scanners usually type into the active cell like a keyboard. A lookup sheet can expand barcode → product fields instantly.
barcodeLookupSheet builds a map from Catalog and fills ScanEntry columns B–D, writing NOT_FOUND when missing.
onEditBarcode shows an installable onEdit filter that only reacts to edits in ScanEntry column A.
Use installable triggers (not simple onEdit) if you need UrlFetch or other authorized services later.
Need this built? Hire a Google Apps Script developer →
Sheet / project setup
| Sheet | Column | Purpose |
|---|---|---|
| Catalog | A barcode | Key |
| Catalog | B name | Product name |
| Catalog | C price | Unit price |
| Catalog | D bin | Location |
| ScanEntry | A barcode | Scanner input |
| ScanEntry | B–D | Filled fields |
What this script does
Dictionary lookup from Catalog into ScanEntry, with optional onEdit.
Prerequisites
Catalog populated; ScanEntry ready; installable onEdit if desired.
- Barcodes as text (leading zeros)
- Unique barcodes
- Authorize installable trigger
Walkthrough
Paste a known barcode in A2; run barcodeLookupSheet or edit-trigger; see name/price/bin.
Edge cases
Leading zeros lost if cells are numeric — format column as plain text.
- NOT_FOUND marker
- Blank inputs skipped
How to test
Unknown code yields NOT_FOUND.
Hardening for production
Append scan timestamp; prevent overwrite of manually corrected names.
Variations
Call from sidebar camera web app instead of onEdit.
Full code: barcodeLookupSheet()
Format barcode columns as text, fill Catalog, then run barcodeLookupSheet or install onEditBarcode.
/**
* Look up a barcode scanned into a cell and write product fields beside it.
*/
const CATALOG = "Catalog"; // A barcode, B name, C price, D bin
const SCAN = "ScanEntry"; // A barcode input, B name, C price, D bin
function barcodeLookupSheet() {
const ss = SpreadsheetApp.getActive();
const cat = ss.getSheetByName(CATALOG).getDataRange().getValues();
const map = {};
for (let i = 1; i < cat.length; i++) {
map[String(cat[i][0])] = { name: cat[i][1], price: cat[i][2], bin: cat[i][3] };
}
const sheet = ss.getSheetByName(SCAN);
const values = sheet.getDataRange().getValues();
for (let i = 1; i < values.length; i++) {
const code = String(values[i][0] || "").trim();
if (!code) continue;
const hit = map[code];
if (hit) {
values[i][1] = hit.name;
values[i][2] = hit.price;
values[i][3] = hit.bin;
} else {
values[i][1] = "NOT_FOUND";
values[i][2] = "";
values[i][3] = "";
}
}
sheet.getRange(1, 1, values.length, 4).setValues(values.map(function (r) { return r.slice(0, 4); }));
}
function onEditBarcode(e) {
if (!e || !e.range) return;
if (e.range.getSheet().getName() !== SCAN) return;
if (e.range.getColumn() !== 1) return;
barcodeLookupSheet();
}- Line 12: Indexes catalog by barcode string.
- Line 26: Marks unknown scans.
- Line 34: Installable trigger entry point.
- Line 37: Ignores edits outside the barcode column.
- Line 5: Sheet name guard for the trigger.
Deploy this example
- 01
Open Apps Script
In the bound spreadsheet: Extensions → Apps Script. For standalone projects, create one at script.google.com and link your Sheet by ID.
- 02
Paste and save
Add a .gs file, paste the code below, rename constants at the top (sheet names, column letters, API property keys), then save.
- 03
Authorize once
Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.
- 04
Add the trigger
Triggers → Add trigger → choose the handler function and event (time-driven, on edit, or on form submit). Delete test triggers before production.
Before you run: barcode lookup
- 1Barcode columns formatted as text
- 2Catalog unique keys
- 3ScanEntry headers set
- 4Install onEditBarcode if using live scans
- 5Test leading-zero barcodes
- 6Train users to scan into column A
Frequently asked questions
Simple onEdit cannot call services needing auth. Installable is safer for growing tools.
Usually advances the cell; consider locking the active column or using a web app input.
Treat as strings; pad if your catalog stores fixed widths.
Rebuilds the whole map — fine for medium catalogs; cache in ScriptCache if huge.
Last catalog row wins — enforce uniqueness.
Ui alert is disruptive; prefer conditional formatting on NOT_FOUND.
Sheets needs connectivity; consider a local PWA for true offline.