How to Make an Equipment Sign-Out Sheet in Google Sheets (with Due-Back and Overdue Email Reminders, 2026)
Laptops, cameras, tripods, lab kits, tools, library-style lending closets: lots of schools, makerspaces, AV teams and small offices lend equipment out and track it in a spreadsheet. The hard part isn't writing down who took what. It's getting things back. This guide sets up a sign-out sheet that shows what's out and what's late at a glance, and a free Apps Script that emails each borrower the day before it's due, on the day, and a few times after it's late, with one email per person even if they borrowed several items.
This is a different job from tracking equipment maintenance: here the date that matters is when an item is due back from a person, and reminders stop as soon as it's returned.
Step 1: a Loans tab with one row per checkout
Create a tab named Loans with these headers in row 1. The script finds columns by name, so the order is up to you, but the formulas below assume A to H:
| Col | Header | What goes in it |
|---|---|---|
| A | Item | e.g. “Camera 2”, “Chromebook 14”, “Drill kit”. Use the same name or asset tag every time. |
| B | Borrower | Name, used to greet them in the email |
| C | Borrower email | Where reminders go |
| D | Checked out | Date it left |
| E | Due back | Date it should come back, formatted as a date |
| F | Returned | Leave blank while it's out. Type the return date (Ctrl+; inserts today) when it comes back. Anything in this cell stops reminders. |
| G | Notes | Optional, e.g. “with charger and bag”. It's included in the email. |
| H | Sent log | Leave empty; the script records what it sent |
Each checkout is a new row, and rows are never reused. That gives you a full lending history (who had Camera 2 when the lens got scratched?) instead of one cell that gets overwritten.
Faster checkouts with a form. A Google Form linked to the sheet lets borrowers or staff sign items out from a phone. Make Item a dropdown so names always match. Form responses land on their own tab, so either use that tab as Loans (rename its header cells to match the ones above; Google Forms adds a Timestamp column, which is fine) or keep Loans as the sheet staff edit by hand. If you set a standard loan period, Due back can be a formula, e.g. =IF(D2="","",D2+7) for one week. Type over it for longer loans.
Step 2: see what's out and what's late
Add a Status column (I) and fill down:
=IF(A2="","",IF(F2<>"","Returned",IF(E2="","Out",IF(E2<TODAY(),"Overdue "&(TODAY()-E2)&" days",IF(E2=TODAY(),"Due today","Out")))))
Then add conditional formatting on A2:I with custom formulas =AND($F2="",$E2<>"",$E2<TODAY()) (red, overdue) and =AND($F2="",$E2=TODAY()) (amber, due today). On another tab, everything still out, oldest due date first:
=QUERY(Loans!A:G,"select A,B,C,E where A is not null and F is null order by E",1)
Availability by item. Keep a tab named Items with one item name per row in column A. In B2, fill down to show whether each item is in or who has it:
=IF(A2="","",IF(COUNTIFS(Loans!$A:$A,A2,Loans!$F:$F,"")=0,"Available","Out: "&TEXTJOIN(", ",TRUE,FILTER(Loans!$B:$B,Loans!$A:$A=A2,Loans!$F:$F=""))))
If an item shows two borrowers at once, someone forgot to mark a return, which is worth knowing too.
Step 3: email borrowers before and after the due date
Google Sheets doesn't send email when a date arrives; its notification rules react to edits, not to the calendar. A short Apps Script with a daily trigger does it. Open Extensions › Apps Script, paste this, save, run sendLoanReminders once to approve permissions, then add a trigger: Triggers › Add trigger › sendLoanReminders › Time-driven › Day timer (morning works best).
/**
* Due-back and overdue reminders for an equipment sign-out sheet.
* "Loans" tab headers used (any order): Item, Borrower, Borrower email,
* Due back, Returned, Sent log. Optional: Checked out, Notes.
* A row with a date (or anything) in Returned gets no more emails.
*/
const SHEET_NAME = 'Loans';
const BEFORE_DAYS = [1, 0]; // days before the due date (0 = due today)
const OVERDUE_DAYS = [1, 3, 7]; // days after the due date
const MANAGER_EMAIL = ''; // e.g. 'equipment@yourschool.org'; CC'd from the last overdue reminder on
const MAX_EMAILS = 50; // per run; the rest go out tomorrow
function sendLoanReminders() {
const sh = SpreadsheetApp.getActive().getSheetByName(SHEET_NAME);
const rows = sh.getDataRange().getValues();
const head = rows[0].map((h) => String(h).trim());
const col = (name) => head.indexOf(name);
const c = {
item: col('Item'), who: col('Borrower'), email: col('Borrower email'),
out: col('Checked out'), due: col('Due back'), back: col('Returned'),
notes: col('Notes'), log: col('Sent log'),
};
for (const k of ['item', 'email', 'due', 'back', 'log']) {
if (c[k] === -1) throw new Error('Missing column for ' + k + '. Check the headers in row 1.');
}
const tz = Session.getScriptTimeZone();
const today = new Date(); today.setHours(0, 0, 0, 0);
const fmt = (d) => Utilities.formatDate(d, tz, 'EEE MMM d');
const lastOverdue = Math.max(...OVERDUE_DAYS);
// Group what is due per borrower so each person gets one email per run.
const byEmail = {};
for (let r = 1; r < rows.length; r++) {
const row = rows[r];
const email = String(row[c.email]).trim();
const due = row[c.due];
if (!email || !(due instanceof Date) || String(row[c.back]).trim() !== '') continue;
const d = new Date(due); d.setHours(0, 0, 0, 0);
const left = Math.round((d - today) / 86400000);
const key = Utilities.formatDate(due, tz, 'yyyy-MM-dd');
const sent = String(row[c.log] || '').split(';').filter(String);
// The most urgent step reached so far that hasn't been sent for this due date.
let step = null;
if (left >= 0) {
const b = BEFORE_DAYS.filter((n) => left <= n).sort((x, y) => x - y)[0];
if (b !== undefined) step = 'b' + b;
} else {
const late = -left;
const o = OVERDUE_DAYS.filter((n) => late >= n).sort((x, y) => y - x)[0];
if (o !== undefined) step = 'o' + o;
}
if (!step || sent.includes(key + '@' + step)) continue;
(byEmail[email] = byEmail[email] || []).push({
r, row, left, step, sent, key,
final: step === 'o' + lastOverdue,
});
}
let emails = 0;
for (const email of Object.keys(byEmail)) {
if (emails >= MAX_EMAILS || MailApp.getRemainingDailyQuota() < 1) break;
const loans = byEmail[email];
const name = String(loans[0].row[c.who] || '').trim();
const late = loans.some((l) => l.left < 0);
const lines = loans.map((l) => {
const when = l.left < 0 ? `was due ${fmt(l.row[c.due])} (${-l.left} day${l.left === -1 ? '' : 's'} overdue)`
: l.left === 0 ? 'is due back today' : `is due back ${fmt(l.row[c.due])}`;
const note = c.notes !== -1 && l.row[c.notes] ? ` (${l.row[c.notes]})` : '';
return `- ${l.row[c.item]}${note} ${when}`;
});
const opts = {};
if (MANAGER_EMAIL && loans.some((l) => l.final)) opts.cc = MANAGER_EMAIL;
MailApp.sendEmail(email,
late ? 'Overdue: please return borrowed equipment' : 'Reminder: equipment due back soon',
`Hi${name ? ' ' + name : ''},\n\n` + lines.join('\n') +
`\n\nIf you've already returned it, reply and we'll fix the sheet. Thanks!`, opts);
emails++;
for (const l of loans) {
l.sent.push(l.key + '@' + l.step);
sh.getRange(l.r + 1, c.log + 1).setValue(l.sent.join(';'));
}
}
return emails;
}
How it behaves:
- One email per person per run. If Ana has a camera due tomorrow and a tripod due today, she gets one email listing both, not two.
- A short sequence, not daily nagging. With the defaults, a borrower hears 1 day before, on the due day, and 1, 3 and 7 days after. If the script starts late (say, the item is already 8 days overdue), it sends one email for the latest step instead of a pile of catch-up emails.
- Returns stop everything. As soon as Returned has a value, that row is skipped.
- Extensions just work. Sent log entries are keyed to the due date, so changing Due back to a later date starts a fresh set of reminders without clearing any cells.
- Escalation is optional. Set
MANAGER_EMAILto CC a teacher, librarian or office manager on the last overdue reminder. - Limits. At most
MAX_EMAILSemails per run, and it stops if your daily Apps Script email quota runs out. Anything left goes out the next day, and the Sent log is only written for emails that were actually sent.
Tips
- Test on yourself first. Put your own address in a row with Due back set to tomorrow, run the function from the editor, and check the email and the Sent log.
- Students and minors. Many schools restrict or filter email to student accounts. Check your district's rules, and consider emailing a teacher or parent contact instead.
- Ownership. The trigger runs as whoever created it, and the emails come from that account. Set it up from a shared department account that won't leave when one person does.
- Privacy. The sheet holds names and emails. Share it only with the staff who run the lending desk, and give borrowers a form, not edit access.
- Need a signed checkout form or receipt for each loan? See a PDF or Google Doc for each row.
See SheetReminders
Short on time? Watch the 50-second version
Related Google Sheets guides
- How to generate a PDF or Google Doc for each row in Google Sheets
- Best Autocrat alternatives for mail merge to PDF and Docs
- Make invoices or certificates from Google Sheets rows
- Create sales quotes or estimates from Google Sheets
- Generate receipts from Google Sheets rows
- Create purchase orders from Google Sheets
- Make packing slips from Google Sheets orders
- Generate a PDF certificate from a Google Form response
- Issue numbered training completion certificates with expiry dates
- Make a PDF progress report for each student
- Generate offer letters from a Google Sheets hiring tracker
- Generate contracts and agreements from a Google Docs template
- Email each row’s PDF as an attachment
- Automatic email reminders from a date column
- Contract renewal and expiration alerts from Google Sheets
- Track certificate of insurance (COI) expirations in Google Sheets
- Track employee license and certification expirations
- Track equipment and vehicle maintenance with reminders
- Equipment sign-out sheet with due-back and overdue reminders
- Track warranty expiration dates with reminders
- Birthday and work anniversary reminders from Google Sheets
- Rent due and lease renewal reminders from Google Sheets
- Send invoice payment reminders from Google Sheets
- Highlight overdue tasks and send email reminders
- Follow-up reminders for leads, clients and candidates
- Membership renewal reminders for clubs and associations
- SheetWeave vs Autocrat
- SheetWeave vs Document Studio
- SheetWeave vs RowDocs
Instructions refer to Google Sheets, Google Forms and Apps Script as they worked on November 13, 2026. Not affiliated with Google or any other vendor named here. Google Sheets, Google Docs, Google Forms and Google Drive are trademarks of Google LLC. Features change, so verify in your own account. Questions or corrections: hello@loomwise.app.