getUi 오류는 코드 문법보다 실행 위치를 먼저 확인해야 해결이 빠릅니다.
Google Apps Script에서 SpreadsheetApp.getUi() 오류가 나면 먼저 실행 위치를 확인해야 합니다. getUi()는 사용자가 열어 둔 구글시트 화면에 메뉴, 알림창, 사이드바 같은 UI를 붙일 때 쓰는 기능입니다. 그래서 시간 기반 트리거, 웹앱, 백그라운드 실행처럼 화면 UI가 없는 컨텍스트에서는 사용할 수 없습니다.
오류 메시지는 보통 아래처럼 표시됩니다.
Exception: Cannot call SpreadsheetApp.getUi() from this context.빠른 결론
SpreadsheetApp.getUi()는 구글시트 화면을 직접 조작하는 코드입니다. 커스텀 메뉴는 onOpen()에 두고, 시간 기반 트리거에서는 getUi() 대신 로그 기록, 시트 기록, 메일 알림 같은 방식으로 바꾸는 것이 안전합니다.
핵심 기준
사용자가 구글시트를 열어 둔 상태에서 실행되는 코드라면 UI를 붙일 수 있습니다. 반대로 트리거, 웹앱, 외부 호출처럼 화면 없이 실행되는 코드는 사용자에게 보여 줄 시트 UI가 없기 때문에 getUi()를 호출하면 오류가 날 수 있습니다.
언제 발생하는 오류인지
이 오류는 코드의 괄호나 함수 이름이 틀려서 생기는 문법 오류가 아닙니다. 실행되는 위치와 getUi()가 요구하는 환경이 맞지 않을 때 발생합니다.
예를 들어 구글시트를 열 때 커스텀 메뉴를 추가하는 코드는 정상적으로 동작할 수 있습니다. 하지만 같은 코드를 시간 기반 트리거로 실행하거나 웹앱의 doGet(), doPost() 안에서 실행하면 화면 UI를 찾을 수 없어 오류가 납니다.
원인 3가지
1. getUi는 구글시트 화면이 있는 실행 컨텍스트에서 사용합니다
SpreadsheetApp.getUi()는 현재 열려 있는 스프레드시트의 사용자 인터페이스에 접근합니다. 메뉴, 알림창, 프롬프트, 사이드바처럼 화면에 보이는 요소를 추가할 때 쓰입니다.
따라서 스크립트가 스프레드시트에 연결되어 있고, 사용자가 해당 파일을 열어 둔 흐름에서 실행되어야 합니다. 커스텀 메뉴를 만들 때 onOpen()을 쓰는 이유가 여기에 있습니다.
2. 시간 기반 트리거는 화면이 없는 백그라운드 실행입니다
시간 기반 트리거는 사용자가 시트를 열지 않아도 정해진 시간에 자동으로 실행됩니다. 이때는 화면에 메뉴를 추가하거나 알림창을 띄울 대상이 없습니다.
그래서 시간 기반 트리거 안에서 SpreadsheetApp.getUi().alert()나 createMenu()를 호출하면 Cannot call SpreadsheetApp.getUi() from this context 오류가 발생할 수 있습니다.
3. 웹앱 doGet·doPost와 스프레드시트 UI는 실행 위치가 다릅니다
doGet()과 doPost()는 Apps Script 웹앱의 진입점입니다. 사용자가 웹앱 URL에 접속하거나 외부 서비스가 HTTP 요청을 보낼 때 실행됩니다.
웹앱은 브라우저에 HTML 응답을 보내는 흐름입니다. 구글시트 메뉴를 직접 띄우는 흐름이 아니므로, 웹앱에서는 getUi()보다 HtmlService 또는 ContentService로 응답 화면을 만드는 방식이 맞습니다.
올바른 사용 예시: onOpen에서 커스텀 메뉴 만들기
구글시트에 커스텀 메뉴를 추가하려면 스프레드시트에 연결된 Apps Script 프로젝트에서 onOpen() 함수를 사용합니다. 사용자가 시트를 열 때 메뉴가 추가되는 구조입니다.
function onOpen(e) {
const ui = SpreadsheetApp.getUi();
ui.createMenu('자동화 메뉴')
.addItem('데이터 정리 실행', 'cleanSheetData')
.addSeparator()
.addItem('도움말 열기', 'showHelp')
.addToUi();
}
function cleanSheetData() {
const sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange('A1').setValue('데이터 정리 실행');
}
function showHelp() {
SpreadsheetApp.getUi().alert('자동화 메뉴에서 필요한 작업을 선택하세요.');
}이 코드는 구글시트 파일을 열 때 상단 메뉴에 “자동화 메뉴”를 추가합니다. 메뉴 항목을 클릭하면 연결된 함수가 실행됩니다.
커스텀 메뉴가 안 보일 때
코드를 저장한 뒤 구글시트 탭을 새로고침하세요. 단순히 Apps Script 편집기에서 onOpen()을 수동 실행하는 것보다, 실제 구글시트 파일을 다시 여는 방식으로 확인하는 편이 정확합니다.
잘못된 사용 예시: 시간 기반 트리거에서 getUi 호출
아래 코드는 시간 기반 트리거에서 실행하면 문제가 생기기 쉽습니다. 트리거는 백그라운드에서 실행되므로 사용자에게 띄울 구글시트 UI가 없습니다.
function runEveryMorning() {
const ui = SpreadsheetApp.getUi();
ui.alert('오늘 자동화가 실행되었습니다.');
}시간 기반 트리거에서는 아래처럼 로그를 남기거나, 특정 시트에 실행 결과를 기록하거나, 메일을 보내는 방식으로 바꿔야 합니다.
function runEveryMorning() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('실행로그') || ss.insertSheet('실행로그');
sheet.appendRow([
new Date(),
'시간 기반 트리거 실행 완료'
]);
Logger.log('시간 기반 트리거 실행 완료');
}실행 결과를 사람에게 알려야 한다면 MailApp.sendEmail()을 사용할 수 있습니다.
function runEveryMorning() {
const email = Session.getActiveUser().getEmail();
MailApp.sendEmail({
to: email,
subject: 'Apps Script 자동화 실행 완료',
body: '시간 기반 트리거가 정상 실행되었습니다.'
});
}실행 위치별 getUi 사용 가능 여부
getUi() 오류는 “어떤 함수에서 실행했는지”보다 “어떤 컨텍스트에서 실행됐는지”가 중요합니다. 아래 표를 기준으로 먼저 실행 위치를 나누어 보세요.
| 실행 위치 | getUi 사용 | 확인할 점 |
|---|---|---|
| onOpen | 가능 | 커스텀 메뉴 추가에 적합 |
| 수동 실행 | 상황에 따라 다름 | 시트 UI가 열린 컨텍스트인지 확인 |
| 시간 기반 트리거 | 사용 불가에 가까움 | Logger, 시트 기록, MailApp 사용 |
| onEdit | 권장하지 않음 | 편집 이벤트 처리와 UI 표시를 분리 |
| doGet·doPost | 부적합 | HtmlService 또는 응답 페이지 사용 |
해결 방법
커스텀 메뉴는 onOpen에 둡니다
커스텀 메뉴 생성 코드는 onOpen()에 두는 것이 가장 일반적입니다. 메뉴가 보이지 않는다면 코드를 저장했는지, 스프레드시트에 연결된 스크립트인지, 시트를 새로고침했는지부터 확인합니다.
function onOpen(e) {
SpreadsheetApp.getUi()
.createMenu('업무 자동화')
.addItem('보고서 만들기', 'createReport')
.addToUi();
}
function createReport() {
const sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange('A1').setValue('보고서 생성 시작');
}트리거에서는 getUi 대신 기록과 알림을 사용합니다
시간 기반 트리거는 사람이 화면을 보고 있는 상황이 아닙니다. 따라서 알림창 대신 실행 로그를 남기거나, 별도 로그 시트에 기록하거나, 필요할 때 이메일 알림을 보내는 구조가 좋습니다.
function scheduledJob() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('실행로그') || ss.insertSheet('실행로그');
sheet.appendRow([
new Date(),
'scheduledJob 실행'
]);
}트리거 자체가 실행되지 않는 경우에는 권한, 소유자, 시간 기반 트리거 설정을 따로 나누어 점검해야 합니다.
웹앱에서는 HtmlService로 화면을 만듭니다
웹앱에서 사용자에게 메시지를 보여 주려면 SpreadsheetApp.getUi().alert()가 아니라 HTML 응답을 반환해야 합니다.
function doGet(e) {
return HtmlService
.createHtmlOutput('<h1>자동화 실행 페이지</h1><p>요청이 정상 처리되었습니다.</p>')
.setTitle('Apps Script 웹앱');
}스프레드시트에 기록까지 해야 한다면 웹앱 응답과 시트 기록 코드를 분리하면 됩니다.
function doPost(e) {
const ss = SpreadsheetApp.openById('스프레드시트_ID');
const sheet = ss.getSheetByName('요청로그');
sheet.appendRow([
new Date(),
e.postData ? e.postData.contents : ''
]);
return HtmlService.createHtmlOutput('요청이 접수되었습니다.');
}권한과 트리거 점검 순서
커스텀 메뉴가 보이지 않거나 getUi() 오류가 계속 난다면 아래 순서로 점검하세요. 오류 원인을 코드 문법에서만 찾기보다 권한, 바인딩 상태, 실행 컨텍스트를 함께 봐야 합니다.
- 스크립트가 해당 구글시트에 연결된 컨테이너 바운드 프로젝트인지 확인합니다.
onOpen(e)함수 이름이 정확한지 확인합니다.- 코드를 저장한 뒤 구글시트 탭을 새로고침합니다.
- Apps Script 편집기에서 필요한 권한 승인을 완료합니다.
- 커스텀 메뉴 생성 코드를 시간 기반 트리거 함수 안에 넣지 않았는지 확인합니다.
doGet(),doPost()안에서SpreadsheetApp.getUi()를 호출하지 않았는지 확인합니다.- 트리거 실행 결과는 실행 로그, 시트 기록, 이메일 알림으로 바꿉니다.
- 여러 계정으로 시트를 열고 있다면 스크립트 소유자와 현재 로그인 계정을 확인합니다.
권한 승인 화면에서 막히거나 “Authorization required”가 함께 표시된다면 권한 오류로 이어진 문제일 수 있습니다. 이 경우 커스텀 메뉴 코드보다 승인 범위와 앱 검증 메시지를 먼저 확인해야 합니다.
공식 자료로 더 확인하기
getUi(), 커스텀 메뉴, 트리거, 웹앱은 실행 컨텍스트가 서로 다릅니다. 코드가 복잡해질수록 공식 문서에서 함수가 실행되는 위치와 UI 접근 가능 범위를 함께 확인하는 것이 좋습니다.
SpreadsheetApp.getUi()가 현재 열려 있는 스프레드시트 UI에 접근하는 함수인지 확인할 수 있습니다.
구글시트, 문서, 슬라이드, 폼에서 커스텀 메뉴를 추가하는 기본 구조와 onOpen() 사용 흐름을 확인할 수 있습니다.
onOpen, onEdit 같은 트리거와 웹앱 HTML 응답 처리 방식을 구분할 때 참고할 수 있습니다.
Apps Script HtmlService 공식 문서 확인하기
함께 보면 좋은 글
onEdit, doPost에서 실행 위치를 헷갈리면 e is undefined 같은 이벤트 객체 오류도 함께 생길 수 있습니다.
자주 묻는 질문
Cannot call SpreadsheetApp.getUi() from this context 오류는 코드 문법보다 실행 컨텍스트를 먼저 나누어 보면 해결 방향이 분명해집니다.
댓글