구글 스프레드시트 하나로 굴러가는 행사 출석체크 웹앱입니다. 서버도, 호스팅도, 회원가입도 없습니다. 시트에 명단만 넣으면 끝납니다.
참석자는 링크를 열어 자기 이름을 찾아 누르고, 확인하면 출석이 시트에 바로 쌓입니다.
[휴대폰·태블릿] → 검색해서 이름 누름 → 확인창 → 구글 시트 '출석' 탭
- 이름·소속 검색 — 부분 일치. 명단이 몇백 명이어도 바로 찾습니다.
- 초성 검색 —
ㅂㅊㅇ만 쳐도 "박찬우"가 나옵니다. 칠수록 좁혀집니다. - 명단에 없는 사람 — 직접 입력 버튼. 입력 칸은 명단 시트의 열 구성대로 자동으로 만들어집니다.
- 출석한 사람은 하늘색으로 바뀌어 목록 맨 아래로 내려갑니다. 누가 아직 안 했는지 한눈에 보입니다.
- 태블릿 여러 대 동시 사용 — 한 대에서 등록하면 다른 대에도 몇 초 안에 반영됩니다.
같은 사람을 동시에 눌러도
LockService로 한 줄만 기록됩니다. - 어떤 명단이든 — 부서·팀·직급이든 학번·학과든, 열 이름만 설정에 적으면 그대로 동작합니다.
-
sheets.new 로 새 스프레드시트를 만듭니다.
-
시트 탭 이름을
명단으로 바꿉니다. -
1행에 머리글, 2행부터 사람을 넣습니다. 열 구성은 자유지만 이름 열의 머리글은
이름이어야 합니다.연번 부서명 팀명 직급 이름 1 기획과 기획팀 7급 김하나 CSV가 있으면 파일 → 가져오기 → 업로드 → 현재 시트 바꾸기 로 넣으면 됩니다. (한글이 깨지면 그 CSV가 UTF-8이 아닌 것입니다. 아래 한글 깨짐 참고)
출석시트는 첫 등록 때 자동으로 만들어집니다. 미리 만들 필요 없습니다.
- 시트 메뉴에서 확장 프로그램 → Apps Script
- 기본으로 열린
코드.gs내용을 지우고 이 저장소의Code.gs를 붙여넣습니다. - 왼쪽 파일 옆 + → HTML, 이름을
index로 지정합니다. (index.html이 아니라index— 확장자는 자동으로 붙습니다) - 그 파일 내용을 지우고 이 저장소의
index.html을 붙여넣습니다. - 저장 (Ctrl+S)
붙여넣은 뒤 첫 줄을 확인하세요.
코드.gs는/**로,index.html은<!DOCTYPE html>로 시작합니다. 서로 바꿔 넣으면SyntaxError: Unexpected token '<'가 납니다.
- 오른쪽 위 배포 → 새 배포
- ⚙️ → 웹 앱
- 설정
- 다음 사용자로 실행: 나
- 액세스 권한이 있는 사용자: 모든 사용자 ← 이게 아니면 참석자에게 로그인 화면이 뜹니다
- 배포 → 권한 승인 ("Google에서 확인하지 않은 앱" 경고가 나오면 고급 → 이동. 본인이 만든 스크립트라 정상입니다)
- 나온 웹 앱 URL 이 참석자에게 뿌릴 주소입니다. QR코드로 만들면 편합니다.
배포 → 배포 관리 → ✏️ → 버전 새 버전 → 배포
"새 배포"를 누르면 URL이 바뀝니다. 이미 QR코드를 뿌렸다면 반드시 배포 관리 → ✏️ 로 갱신하세요.
Code.gs 맨 위 CONFIG 만 고치면 됩니다. 나머지는 건드릴 일이 없습니다.
const CONFIG = {
title: '출석체크', // 화면 제목
subtitle: '명단에서 본인 이름을...', // 제목 아래 안내문
rosterSheet: '명단', // 명단이 든 탭 이름
logSheet: '출석', // 출석이 쌓일 탭 이름
nameHeader: '이름', // 명단 1행에서 이름이 든 열의 머리글
hiddenHeaders: ['연번'], // 화면에 쓰지 않을 열
requiredHeaders: ['부서명'], // 직접 입력 시 필수인 열
dupKeyHeaders: ['부서명'], // 이름과 함께 중복 판정에 쓸 열
logoDataUri: '', // 제목 오른쪽 로고 (아래 참고)
refreshSeconds: 10, // 다른 기기의 등록을 몇 초마다 받아올지
sheetId: '', // 비우면 붙어 있는 시트를 씀
};열 이름만 바꾸면 됩니다. 예를 들어 학번 / 이름 / 학과 구성이라면:
nameHeader: '이름',
hiddenHeaders: ['학번'],
requiredHeaders: ['학과'],
dupKeyHeaders: ['학과'],화면의 소속 표시, 직접 입력 칸, 출석 시트의 열이 전부 여기에 맞춰 자동으로 바뀝니다.
같은 이름이 여럿일 수 있어서입니다. 비워 두면 이름만으로 중복을 판정해서, 동명이인 중 한 명이 등록하면 다른 사람이 못 하게 됩니다. 소속처럼 사람을 구분해 주는 열을 넣으세요.
이미지 파일을 따로 올릴 데가 없으므로 base64로 코드 안에 넣습니다. 수십 KB 이하 이미지면 충분합니다.
# 결과 문자열을 CONFIG.logoDataUri 에 통째로 붙여넣기
python -c "import base64,sys;print('data:image/png;base64,'+base64.b64encode(open(sys.argv[1],'rb').read()).decode())" logo.png배경이 흰색인 로고는 mix-blend-mode: multiply 로 페이지 바탕색에 자연히 녹습니다(이미 적용됨).
- 시트 메뉴 출석체크 → 출석 기록 모두 지우기 로 테스트 기록을 비웁니다.
- 태블릿마다 URL을 열고 화면 자동 꺼짐을 "안 함" 으로, 브라우저는 전체화면으로.
- 한 대에서 본인 이름으로 등록해 보고, 다른 대에도 반영되는지 확인한 뒤 그 기록을 지웁니다.
- 끝나면 배포 → 배포 관리 → 보관처리 로 링크를 닫아 두면 추가 등록이 막힙니다.
| 증상 | 원인 / 조치 |
|---|---|
SyntaxError: Unexpected token '<' |
index.html 을 코드.gs 에 붙여넣은 것입니다 |
'명단' 시트 1행에 '이름' 열이 없습니다 |
머리글을 확인하세요. CONFIG.nameHeader 와 정확히 같아야 합니다 |
| 참석자에게 로그인 화면이 뜸 | 배포 설정의 액세스 권한이 모든 사용자 가 아닙니다 |
| 고쳤는데 화면이 그대로 | ① 새 버전으로 재배포했는지 ② 브라우저 강력 새로고침 (Ctrl+Shift+R) |
| 상단에 파란 띠 안내문 | Apps Script 웹앱에 구글이 붙이는 배너입니다. 제거할 수 없고, X로 닫을 수 있습니다 |
| 출석했는데 하늘색이 안 됨 | Code.gs 가 옛 버전입니다. 다시 붙여넣고 새 버전으로 재배포하세요 |
CSV를 가져왔는데 ¿¬¹ø 처럼 깨져 보이면 그 파일이 UTF-8이 아니라 CP949입니다. 변환 후 가져오세요.
python -c "import io,sys;io.open(sys.argv[2],'w',encoding='utf-8-sig').write(open(sys.argv[1],'rb').read().decode('cp949'))" in.csv out.csvnode test.jsSpreadsheetApp 을 가짜로 만들어 Code.gs 를 실제로 실행합니다. 설치할 것은 없습니다.
명단 읽기, 중복 차단, 직접 입력, 다른 열 구성, 빈 시트, 초성 검색을 검사합니다.
| 파일 | 용도 |
|---|---|
Code.gs |
Apps Script 서버 코드 |
index.html |
참석자 화면. Apps Script에 index 라는 HTML 파일로 넣습니다 |
test.js |
node test.js — 의존성 없음 |
sample-roster.csv |
예시 명단 |
- 구글 계정이 필요합니다 (배포하는 사람만. 참석자는 필요 없습니다).
- Apps Script 무료 한도를 씁니다. 태블릿 3대 × 10초 갱신 기준으로 몇 시간짜리 행사는 여유롭습니다.
기기가 훨씬 많다면
refreshSeconds를 늘리세요. - 링크를 아는 사람은 누구나 등록할 수 있습니다. 출석체크 용도에는 맞지만, 본인 확인이 필요한 곳에는 쓰지 마세요.
- 상단의 구글 안내 배너는 제거할 수 없습니다.
MIT — LICENSE
Attendance check-in that runs entirely on one Google Sheet. No server, no hosting, no signup.
Attendees open a link, search for their name (Korean initial-consonant search included), tap it, confirm, and the check-in lands in the sheet immediately. Checked-in people turn light blue and move to the bottom of the list, so you can see at a glance who is still missing.
Setup: create a sheet with a 명단 (roster) tab → Extensions → Apps Script → paste Code.gs and
index.html (as an HTML file named index) → Deploy as a Web app with access set to Anyone.
Full walkthrough in the Korean section above.
Any roster layout works. The app reads the roster by header name, not column position. Point
CONFIG.nameHeader at whichever column holds the name; every other column is shown as the person's
affiliation, generated into the manual-entry form, and written to the log sheet. Student ID / name /
major works exactly as well as department / team / rank / name.
Multiple tablets stay in sync — each screen re-reads the roster every refreshSeconds, and
LockService guarantees a single row even if two devices submit the same person at once.
The UI strings are Korean. Translating means editing CONFIG.title, CONFIG.subtitle, and a handful
of literals in index.html — no logic changes.
Run node test.js to exercise the server logic against a stubbed SpreadsheetApp. No dependencies.
MIT licensed.