Hướng dẫn

Apps Script Google Sheets: 10 Trigger Tự Động Hóa Thông Dụng Nhất [2026]

Tuân HoangTuân Hoang
9 tháng 6, 2026
11 phút đọc
Ảnh minh họa bài viết: Apps Script Google Sheets: 10 Trigger Tự Động Hóa Thông Dụng Nhất [2026]

Trigger Là Gì và Tại Sao Bạn Cần Biết?

Nếu bạn đã từng viết một hàm Apps Script rồi phải vào Extensions → Apps Script → chạy tay mỗi ngày, bạn hiểu nỗi đau đó. Trigger là giải pháp: một bộ kích hoạt tự động chạy function của bạn khi một sự kiện cụ thể xảy ra — mở file, sửa ô, gửi form, hay đến giờ định trước — mà không cần ai can thiệp.

Hiểu đúng và dùng đúng trigger, bạn có thể xây dựng những quy trình tự động hoàn toàn: gửi báo cáo sáng mỗi 8h, tự động ghi log khi ai đó sửa bảng tính, hay nhắn tin Telegram khi có đơn hàng mới — tất cả không cần mở máy tính.

Phân Loại Trigger Trong Apps Script

Trước khi đi vào từng trigger cụ thể, cần hiểu rõ hai nhóm chính để tránh nhầm lẫn khi triển khai:

Tiêu chí Simple Triggers Installable Triggers
Cách kích hoạt Tự động (đặt tên đúng function) Phải cài đặt thủ công qua UI hoặc code
Quyền truy cập Giới hạn (không gửi email, không gọi API ngoài) Đầy đủ (GmailApp, UrlFetchApp, DriveApp...)
Chạy với quyền ai User đang mở file Chủ tài khoản cài trigger
Hoạt động khi shared? Chỉ cho người có quyền edit Chạy độc lập dù chủ không online
Các loại có sẵn onOpen, onEdit, onChange, onFormSubmit time-driven, form submit, sheet change, calendar

Quy tắc chọn nhanh: Nếu bạn cần gửi email, gọi API bên ngoài, hay chạy kể cả khi bạn không online → dùng Installable Trigger. Nếu chỉ cần thao tác đơn giản trong sheet như hiển thị menu hay format ô → Simple Trigger là đủ.

10 Trigger Thực Tế Kèm Code Mẫu

1. onOpen — Thêm Menu Tùy Chỉnh Khi Mở File

Dùng khi nào: Bạn muốn tạo thanh menu riêng với các chức năng hay dùng, thay vì phải vào Extensions → Apps Script mỗi lần. Đây là cách tốt nhất để tạo "shortcut" cho người dùng không biết code.

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('⚙️ Công cụ')
    .addItem('Gửi báo cáo hôm nay', 'guiBaoCao')
    .addItem('Làm mới dữ liệu', 'lamMoiDuLieu')
    .addSeparator()
    .addItem('Hướng dẫn sử dụng', 'moHuongDan')
    .addToUi();
}

Giải thích: createMenu() tạo menu với tên hiển thị. addItem() nhận tên hiển thị và tên function sẽ gọi khi click. addSeparator() thêm đường kẻ ngăn cách. Menu xuất hiện ngay khi file mở, nằm trên thanh menu cạnh Help.

2. onEdit — Tự Động Xử Lý Khi Sửa Ô

Dùng khi nào: Cần phản ứng ngay lập tức khi một ô cụ thể thay đổi — ví dụ: cập nhật timestamp, validate dữ liệu, hay gửi thông báo khi trạng thái đơn hàng đổi.

function onEdit(e) {
  const sheet = e.source.getActiveSheet();
  if (sheet.getName() !== 'DonHang') return;
  if (e.range.getColumn() !== 5) return; // chỉ theo dõi cột E (Trạng thái)

  const trangThai = e.value;
  const madon = sheet.getRange(e.range.getRow(), 1).getValue();

  if (trangThai === 'Đã giao') {
    // Ghi ngày giao vào cột F
    sheet.getRange(e.range.getRow(), 6).setValue(new Date());
    // Gửi email thông báo (cần Installable Trigger để dùng GmailApp)
    GmailApp.sendEmail('manager@company.com',
      'Đơn ' + madon + ' đã giao',
      'Đơn hàng ' + madon + ' vừa được cập nhật: Đã giao thành công.'
    );
  }
}

Giải thích: Đối tượng e chứa thông tin sự kiện: e.range là ô vừa sửa, e.value là giá trị mới, e.source là spreadsheet. Hai dòng return đầu là "guard clause" — lọc chỉ xử lý đúng sheet và đúng cột, tránh trigger chạy vô ích cho mọi chỉnh sửa. Lưu ý: Simple trigger onEdit không thể dùng GmailApp — cần cài Installable Trigger.

3. Time-Driven Hàng Ngày — Gửi Báo Cáo Sáng Tự Động

Dùng khi nào: Muốn nhận bản tóm tắt đơn hàng, doanh thu, hay KPI mỗi sáng mà không cần mở bảng tính. Đây là trigger được dùng phổ biến nhất trong môi trường kinh doanh.

function baoCaoSang() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const homNay = new Date();

  // Lọc dữ liệu có ngày trùng hôm nay
  const donHomNay = sheet.getDataRange().getValues()
    .filter(row => {
      const ngay = new Date(row[0]);
      return ngay.toDateString() === homNay.toDateString();
    });

  const subject = 'Báo cáo ngày ' + homNay.toLocaleDateString('vi-VN') + ': ' + donHomNay.length + ' đơn hàng';
  const body = donHomNay.length > 0
    ? donHomNay.map(r => r[0] + ' | ' + r[1] + ' | ' + r[3]).join('
')
    : 'Không có đơn hàng nào hôm nay.';

  GmailApp.sendEmail(Session.getActiveUser().getEmail(), subject, body);
}
// Cài trigger: time-driven → Day timer → 8am–9am

Giải thích: getDataRange().getValues() lấy toàn bộ dữ liệu dưới dạng mảng 2 chiều. filter() lọc theo ngày hôm nay. toDateString() so sánh chỉ phần ngày, bỏ qua giờ. Sau khi code xong, vào Triggers → Add Trigger: chọn baoCaoSang, event source: Time-driven, type: Day timer, time: 8am–9am.

4. Time-Driven Hàng Tuần — Tổng Hợp Doanh Số

Dùng khi nào: Báo cáo tuần tự động cho manager vào sáng thứ Hai, giúp tiết kiệm 30–60 phút tổng hợp thủ công mỗi tuần.

function tongHopTuan() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('DoanSo');
  const data = sheet.getDataRange().getValues();

  // Tính ngày đầu tuần này (Chủ nhật = 0)
  const now = new Date();
  const startOfWeek = new Date(now);
  startOfWeek.setDate(now.getDate() - now.getDay());
  startOfWeek.setHours(0, 0, 0, 0);

  const tuanNay = data.slice(1).filter(r => new Date(r[0]) >= startOfWeek);
  const tongDoanhSo = tuanNay.reduce((sum, r) => sum + (Number(r[3]) || 0), 0);

  GmailApp.sendEmail(
    Session.getActiveUser().getEmail(),
    'Tổng kết tuần: ' + tongDoanhSo.toLocaleString('vi-VN') + ' VNĐ',
    'Tuần này bán được ' + tuanNay.length + ' đơn, tổng doanh thu: ' + tongDoanhSo.toLocaleString('vi-VN') + ' VNĐ'
  );
}
// Cài trigger: time-driven → Week timer → Thứ Hai, 8am–9am

Giải thích: slice(1) bỏ hàng tiêu đề. setHours(0,0,0,0) reset về đầu ngày để so sánh chính xác. reduce() cộng tổng cột thứ 4 (index 3). Number(r[3]) || 0 xử lý ô trống không làm NaN phá kết quả.

5. onFormSubmit — Phản Hồi Tức Thì Khi Có Form Mới

Dùng khi nào: Khi Google Form được gửi, tự động gửi email cảm ơn cho người điền và thông báo cho nhân viên sales — thay cho việc theo dõi thủ công từng submission.

function xuLyFormMoi(e) {
  const responses = e.namedValues;
  const hoTen = responses['Họ và tên'][0];
  const email = responses['Email'][0];
  const nhuCau = responses['Nhu cầu'][0];

  // Email cảm ơn tự động gửi cho người điền form
  GmailApp.sendEmail(
    email,
    'Cảm ơn ' + hoTen + ' đã liên hệ!',
    'Chúng tôi đã nhận được yêu cầu của bạn về: ' + nhuCau + '. Sẽ phản hồi trong 24h.'
  );

  // Thông báo nội bộ cho nhân viên sales
  GmailApp.sendEmail(
    'sales@company.com',
    '[Lead mới] ' + hoTen + ' — ' + nhuCau,
    'Email: ' + email + '
Nhu cầu: ' + nhuCau
  );
}
// Cài trigger: Spreadsheet → On form submit

Giải thích: e.namedValues là object với key là tên câu hỏi trong form, value là mảng (thường chỉ 1 phần tử). Đây là Installable Trigger — cài từ phía Spreadsheet (không phải Form). Lưu ý tên câu hỏi trong namedValues phải khớp chính xác với tên câu hỏi trong Google Form của bạn.

6. onChange — Phát Hiện Thay Đổi Cấu Trúc Sheet

Dùng khi nào: Cần audit log khi ai đó thêm/xóa hàng, thêm/xóa sheet, hay format ô — những thay đổi mà onEdit không bắt được.

function onChange(e) {
  const changeType = e.changeType;
  const time = new Date().toLocaleString('vi-VN');

  if (changeType === 'INSERT_ROW') {
    SpreadsheetApp.getActive().toast('Đã thêm hàng mới vào bảng', '✅ Thông báo', 3);
    Logger.log('[' + time + '] INSERT_ROW');
  }

  if (changeType === 'REMOVE_ROW') {
    Logger.log('[' + time + '] REMOVE_ROW — hàng đã bị xóa');
    // Tùy chọn: gửi cảnh báo cho admin
  }

  if (changeType === 'INSERT_COLUMN' || changeType === 'REMOVE_COLUMN') {
    Logger.log('[' + time + '] Cấu trúc cột bị thay đổi: ' + changeType);
  }
}
// Các changeType khác: FORMAT, OTHER, INSERT_GRID, REMOVE_GRID

Giải thích: e.changeType cho biết loại thay đổi. toast() hiển thị thông báo góc dưới phải trong 3 giây. Logger.log() ghi vào Execution Log — xem tại View → Logs trong Apps Script editor. onChange chỉ có phiên bản Installable (không có Simple Trigger).

7. Time-Driven Ngày 1 Hàng Tháng — Tự Động Backup Sheet

Dùng khi nào: Tự động lưu lại bản sao dữ liệu tháng trước mỗi đầu tháng, đảm bảo không mất dữ liệu khi có ai đó xóa nhầm hoặc ghi đè.

function dongBaoCaoThang() {
  const today = new Date();
  // Guard: chỉ chạy vào ngày 1 (trigger hàng ngày nhưng tự lọc)
  if (today.getDate() !== 1) return;

  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const thangTruoc = today.getMonth(); // getMonth() trả 0-11, tháng trước = tháng hiện tại (trước khi tăng)
  const namHienTai = today.getFullYear();

  // Copy sheet dữ liệu → tạo backup với tên có timestamp
  const sheetNguon = ss.getSheetByName('DuLieu');
  if (!sheetNguon) {
    Logger.log('Không tìm thấy sheet DuLieu');
    return;
  }

  const tenBackup = 'Backup_T' + thangTruoc + '_' + namHienTai;

  // Tránh tạo trùng nếu trigger chạy nhiều lần
  if (ss.getSheetByName(tenBackup)) {
    Logger.log('Backup đã tồn tại: ' + tenBackup);
    return;
  }

  sheetNguon.copyTo(ss).setName(tenBackup);

  GmailApp.sendEmail(
    Session.getActiveUser().getEmail(),
    'Đã tạo backup tháng ' + thangTruoc + '/' + namHienTai,
    'Sheet ' + tenBackup + ' đã được tạo thành công lúc ' + today.toLocaleString('vi-VN')
  );
}
// Cài trigger: time-driven → Day timer → 0am–1am (chạy khuya ngày 1)

Giải thích: Dùng Day timer thay vì Month timer để kiểm soát tốt hơn. Guard clause getDate() !== 1 đảm bảo chỉ thực sự chạy ngày 1. Kiểm tra getSheetByName(tenBackup) trước khi tạo để tránh lỗi trùng tên nếu trigger bị chạy nhiều lần.

8. Nhắc Deadline Tự Động — Cảnh Báo Task Sắp Hết Hạn

Dùng khi nào: Quản lý danh sách task/dự án trong Sheets và muốn nhận nhắc nhở trước 1 ngày khi deadline sắp đến, thay vì phải nhớ tự check.

function nhacDeadline() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const data = sheet.getDataRange().getValues();

  const ngayMai = new Date();
  ngayMai.setDate(ngayMai.getDate() + 1);
  ngayMai.setHours(0, 0, 0, 0);

  // Cấu trúc sheet: [0]Tên task, [1]Mô tả, [2]Deadline, [3]Assignee, [4]Đã xong (TRUE/FALSE)
  const sapHan = data.slice(1).filter(row => {
    if (row[4] === true || row[4] === 'TRUE') return false; // bỏ task đã xong
    const deadline = new Date(row[2]);
    deadline.setHours(0, 0, 0, 0);
    return deadline.getTime() === ngayMai.getTime();
  });

  if (sapHan.length === 0) return; // không cần gửi email nếu không có task nào

  const body = sapHan.map(r => '• ' + r[0] + ' (' + r[3] + '): ' + r[1] + ' — hạn ngày mai').join('
');

  GmailApp.sendEmail(
    Session.getActiveUser().getEmail(),
    '🔔 ' + sapHan.length + ' task deadline ngày mai',
    body
  );
}
// Cài trigger: time-driven → Day timer → 7am–8am

Giải thích: setHours(0,0,0,0) chuẩn hóa cả hai ngày về 0h trước khi so sánh — bắt buộc để tránh sai lệch do giờ lưu trong ô. getTime() trả về milliseconds, so sánh chính xác hơn toDateString(). Guard if (sapHan.length === 0) return tránh gửi email rỗng mỗi ngày.

9. onEdit Nâng Cao — Tự Động Sort Bảng Sau Khi Nhập Liệu

Dùng khi nào: Bảng danh sách sản phẩm, khách hàng, hay nhân viên cần luôn được sắp xếp theo thứ tự chữ cái hoặc số — mà không cần nhớ sort thủ công sau mỗi lần thêm dòng mới.

function onEdit(e) {
  const sheet = e.source.getActiveSheet();
  if (sheet.getName() !== 'SanPham') return;

  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return; // không có dữ liệu để sort

  // Sort theo cột A (Tên SP) từ A đến Z sau mỗi lần edit
  const range = sheet.getRange('A2:E' + lastRow);
  range.sort({ column: 1, ascending: true });

  // Tùy chọn: highlight hàng vừa edit
  // e.range.getRow() cho số hàng vừa sửa
}

Giải thích: getLastRow() lấy số hàng cuối có dữ liệu — giúp range luôn đúng khi thêm hàng mới. sort({ column: 1, ascending: true }) sort theo cột đầu tiên của range (cột A). Lưu ý: sort ngay sau edit có thể làm cursor nhảy vị trí — cân nhắc UX trước khi áp dụng cho sheet nhiều người dùng.

10. Gửi Thông Báo Webhook (Telegram/Zalo) Khi Có Đơn Hàng Mới

Dùng khi nào: Muốn nhận thông báo tức thì trên điện thoại qua Telegram bot hay Zalo OA mỗi khi có đơn hàng mới được nhập vào sheet — thay vì phải mở máy tính check.

function guiThongBaoDonMoi(e) {
  const sheet = e.source.getActiveSheet();
  if (sheet.getName() !== 'DonHang') return;

  const row = e.range.getRow();
  if (row < 2) return; // bỏ qua hàng tiêu đề
  if (e.range.getColumn() !== 1) return; // chỉ trigger khi nhập cột A (mã đơn)

  const maDon = e.value;
  if (!maDon) return; // bỏ qua khi xóa ô

  const khachHang = sheet.getRange(row, 2).getValue();
  const sanPham = sheet.getRange(row, 3).getValue();
  const tongTien = sheet.getRange(row, 5).getValue();

  // Đọc webhook URL từ Script Properties (không hardcode vào code)
  const webhookUrl = PropertiesService.getScriptProperties().getProperty('WEBHOOK_URL');
  if (!webhookUrl) {
    Logger.log('Chưa cài WEBHOOK_URL trong Script Properties');
    return;
  }

  const message = '🛒 Đơn mới #' + maDon + '
KH: ' + khachHang + '
SP: ' + sanPham + '
Giá: ' + tongTien.toLocaleString('vi-VN') + ' VNĐ';

  UrlFetchApp.fetch(webhookUrl, {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify({ message: message })
  });
}
// Cài Script Properties: File → Project properties → Script properties → WEBHOOK_URL = [URL của bạn]

Giải thích: PropertiesService.getScriptProperties() lưu thông tin nhạy cảm (URL, token) bên ngoài code — an toàn hơn hardcode. UrlFetchApp.fetch() gọi HTTP POST đến webhook. Đây là Installable Trigger vì dùng UrlFetchApp. Để test, dùng webhook.site để lấy URL test miễn phí.

Cách Cài Đặt Installable Trigger

Với Simple Triggers (onOpen, onEdit), chỉ cần đặt tên function đúng là xong. Với Installable Triggers, cần thêm bước cài đặt:

Cách 1: Cài Qua Giao Diện (Khuyến Nghị)

  1. Mở file Google Sheets → Extensions → Apps Script
  2. Ở thanh bên trái, click biểu tượng đồng hồ (Triggers)
  3. Click + Add Trigger (góc dưới phải)
  4. Điền thông tin:
    • Choose which function to run: Chọn tên function của bạn
    • Select event source: Time-driven / From spreadsheet / From calendar
    • Select type of time: Chọn tần suất (ngày, tuần, phút...)
    • Select time of day: Khung giờ chạy
  5. Click Save → Google yêu cầu cấp quyền → Cho phép

Cách 2: Cài Bằng Code (Automation)

function caiDatTrigger() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  // Trigger chạy hàng ngày lúc 8h sáng
  ScriptApp.newTrigger('baoCaoSang')
    .timeBased()
    .everyDays(1)
    .atHour(8)
    .create();

  // Trigger khi có form submit
  ScriptApp.newTrigger('xuLyFormMoi')
    .forSpreadsheet(ss)
    .onFormSubmit()
    .create();

  console.log('Đã cài đặt triggers thành công!');
}

function xoaTatCaTrigger() {
  ScriptApp.getProjectTriggers().forEach(t => ScriptApp.deleteTrigger(t));
  console.log('Đã xóa tất cả triggers.');
}

Chạy caiDatTrigger() một lần để tạo trigger. Dùng xoaTatCaTrigger() khi cần dọn dẹp tránh trigger bị trùng lặp.

Giới Hạn Quota Cần Biết

Dùng trigger quá nhiều hoặc sai cách có thể vượt quota của Google. Đây là các giới hạn quan trọng nhất:

Giới hạn Tài khoản miễn phí Google Workspace
Thời gian chạy tối đa/lần 6 phút 6 phút
Tổng thời gian chạy/ngày 90 phút 6 giờ
Số trigger có thể cài 20 triggers/project 20 triggers/project
Email gửi qua GmailApp/ngày 100 email 1.500 email
UrlFetchApp gọi/ngày 20.000 lần 100.000 lần

Lời khuyên: Đừng cài trigger chạy mỗi phút nếu script mất 10–20 giây mỗi lần — bạn sẽ hết quota rất nhanh. Luôn dùng guard clauses để thoát sớm khi không cần thiết.

Mẹo Debug Trigger Hiệu Quả

  • Xem Execution Log: Vào Apps Script → Executions (biểu tượng play list). Mỗi lần trigger chạy sẽ có log ở đây, kể cả lỗi. Đây là công cụ debug chính.
  • Thêm Logger.log() vào code: Logger.log('Giá trị: ' + value) giúp bạn trace vấn đề. Log xuất hiện trong Executions sau khi trigger chạy.
  • Test thủ công trước: Trước khi cài trigger, hãy chạy function thủ công trong editor (nút ▶) và kiểm tra kết quả. Trigger chỉ nên cài sau khi function chạy đúng.
  • Dùng try/catch: Bọc code quan trọng trong try/catch và gửi email khi có lỗi — giúp bạn biết ngay khi trigger chạy thất bại.
  • Kiểm tra trùng trigger: Nếu vô tình cài trigger nhiều lần, function sẽ chạy nhiều lần. Vào Triggers → kiểm tra danh sách và xóa trigger thừa.
// Pattern bọc lỗi chuẩn cho trigger quan trọng
function baoCaoSangAnToan() {
  try {
    baoCaoSang(); // gọi hàm thật
  } catch (error) {
    GmailApp.sendEmail(
      Session.getActiveUser().getEmail(),
      '[LỖI] Trigger baoCaoSang thất bại',
      'Lỗi: ' + error.message + '
Stack: ' + error.stack
    );
  }
}

Kết Luận

Trigger Apps Script là nền tảng của mọi hệ thống tự động hóa trên Google Sheets. 10 trigger trong bài này bao phủ đầy đủ các tình huống thực tế: từ phản ứng tức thì khi sửa ô (onEdit), xử lý form submission, đến báo cáo lên lịch và gửi thông báo đa kênh.

Bắt đầu với những trigger đơn giản nhất: cài onOpen tạo menu tùy chỉnh, hay một time-driven gửi báo cáo sáng. Khi đã quen với vòng lặp cài đặt → kiểm tra log → debug, bạn sẽ dần tự tin xây dựng những automation phức tạp hơn.

Quy tắc vàng: Luôn test function thủ công trước khi cài trigger. Trigger chạy ngầm — nếu code sai, bạn sẽ không biết cho đến khi check Execution Log. Giữ code đơn giản, có guard clause rõ ràng, và bọc lỗi bằng try/catch là thói quen tốt với mọi trigger quan trọng.

Nguồn tham khảo: Google Apps Script — Triggers | ScriptApp Reference

Chia sẻ bài viết:

Tuân Hoang

Tuân Hoang

Đội ngũ SheetStore

Google SheetsGoogle Apps ScriptCRMAutomationPhần mềm quản lý doanh nghiệp

Google Workspace Certified, 5+ years experience

Bạn thấy bài viết hữu ích?

Đăng ký nhận thông báo khi có bài viết mới.

Nhận thông báo khi có bài viết mới. Không spam, hứa luôn! 😊

Bình luận (0)

Vui lòng đăng nhập để tham gia thảo luận