Простые триггеры: onOpen и onEdit
Как научить таблицу показывать своё меню при открытии и сама отмечать время правки — без единой кнопки «Запустить».
Простой триггер (simple trigger) — функция с зарезервированным именем (
onOpen,onEdit,onSelectionChange), которую Google Таблицы вызывают сами, как только произошло соответствующее событие. Регистрировать её нигде не нужно: достаточно правильно назвать.
Зачем это нужно
Пока вы работаете со скриптом в одиночку, запускать его из редактора терпимо. Но как только таблицей пользуется отдел, картина меняется: бухгалтер не должен открывать «Расширения → Apps Script» и искать нужную функцию в списке. Ему нужен пункт меню с понятным названием — или чтобы вообще ничего не нажимать, и всё случилось само.
Ровно это и дают простые триггеры. Два из них закрывают 90% задач: onOpen — «человек открыл файл», onEdit — «человек изменил ячейку». Вы пишете функцию с таким именем, сохраняете проект, и Google начинает вызывать её за вас.
onOpen: своё меню в таблице
Функция onOpen выполняется каждый раз, когда пользователь открывает документ. Классическое применение — собрать собственное меню рядом со стандартными «Файл», «Правка», «Вид».
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Отчёты')
.addItem('Обновить сводку', 'obnovitSvodku')
.addItem('Очистить черновики', 'ochistitChernoviki')
.addSeparator()
.addItem('О скрипте', 'pokazatSpravku')
.addToUi();
}
function obnovitSvodku() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
// ... здесь пересчёт данных ...
ss.toast('Сводка пересчитана', 'Готово', 3);
}
function pokazatSpravku() {
SpreadsheetApp.getUi().alert('Меню обновляет сводку по данным листа «Заявки».');
}
Разберём построчно:
SpreadsheetApp.getUi()— доступ к интерфейсу таблицы: меню, диалоги, боковые панели.createMenu('Отчёты')— создаёт меню; текст в скобках увидят пользователи.addItem('Обновить сводку', 'obnovitSvodku')— первый аргумент это подпись, второй — имя функции строкой. Частая ошибка новичка: написатьaddItem('Обновить сводку', obnovitSvodku)без кавычек. Кавычки обязательны.addSeparator()— разделительная черта, чисто косметика.addToUi()— без этой строки меню не появится: до неё вы просто описывали конструкцию в памяти.
Сохраните проект и перезагрузите страницу таблицы — меню появляется только при открытии документа, а не по волшебству в уже открытой вкладке.
onEdit: реакция на правку
Функция onEdit вызывается после того, как пользователь изменил ячейку. В неё Google передаёт объект события — обычно его называют e. Это «протокол происшествия»: что именно поменяли, где и на что.
| Поле | Что в нём |
e.range | диапазон, который правили (объект Range) |
e.value | новое значение — только если правили одну ячейку |
e.oldValue | что было до правки (тоже только для одной ячейки; при вводе в пустую ячейку поля просто нет) |
e.source | сама таблица (Spreadsheet) |
e.authMode | режим авторизации, в котором запущен триггер |
Практическая задача: на листе «Заявки» в колонке C менеджеры ставят статус. Нужно, чтобы в колонке D автоматически появлялась дата и время последнего изменения статуса.
const LIST_ZAYAVKI = 'Заявки';
const KOL_STATUS = 3; // колонка C
const KOL_METKA = 4; // колонка D
function onEdit(e) {
const range = e.range;
const sheet = range.getSheet();
// 1. Нас интересует только один лист
if (sheet.getName() !== LIST_ZAYAVKI) return;
// 2. И только колонка со статусом
if (range.getColumn() !== KOL_STATUS) return;
// 3. Шапку не трогаем
if (range.getRow() === 1) return;
sheet.getRange(range.getRow(), KOL_METKA).setValue(new Date());
}
Три ранних выхода в начале — не придирка, а главный приём для onEdit. Триггер срабатывает на любую правку в любой ячейке документа, поэтому первое, что делает функция, — проверяет, её ли это случай, и молча выходит, если нет. Без этих проверок скрипт будет штамповать дату всякий раз, когда кто-то поправит опечатку на соседнем листе.
Журнал изменений на e.oldValue
Ещё один частый сценарий — вести историю правок: кто, когда, что было и что стало. Пригодится, когда цифры в общем файле «сами меняются».
function onEdit(e) {
const log = e.source.getSheetByName('Журнал');
if (!log) return;
log.appendRow([
new Date(),
e.range.getSheet().getName(),
e.range.getA1Notation(),
e.oldValue === undefined ? '' : e.oldValue,
e.value === undefined ? '(несколько ячеек)' : e.value
]);
}
Результат: в листе «Журнал» появляется строка вида
14.07.2026 10:32:05 | Заявки | C7 | Новая | Оплачено
Обратите внимание на проверку undefined: если пользователь выделил и удалил сразу пять ячеек, полей e.value и e.oldValue в объекте события просто не будет. Код, который слепо им доверяет, начнёт писать в журнал пустоту.
Что НЕ считается правкой
Это место, где ломается больше всего сценариев. Простой onEdit реагирует только на правку человеком в интерфейсе. Он не сработает:
- когда значение записал другой скрипт через
setValue()илиsetValues(); - когда пересчиталась формула (например,
=NOW()или ссылка на другой лист); - когда данные подтянула
IMPORTRANGE; - когда строку добавила отправка Google Формы — для этого есть отдельный триггер
onFormSubmit, о нём в третьем уроке раздела; - когда таблицу изменили через Sheets API извне.
Иначе говоря: onEdit — это про пальцы на клавиатуре, а не про любое изменение данных.
Ограничения простых триггеров
Теперь главное. Простые триггеры выполняются без авторизации: Google никогда не показывает окно «Приложению нужен доступ к вашей почте». Удобно — но у медали есть обратная сторона. Раз разрешений никто не давал, то и сервисов, которым разрешения нужны, скрипт не увидит.
| Можно из простого триггера | Нельзя |
читать и писать текущую таблицу (SpreadsheetApp.getActive...) | отправлять письма (MailApp, GmailApp) |
| показывать меню, тосты, диалоги | ходить во внешние API (UrlFetchApp) |
| форматировать ячейки, добавлять строки | открывать другие файлы (SpreadsheetApp.openById, DriveApp) |
писать в Logger/console | работать дольше 30 секунд |
Есть и менее очевидные ограничения:
- Триггер не выполнится у пользователя, открывшего файл только для просмотра: правок он не делает, а
onOpenв режиме просмотра урезан. - Функция должна называться ровно
onEdit.onEdit2,myOnEdit,OnEdit— это обычные функции, Google их не вызовет. - В одном проекте скрипта может быть только одна функция с таким именем. Если вы объявите
onEditв двух файлах проекта, победит одна из них — обычно последняя, и это будет очень неочевидный баг. Собирайте всю логику в однойonEditи разводите её условиями.
Вывод простой: как только вам нужно отправить письмо, дёрнуть внешний сервис или что-то сделать по расписанию — простого триггера не хватит. Нужен устанавливаемый, о нём следующий урок.
Как это работает
Когда вы открываете таблицу с привязанным (bound) скриптом, Google просматривает проект и ищет функции с зарезервированными именами. Найдя onOpen, он выполняет её от имени текущего пользователя — того, кто открыл файл, — в ограниченном режиме авторизации. Именно этот режим лежит в e.authMode и обычно равен LIMITED.
Ключевое следствие: код исполняется у каждого, кто открыл документ. Ваш коллега, у которого есть право на правку, тоже увидит меню и тоже запустит вашу onEdit — но от своего имени и со своими правами. Никаких «а у меня всё работало» тут не бывает: работает у всех одинаково, потому что разрешений нет ни у кого.
Второе следствие — производительность. onEdit выполняется в момент правки, и пока он думает, таблица подтормаживает. Тяжёлые операции (пересчёт тысяч строк, выгрузка в PDF) из onEdit делать нельзя: и 30 секунд закончатся, и пользователей вы разозлите.
Частые ошибки
- Пытаются отправить письмо из onEdit. В журнале появляется
You do not have permission to call MailApp.sendEmail. Это не баг прав доступа к почте — это архитектурное ограничение простых триггеров. Лечится переходом на устанавливаемый триггер. - Тестируют onEdit кнопкой «Выполнить» в редакторе. Google не подставляет объект события,
eоказываетсяundefined, и вы получаетеTypeError: Cannot read properties of undefined (reading 'range'). Правильный способ — реально изменить ячейку в таблице, а результат посмотреть в журнале выполнений. Либо написать обёртку с поддельным событием:
function testOnEdit() {
const sheet = SpreadsheetApp.getActive().getSheetByName('Заявки');
onEdit({
range: sheet.getRange('C2'),
value: 'Оплачено',
oldValue: 'Новая',
source: SpreadsheetApp.getActive()
});
}
- Забывают проверить лист и колонку. Скрипт срабатывает на каждую правку во всём документе, ставит метки где попало и тормозит файл.
- Ждут, что onEdit поймает ответ формы или пересчёт формулы. Не поймает — см. выше.
- Ставят onOpen в отдельный (не привязанный) проект. Простые триггеры живут только в скрипте, привязанном к документу: «Расширения → Apps Script» из самой таблицы.
- Считают строки от нуля.
range.getRow()возвращает номер строки листа: шапка — это 1, а не 0. Массивы в JavaScript начинаются с нуля, лист — с единицы; на стыке рождается сдвиг на одну строку.
Итоги
onOpenиonEdit— функции с зарезервированными именами; их не нужно регистрировать, Google вызывает их сам.onOpenобычно строит пользовательское меню:getUi().createMenu(...).addItem(подпись, 'имяФункцииСтрокой').addToUi().onEditполучает объект событияe:e.range,e.value,e.oldValue,e.source.e.value/e.oldValueесть только при правке одной ячейки.- Первое, что должен делать
onEdit, — проверить лист, колонку и строку и выйти, если событие не его. - Простой триггер работает без авторизации: не более 30 секунд, никакой почты, внешних запросов и чужих файлов.
- Он реагирует только на правку человеком: ни setValue из скрипта, ни формулы, ни отправка формы его не разбудят.
- Нужно письмо, расписание или внешний сервис — переходите на устанавливаемые триггеры.