Attendance Management System
Go to sheets.google.com → create a new sheet → name it Attendance.
In row 1, add these headers exactly:
Timestamp Name Phone IC First 6 Digit Class Type Session Date Time Trainer Venue
⚠️ Registration Sheet (for verification)
Create a second sheet named Registration with these headers:
Name Phone IC First 6 Digit Paid
Fill this sheet with your participants' registration data. The Paid column must be Y or N. Only participants with Paid = Y can check in.
📋 Rejected Sheet (auto-created)
A third sheet named Rejected will be created automatically the first time someone is rejected. No setup needed — headers and data are written by the script.
In your Google Sheet, click Extensions → Apps Script. Delete any existing code, then paste this:
const SHEET_NAME = "Attendance";
const REG_SHEET_NAME = "Registration"; // sheet with registration data for verification
const REJECTED_SHEET_NAME = "Rejected"; // sheet to log rejected check-ins
function doPost(e) {
try {
const data = JSON.parse(e.postData.contents);
// ── VERIFICATION ──
if (data.action === "verify") {
return verifyParticipant(data);
}
// ── LOG REJECTED ──
if (data.action === "logRejected") {
return logRejectedEntry(data);
}
// ── WRITE ATTENDANCE ──
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
sheet.appendRow([
new Date().toLocaleString("en-MY"),
data.name, data.phone, data.ic4, data.classType,
data.session, data.date, data.time, data.trainer, data.venue
]);
return ContentService
.createTextOutput(JSON.stringify({ result: "success" }))
.setMimeType(ContentService.MimeType.JSON);
} catch(err) {
return ContentService
.createTextOutput(JSON.stringify({ result: "error", error: err.toString() }))
.setMimeType(ContentService.MimeType.JSON);
}
}
function logRejectedEntry(data) {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(REJECTED_SHEET_NAME);
// Auto-create the sheet + headers if it doesn't exist
if (!sheet) {
sheet = ss.insertSheet(REJECTED_SHEET_NAME);
sheet.appendRow(["Timestamp", "Name", "Phone", "IC First 6 Digit", "Class Type", "Session", "Date", "Time", "Reason"]);
sheet.getRange(1, 1, 1, 9).setFontWeight("bold");
}
sheet.appendRow([
new Date().toLocaleString("en-MY"),
data.name || "", data.phone || "", data.ic4 || "", data.classType || "",
data.session || "", data.date || "", data.time || "", data.reason || ""
]);
return ContentService
.createTextOutput(JSON.stringify({ result: "success" }))
.setMimeType(ContentService.MimeType.JSON);
} catch(err) {
return ContentService
.createTextOutput(JSON.stringify({ result: "error", error: err.toString() }))
.setMimeType(ContentService.MimeType.JSON);
}
}
function verifyParticipant(data) {
try {
const regSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(REG_SHEET_NAME);
if (!regSheet) {
// No registration sheet = allow all (open mode)
return ContentService
.createTextOutput(JSON.stringify({ result: "success", verified: true, mode: "open" }))
.setMimeType(ContentService.MimeType.JSON);
}
const rows = regSheet.getDataRange().getValues();
const headers = rows[0].map(h => String(h).trim().toLowerCase());
const nameIdx = headers.indexOf("name");
const phoneIdx = headers.indexOf("phone");
const ic4Idx = headers.indexOf("ic first 6 digit");
const paidIdx = headers.indexOf("paid");
const ic4Input = String(data.ic4 || "").trim();
const phoneInput = String(data.phone || "").trim().replace(/\D/g, "").slice(-4);
const nameInput = String(data.name || "").trim().toLowerCase();
let matched = false;
for (let i = 1; i < rows.length; i++) {
const row = rows[i];
const regPhone = String(row[phoneIdx] || "").replace(/\D/g, "").slice(-4);
const regIc4 = String(row[ic4Idx] || "").trim();
const regName = String(row[nameIdx] || "").trim().toLowerCase();
const regPaid = paidIdx >= 0 ? String(row[paidIdx] || "").trim().toUpperCase() : "Y";
// Reject immediately if not paid
if (regPaid !== "Y") continue;
// Priority check: IC First 6 + Phone last 4 + Paid Y
if (ic4Input && regIc4 && ic4Input === regIc4 && phoneInput === regPhone) {
matched = true; break;
}
// Fallback: Name + IC First 6 + Paid Y
if (ic4Input && regIc4 && ic4Input === regIc4 && nameInput === regName) {
matched = true; break;
}
}
return ContentService
.createTextOutput(JSON.stringify({ result: "success", verified: matched }))
.setMimeType(ContentService.MimeType.JSON);
} catch(err) {
return ContentService
.createTextOutput(JSON.stringify({ result: "error", error: err.toString() }))
.setMimeType(ContentService.MimeType.JSON);
}
}
function doGet(e) {
try {
// ── Verify via GET (called from client for readable response) ──
if (e && e.parameter && e.parameter.action === "verify") {
return verifyParticipant({
name: e.parameter.name || "",
phone: e.parameter.phone || "",
ic4: e.parameter.ic4 || ""
});
}
// ── Check duplicate check-in ──
if (e && e.parameter && e.parameter.action === "checkDuplicate") {
const nameInput = String(e.parameter.name || "").trim().toLowerCase();
const phoneInput = String(e.parameter.phone || "").trim().replace(/\D/g, "");
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
const rows = sheet.getDataRange().getValues();
const headers = rows[0].map(h => String(h).trim());
const nameIdx = headers.indexOf("Name");
const phoneIdx = headers.indexOf("Phone");
let duplicate = false;
for (let i = 1; i < rows.length; i++) {
const rName = String(rows[i][nameIdx] || "").trim().toLowerCase();
const rPhone = String(rows[i][phoneIdx] || "").trim().replace(/\D/g, "");
if (rName === nameInput || rPhone === phoneInput) {
duplicate = true; break;
}
}
return ContentService
.createTextOutput(JSON.stringify({ result: "success", duplicate: duplicate }))
.setMimeType(ContentService.MimeType.JSON);
}
// ── Load attendance records ──
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
const rows = sheet.getDataRange().getValues();
const headers = rows[0];
const data = rows.slice(1).map(row => {
const obj = {};
headers.forEach((h, i) => obj[h] = row[i]);
return obj;
});
return ContentService
.createTextOutput(JSON.stringify({ result: "success", data: data }))
.setMimeType(ContentService.MimeType.JSON);
} catch(err) {
return ContentService
.createTextOutput(JSON.stringify({ result: "error", error: err.toString() }))
.setMimeType(ContentService.MimeType.JSON);
}
}
Click Deploy → New deployment
• Type: Web app
• Execute as: Me
• Who has access: Anyone
Click Deploy → copy the URL it gives you.
Paste the deployment URL into the field at the top of this page and click Save. You're done — data will now go directly to your Google Sheet.
Each session can send data to a different Google Sheet. Paste a unique Apps Script URL here, or leave blank to use the default.
Please fill in your details to record your attendance.
Your information is only used for attendance purposes.