구글 앱스 스크립트(Google Apps Script, 이하 GAS)는 자바스크립트 기반으로 구글 워크스페이스(스프레드시트, 문서, 드라이브, 지메일, 캘린더 등)를 자동화·연동·확장하는 플랫폼입니다. 브라우저만 있으면 바로 코드를 작성하고 실행할 수 있으며, 시간/이벤트 트리거로 스케줄러를 만들고, 웹 앱으로 폼이나 미니 백엔드까지 배포 가능합니다. “매일 아침 스프레드시트 요약을 메일로 보내기”, “구글 폼 응답을 읽어 PDF 영수증을 생성해 드라이브에 저장하기”, “여러 시트의 데이터를 병합해서 대시보드를 갱신하기” 같은 일을 몇 줄의 코드로 해결할 수 있죠.
이 가이드는 처음 시작하는 분을 위해 ① 환경·권한·트리거를 이해하고, ② 핵심 문법과 서비스 객체(SpreadsheetApp/DriveApp/GmailApp 등)를 익히며, ③ 실무 레시피로 바로 효과를 체감하는 흐름으로 구성했습니다. 모든 예시는 복붙→실행이 가능하도록 최소 설정만 요구합니다. 목표는 간단합니다. 오늘 1개 자동화를 만들어 사람의 반복을 도구로 치환하는 것. 그 첫걸음을 지금 시작해 보죠.
1) 시작하기: 편집기, 권한(OAuth), 트리거, 로그·디버그까지 한 번에
앱스 스크립트는 설치가 필요 없습니다. 스프레드시트에서 확장 프로그램 > 앱스 스크립트를 열거나, script.google.com으로 들어가 프로젝트를 생성하면 즉시 코드를 작성할 수 있습니다. 아래 순서를 그대로 따라 하면 “만들기→권한 부여→실행→자동화”의 전체 흐름을 경험할 수 있어요.
① 프로젝트 열기(두 가지 방법)
- 스프레드시트 기반 — 스프레드시트를 연 뒤 확장 프로그램 > 앱스 스크립트를 클릭하면 그 파일에 종속된 컨테이너 바운드 프로젝트가 열립니다(파일과 수명 공유·간편 참조 장점).
- 독립형 — script.google.com > 새 프로젝트: 여러 파일·서비스를 아우르는 범용 스크립트를 만들 때 적합.
② 첫 함수: “시트에서 합계 구해 메일로 보내기”
function sendDailySpend() {
const ss = SpreadsheetApp.getActive(); // 현재 스프레드시트
const sh = ss.getSheetByName('대시보드') || ss.getSheets()[0];
const total = sh.getRange('B2').getDisplayValue(); // 예: B2 = 오늘 지출 합계
const subject = `📊 오늘 지출 요약 (${Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd')})`;
const body = `오늘 지출 합계: ${total}\n- 자동 발송: Apps Script`;
GmailApp.sendEmail(Session.getActiveUser().getEmail(), subject, body);
}
에디터 상단의 실행 ▶ 버튼을 누르면 최초 1회 권한(OAuth) 요청이 뜹니다. “권한 검토” → 사용하는 서비스(지메일/스프레드시트) 접근 권한 허용을 진행하세요. 이 과정은 “스크립트가 어떤 리소스를 사용해 무엇을 하려는지”를 승인받는 절차입니다.
③ 트리거로 자동화(스케줄러)
- 에디터 좌측 시계 아이콘(트리거) → 트리거 추가 → 함수 선택(sendDailySpend)
- 이벤트 소스: 시간 기반 → 매일/매시 등 원하는 주기로 설정
- 이제 버튼 누르지 않아도 정해진 시간에 자동으로 메일이 전송됩니다.
④ 로그·디버그: Logger·Stackdriver(Execution log)
function demoLog() {
const tz = Session.getScriptTimeZone();
Logger.log('현재 시간: %s', Utilities.formatDate(new Date(), tz, 'yyyy/MM/dd HH:mm:ss'));
}
- 실행 ▶ 후 상단 메뉴 실행 기록 또는 로그 보기에서 출력 확인.
- 디버그 버튼으로 중단점(breakpoint)을 걸어 변수 상태를 단계별 확인 가능.
⑤ 권한·제한 이해(중요)
- 스코프: 코드가 사용하는 서비스에 따라 필요한 권한 범위가 자동 지정됩니다(예: Gmail 읽기/쓰기, Drive 파일 편집 등).
- 할당량(Quota): 일·분 단위 호출 제한이 있습니다(예: 메일 전송 건수, Drive API 호출 수). 설계를 일괄 처리/캐시 중심으로 구성하세요.
- 시간제한: 단일 실행 시간 한도가 있습니다(유료 워크스페이스에서 상향). 긴 작업은 청크 분할 또는 재귀 트리거 전략을 사용.
⑥ 보안 팁
- 민감 정보(토큰/키)는 프로퍼티 서비스(PropertiesService)에 저장하고 코드에 하드코딩하지 않습니다.
- 외부 웹훅/웹앱은 도메인 제한·토큰 검증을 반드시 구현하세요.
2) 핵심 문법·서비스 빠르게 익히기: SpreadsheetApp·DriveApp·GmailApp·CalendarApp
GAS는 표준 자바스크립트 문법을 기반으로 하되, 구글 서비스에 접근하는 서비스 객체를 제공합니다. 가장 자주 쓰는 네 가지를 예시로 익혀보겠습니다. 모든 코드는 독립 실행이 가능하며, 변수만 여러분 상황에 맞게 바꾸면 됩니다.
① SpreadsheetApp — 시트 읽기/쓰기/찾기
function sheetBasics() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('원천') || ss.insertSheet('원천');
// 읽기
const values = sh.getRange(2,1, sh.getLastRow()-1, 5).getValues(); // A2:E
// 쓰기(한 번에 쓰면 빠름)
sh.getRange(1,6).setValue('합계'); // F1 = "합계"
const sums = values.map(r => [ (Number(r[4])||0) + (Number(r[3])||0) ]);
sh.getRange(2,6, sums.length, 1).setValues(sums); // F열 채우기
// 찾기
const found = sh.createTextFinder('오류').findNext();
if (found) found.setBackground('#fde68a'); // '오류'가 있으면 노랗게
}
② DriveApp — 폴더/파일 생성·검색·이동
function driveBasics() {
const root = DriveApp.getRootFolder();
const folderName = '자동리포트';
// 폴더 찾거나 만들기
const it = DriveApp.getFoldersByName(folderName);
const folder = it.hasNext() ? it.next() : DriveApp.createFolder(folderName);
// 파일 만들기
const file = DriveApp.createFile('readme.txt', '자동 생성된 파일', 'text/plain');
file.moveTo(folder); // 폴더로 이동
// 목록 출력
const files = folder.getFiles();
while (files.hasNext()) {
const f = files.next();
Logger.log('%s / %s bytes', f.getName(), f.getSize());
}
}
③ GmailApp — 자동 메일 발송(HTML 템플릿 포함)
function mailWithHtml() {
const to = Session.getActiveUser().getEmail();
const subject = '주간 리포트';
const html = `
<h3 style="margin:0 0 8px">주간 요약</h3>
<p>이번 주 지출 합계: <strong>₩1,245,000</strong></p>
<p style="color:#64748b">* Apps Script 자동 발송</p>`;
GmailApp.sendEmail(to, subject, 'HTML 보기 지원 필요', {htmlBody: html});
}
④ CalendarApp — 일정 생성·조회(가계부 리마인드 등)
function calendarReminder() {
const cal = CalendarApp.getDefaultCalendar();
const tomorrow = new Date(Date.now() + 24*60*60*1000);
cal.createAllDayEvent('가계부 정리', tomorrow, {description: '카테고리/예산 점검'});
}
⑤ Utilities·Session·PropertiesService — 시간·환경·비밀키
function helpers() {
// 시간 포맷
const now = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd HH:mm');
// 스크립트 속성(비밀키 저장)
const props = PropertiesService.getScriptProperties();
props.setProperty('WEBHOOK_URL', 'https://example.com/hook'); // 저장
const url = props.getProperty('WEBHOOK_URL'); // 읽기
Logger.log('%s / %s', now, url);
}
⑥ onEdit/onFormSubmit — 이벤트 기반 실행(시트 편집/폼 응답)
function onEdit(e) {
const range = e.range; // 편집된 범위
if (range.getColumn() === 7) { // 예: G열(상태) 바뀌면
range.setBackground('#e0f2fe'); // 색상 표시
}
}
function onFormSubmit(e) {
const row = e.values; // 폼 응답 한 행
const [time, category, price] = row; // 폼 질문 순서에 맞춰 구조 분해
if (Number(price) > 1000000) {
GmailApp.sendEmail(Session.getActiveUser().getEmail(), '고액 지출 감지', `${category}: ${price}`);
}
}
⑦ HTML Service로 간단한 웹 앱(폼·대시보드) 만들기
// Code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile('index').setTitle('미니 대시보드');
}
function addItem(name, price) {
const sh = SpreadsheetApp.getActive().getSheetByName('원천');
sh.appendRow([new Date(), name, Number(price)||0]);
return 'OK';
}
<!-- index.html -->
<div style="font-family:system-ui;padding:16px">
<h3>지출 입력</h3>
<input id="name" placeholder="항목" />
<input id="price" type="number" placeholder="금액" />
<button onclick="submit()">저장</button>
<script>
function submit(){
const n = document.getElementById('name').value;
const p = document.getElementById('price').value;
google.script.run.withSuccessHandler(()=>alert('OK')).addItem(n,p);
}
</script>
</div>
- 배포: 배포 > 새 배포 > 유형: 웹 앱 → “나로 실행” / “접근 권한: 본인 또는 도메인” 선택.
3) 실전 자동화 레시피: 바로 효과 나는 10가지 스니펫
기초를 익혔다면, 바로 써먹는 짧고 강력한 레시피들로 체감을 높여 보세요. 각 스니펫은 독립 실행 가능하며, 여러분의 시트/폴더 이름만 바꾸면 됩니다.
① 전일 요약을 메일로(스프레드시트 → 지메일)
function mailYesterdaySummary() {
const tz = Session.getScriptTimeZone();
const y = new Date(Date.now()-24*60*60*1000);
const ymd = Utilities.formatDate(y, tz, 'yyyy-MM-dd');
const sh = SpreadsheetApp.getActive().getSheetByName('거래');
const rows = sh.getRange(2,1, sh.getLastRow()-1, 5).getValues();
let spend=0, income=0;
rows.forEach(r => {
const d = Utilities.formatDate(new Date(r[0]), tz, 'yyyy-MM-dd');
if (d===ymd) { if (r[1]==='지출') spend+=r[4]; if (r[1]==='수입') income+=r[4]; }
});
const body = `📅 ${ymd}\n- 지출: ${spend.toLocaleString()}원\n- 수입: ${income.toLocaleString()}원\n- 순현금흐름: ${(income+spend).toLocaleString()}원`;
GmailApp.sendEmail(Session.getActiveUser().getEmail(), `[요약] ${ymd}`, body);
}
② 완료 행을 아카이브로 이동
function archiveDone() {
const ss = SpreadsheetApp.getActive(), src = ss.getSheetByName('작업'), dst = ss.getSheetByName('아카이브')||ss.insertSheet('아카이브');
const data = src.getDataRange().getValues(); const keep=[data[0]], move=[];
for (let i=1;i<data.length;i++){ (data[i][3]==='완료' ? move : keep).push(data[i]); }
if (move.length) dst.getRange(dst.getLastRow()+1,1,move.length,move[0].length).setValues(move);
src.clearContents(); src.getRange(1,1,keep.length,keep[0].length).setValues(keep);
}
③ 여러 스프레드시트에서 범위 끌어와 병합(간편 ETL)
function mergeSheets() {
const urls = ['스프레드시트URL1','스프레드시트URL2'];
const dst = SpreadsheetApp.getActive().getSheetByName('원천')||SpreadsheetApp.getActive().insertSheet('원천');
dst.clearContents();
let r = 1;
urls.forEach(u => {
const sh = SpreadsheetApp.openByUrl(u).getSheets()[0];
const rg = sh.getDataRange().getValues();
dst.getRange(r,1,rg.length,rg[0].length).setValues(rg);
r += rg.length;
});
}
④ 피벗 테이블 자동 생성(시트 내부)
function makePivotGAS() {
const ss = SpreadsheetApp.getActive();
const src = ss.getSheetByName('원천').getDataRange();
const psh = ss.getSheetByName('피벗') || ss.insertSheet('피벗'); psh.clear();
const pivot = psh.getRange(1,1).createPivotTable(src);
pivot.addRowGroup(1).showTotals(true); // 1열(예: 날짜)
pivot.addColumnGroup(2); // 2열(예: 채널)
pivot.addPivotValue(5, SpreadsheetApp.PivotTableSummarizeFunction.SUM); // 5열(금액) 합계
}
⑤ 차트를 이미지로 내보내 드라이브 저장
function exportChartAsImage() {
const sh = SpreadsheetApp.getActive().getSheetByName('대시보드');
const chart = sh.getCharts()[0];
const blob = chart.getAs('image/png').setName('dashboard.png');
DriveApp.createFile(blob);
}
⑥ 드라이브 폴더의 최신 파일 찾기
function getLatestFileInFolder() {
const folder = DriveApp.getFoldersByName('input').next();
let latest=null, t=0;
const files = folder.getFiles();
while (files.hasNext()){
const f = files.next();
if (f.getLastUpdated().getTime() > t) { t = f.getLastUpdated().getTime(); latest = f; }
}
Logger.log('최신 파일: %s', latest && latest.getName());
}
⑦ 슬랙/웹훅으로 알림 보내기(간단 POST)
function postWebhook() {
const url = PropertiesService.getScriptProperties().getProperty('WEBHOOK_URL');
const payload = {text: 'Apps Script 테스트 알림 ✅'};
UrlFetchApp.fetch(url, {method:'post', contentType:'application/json', payload: JSON.stringify(payload)});
}
⑧ onEdit으로 데이터 검증(잘못된 값 즉시 되돌리기)
function onEdit(e) {
const r = e.range, sh = r.getSheet();
if (sh.getName()==='원천' && r.getColumn()===5) { // 금액열
const v = Number(r.getValue());
if (isNaN(v)) { r.setValue(''); SpreadsheetApp.getActive().toast('숫자만 입력하세요'); }
}
}
⑨ 시간 초과 회피(청크 분할)
function processChunk() {
const props = PropertiesService.getScriptProperties();
const idx = Number(props.getProperty('IDX')||'2'); // 2행부터 처리
const sh = SpreadsheetApp.getActive().getSheetByName('원천');
const last = sh.getLastRow();
for (let r=idx; r<=last; r++){
// ...행 처리...
if ((r-idx) % 500 === 0 && (Date.now()%60000)>50000) { // 대략 시간 체크
props.setProperty('IDX', String(r+1));
ScriptApp.newTrigger('processChunk').timeBased().after(1000).create();
return;
}
}
props.deleteProperty('IDX'); // 완료
}
⑩ 에러 핸들링·알림(예외 발생 시 메일)
function safeWrapper() {
try {
// 핵심 작업 함수 호출
mailYesterdaySummary();
} catch (err) {
const msg = `오류: ${err.message}\nStack: ${err.stack}`;
GmailApp.sendEmail(Session.getActiveUser().getEmail(), 'GAS 오류 알림', msg);
throw err;
}
}
결론: 작은 자동화 1개가 하루 10분을 되돌려 준다
앱스 스크립트의 가치는 “복잡한 개발”이 아니라 업무의 마찰을 줄이는 것에 있습니다. 브라우저에서 바로 코드를 쓰고, 스프레드시트/드라이브/지메일을 원클릭으로 묶어, 사람이 반복하던 클릭을 도구에 맡기는 순간, 하루 10분·한 달 수시간의 여유가 생깁니다. 이 가이드를 통해 환경·권한·트리거를 설정하고, 핵심 서비스 객체의 사용감을 익혔으며, 실전 스니펫으로 바로 효과를 냈을 것입니다.
다음 단계는 간단합니다. 오늘은 “전일 요약 메일”을 자동화하고, 내일은 “완료 행 아카이브”를 붙이고, 이번 주에는 “피벗·차트 갱신 + 이미지 내보내기”까지 확장해 보세요. 중요한 건 완벽함이 아니라 작게 시작해 꾸준히 확장하는 리듬입니다. 자동화가 쌓일수록 여러분은 입력·정리에 쓰던 시간을 해석과 의사결정에 투입하게 됩니다. 그게 도구가 우리를 돕는 가장 정확한 방식이니까요.