Filter
Records
Equipment & Asset IDs
Tap equipment to expand. Select IDs from the list or type custom ones.
๐ก Google Sheets sync โ free, no backend needed. Takes ~10 minutes to set up.
Step 1 โ Create the Sheet
1
New Google Sheet
Go to sheets.google.com, create a sheet named "STEM Lab Tracker 2026".
2
Rename tab to "Records"
Right-click the tab โ Rename โ type: Records
3
Paste headers in Row 1
id instructor location checkoutDate equipment missingItems benchedItems status notes checkedOutAt returnedAt
Step 2 โ Apps Script
1
Open Apps Script
Extensions โ Apps Script. Delete existing code.
2
Paste this script
// STEM Lab Equipment Tracker โ Apps Script
// Paste this entire block into Extensions > Apps Script, then Deploy > New deployment > Web App
// Execute as: Me | Access: Anyone
const SHEET_NAME = 'Records';
function doGet(e) {
try {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
const data = sheet.getDataRange().getValues();
if (data.length < 2) return respond({ records: [] });
const headers = data[0].map(h => String(h).trim());
const records = data.slice(1).map(function(row) {
var obj = {};
headers.forEach(function(h, i) { obj[h] = row[i]; });
// Parse JSON fields โ send as raw strings, let client parse
// so we don't lose data if parsing fails here
return obj;
});
return respond({ records: records });
} catch(err) {
return respond({ error: err.toString(), records: [] });
}
}
function doPost(e) {
try {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
const d = JSON.parse(e.postData.contents);
if (d.action === 'checkout') {
var customNote = '';
if (d.customEquipment && d.customEquipment.length > 0) {
customNote = ' [CUSTOM:' + JSON.stringify(d.customEquipment) + ']';
}
sheet.appendRow([
String(d.id),
String(d.instructor || ''),
String(d.location || ''),
String(d.checkoutDate || ''),
JSON.stringify(d.equipment || {}),
JSON.stringify([]),
JSON.stringify([]),
'out',
String(d.notes || '') + customNote,
String(d.checkedOutAt || new Date().toISOString()),
''
]);
return respond({ ok: true, action: 'checkout' });
}
if (d.action === 'return') {
var rows = sheet.getDataRange().getValues();
for (var i = 1; i < rows.length; i++) {
if (String(rows[i][0]) === String(d.id)) {
sheet.getRange(i + 1, 6).setValue(JSON.stringify(d.missingItems || []));
sheet.getRange(i + 1, 7).setValue(JSON.stringify(d.benchedItems || []));
sheet.getRange(i + 1, 8).setValue(String(d.status || 'returned'));
sheet.getRange(i + 1, 9).setValue(String(d.returnNotes || rows[i][8] || ''));
sheet.getRange(i + 1, 11).setValue(String(d.returnedAt || new Date().toISOString()));
return respond({ ok: true, action: 'return' });
}
}
return respond({ ok: false, error: 'ID not found' });
}
return respond({ ok: false, error: 'Unknown action' });
} catch(err) {
return respond({ ok: false, error: err.toString() });
}
}
function respond(obj) {
return ContentService
.createTextOutput(JSON.stringify(obj))
.setMimeType(ContentService.MimeType.JSON);
}
3
Deploy as Web App
Deploy โ New Deployment โ Web App โ Execute as: Me โ Access: Anyone โ Deploy. Copy the URL.
Deploy to Netlify
1
Free account at netlify.com
Sign up free โ no credit card.
2
Sites โ Add new โ Deploy manually
Drag this HTML file in โ Rename to index.html โ Deploy. Done.
3
Add to phone home screen
iPhone: Safari โ Share โ Add to Home Screen. Android: Chrome โ menu โ Add to Home Screen.