毎月のシフト表づくりは、地味に時間を食う仕事です。先月の表をコピーして日付と曜日を直し、スタッフの希望を埋めて、「この日、早番が足りてない」を目で数えて探す——1回あたり1〜2時間、月に必ず発生します。しかも曜日のずれや人数の数え間違いは、そのまま現場の混乱になります。
このページでは、シフト表づくりを楽にする方法を2段階で紹介します。①今日から使える無料のExcelテンプレート(集計式・プルダウンつき)、②ボタン一つでその月のシフト表が丸ごと生成されるGAS(Google Apps Script)。GASのコードは全文コピペできる形で載せています。
私はAIを活用してこうした業務自動化ツールを作っています。この記事のコードとテンプレートは、公開前に実際にGoogleスプレッドシート上で動かして動作確認済みです(2026年7月)。
完成イメージ:自動生成されたシフト表
下の画像は、この記事のGASをスプレッドシートに貼って、メニューから「今月のシフト表を作る」を1回押した結果です(実際の動作確認時のスクリーンショット)。

▲ 1〜31日の日付と曜日・土日の色分け・記号のプルダウン・集計式まで、すべて自動で入ります。最下段の「出勤合計」が設定人数を下回る日は自動で赤くなります。
手でやると1時間かかる「月替わりの表の作り直し」が数秒になり、さらに:
- 各スタッフの出勤日数・休み・有休が右端に自動集計される
- 日ごとの早番/日勤/遅番の人数と出勤合計が最下段に出る
- 出勤人数が足りない日は自動で赤くなる+メニューから不足日の一覧も出せる
Excelテンプレ派とGAS派、どちらを選ぶか
| ①Excelテンプレート | ②GAS(スプレッドシート) | |
|---|---|---|
| 向いている人 | まず今月分をすぐ整えたい/Excelで管理したい | 毎月の表づくり自体をなくしたい/Googleスプレッドシート派 |
| できること | 記号を入れるだけで集計(出勤・休・有休・日別人数) | 左に加えて、月替わりの表の生成・曜日・土日色分け・人数不足の警告まで自動 |
| 導入の手間 | ダウンロードするだけ | コードを1回コピペ(5分) |
迷ったら、まず①のテンプレートで運用を始めて、「月初の表づくりが面倒だ」と感じたら②に進むのがおすすめです。管理する項目はどちらも同じなので、あとから乗り換えても運用は変わりません。
①無料Excelテンプレートのダウンロード
縦=名前(12人分)× 横=1〜31日のマトリクスに、記号(早=早番/日=日勤/遅=遅番/休=休み/有=有休)をプルダウンで入れるだけのテンプレートです。無料・登録不要で、そのまま使えます。
.xlsx形式(約14KB)。使用関数はCOUNTIFとIFのみで、Googleスプレッドシートにインポートしてもプルダウン・自動集計ごと動きます(動作確認済み)。A4横向きの印刷設定済み。個人・自社の業務での利用は自由です。
中身は3シートです。
- 使い方: 3ステップの使い方と記入のポイント
- 記入例: 小さな飲食店(社員2人+パート2人)で1か月記入した見本
- シフト表(ひな形): 記入用。月ごとにシートをコピーして使います
記号を入れると、右端に各人の出勤日数・休・有休、最下段に早番/日勤/遅番の人数と出勤合計が自動で出ます。「休み」と「シフトなし」を区別したいときは、休みの日だけ「休」を入れ、そもそもシフトが無い日(パートの非稼働日など)は空欄にしておくのがコツです。
②GASでシフト表の作成まで自動化する(コピペコード)
テンプレートでも集計は自動になりますが、月が替わるたびの「表の作り直し」——日数の違い、曜日のずれ、土日の色分け——は残ります。ここを消すのがGASです。導入は次の5ステップ、1回だけです。
- Googleスプレッドシートを新規作成する(
sheets.newをブラウザで開くと早いです) - メニューの拡張機能 → Apps Script を開く
- エディタの中身を全部消して、下のコードを貼り付ける
- 上部の保存アイコン(またはCtrl+S)で保存する
- スプレッドシートのタブに戻って再読み込みする → メニューに「シフト表」が増えます
あとは シフト表 → 今月のシフト表を作る(または「来月」)を押すだけです。初回だけ承認画面が出ます(つまづきやすいポイント参照)。
/**
* 月間シフト表の自動作成+集計(Google Apps Script)
* メニュー「シフト表」から実行します。
* 配布元: https://nishi-jidoka.pages.dev/articles/shift-gas-automation
*/
// ==== 設定(ここだけ自分のお店・チームに合わせる)====
const STAFF = ['山田(店長)', '佐々木', '井上(パート)', '高橋(パート)']; // 名簿(行の並び順)
const SHIFT_CODES = ['早', '日', '遅', '休', '有']; // プルダウンの選択肢
const WORK_CODES = ['早', '日', '遅']; // 「出勤」として数える記号
const MIN_STAFF = 2; // 1日に最低必要な出勤人数(不足日を赤くする)
/** スプレッドシートを開くとメニューを追加 */
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('シフト表')
.addItem('今月のシフト表を作る', 'createThisMonth')
.addItem('来月のシフト表を作る', 'createNextMonth')
.addItem('人数不足の日を一覧表示', 'listShortage')
.addToUi();
}
function createThisMonth() { createShiftSheet_(0); }
function createNextMonth() { createShiftSheet_(1); }
/** 対象月のシフト表シートを作る(offset=0: 今月, 1: 来月) */
function createShiftSheet_(offset) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const now = new Date();
const first = new Date(now.getFullYear(), now.getMonth() + offset, 1);
const days = new Date(first.getFullYear(), first.getMonth() + 1, 0).getDate();
const name = first.getFullYear() + '年' + (first.getMonth() + 1) + '月';
if (ss.getSheetByName(name)) {
SpreadsheetApp.getUi().alert('シート「' + name + '」は既にあります。作り直す場合は先に削除してください。');
return;
}
const sheet = ss.insertSheet(name, 0);
const WD = ['日', '月', '火', '水', '木', '金', '土'];
const HEAD_NUM = 2; // 日付(数字)の行
const HEAD_WD = 3; // 曜日の行
const FIRST_ROW = 4; // 名簿の先頭行
const lastRow = FIRST_ROW + STAFF.length - 1;
const totalRow = lastRow + 1; // 出勤合計の行
const dayCol1 = 2; // 1日の列(B)
const dayColN = dayCol1 + days - 1;
const aggCol = dayColN + 1; // 出勤/休/有休の3列
// --- タイトルとヘッダー(値は配列で一括セット) ---
sheet.getRange(1, 1)
.setValue(name + ' シフト表(早=早番/日=日勤/遅=遅番/休=休み/有=有休。シフトが無い日は空欄)')
.setFontWeight('bold');
const numRow = ['名前'];
const wdRow = [''];
for (let d = 1; d <= days; d++) {
numRow.push(d);
wdRow.push(WD[new Date(first.getFullYear(), first.getMonth(), d).getDay()]);
}
numRow.push('出勤', '休', '有休');
wdRow.push('', '', '');
sheet.getRange(HEAD_NUM, 1, 1, numRow.length).setValues([numRow]);
sheet.getRange(HEAD_WD, 1, 1, wdRow.length).setValues([wdRow]);
sheet.getRange(HEAD_NUM, 1, 2, numRow.length)
.setFontWeight('bold').setHorizontalAlignment('center')
.setBackground('#0F766E').setFontColor('#FFFFFF');
// --- 土日のヘッダーを色分け(日=赤系/土=青系) ---
for (let d = 1; d <= days; d++) {
const wd = new Date(first.getFullYear(), first.getMonth(), d).getDay();
if (wd === 0 || wd === 6) {
sheet.getRange(HEAD_NUM, dayCol1 + d - 1, 2, 1)
.setBackground(wd === 0 ? '#B91C1C' : '#1D4ED8');
}
}
// --- 名簿 ---
sheet.getRange(FIRST_ROW, 1, STAFF.length, 1)
.setValues(STAFF.map(function (s) { return [s]; }));
// --- 行ごとの集計式(出勤・休・有休) ---
const rowFormulas = STAFF.map(function (_, i) {
const r = FIRST_ROW + i;
const rng = colLetter_(dayCol1) + r + ':' + colLetter_(dayColN) + r;
const work = WORK_CODES.map(function (c) { return 'COUNTIF(' + rng + ',"' + c + '")'; }).join('+');
return ['=' + work, '=COUNTIF(' + rng + ',"休")', '=COUNTIF(' + rng + ',"有")'];
});
sheet.getRange(FIRST_ROW, aggCol, STAFF.length, 3).setFormulas(rowFormulas);
// --- 日別の出勤合計(最下段) ---
sheet.getRange(totalRow, 1).setValue('出勤合計').setFontWeight('bold');
const totals = [[]];
for (let d = 0; d < days; d++) {
const col = colLetter_(dayCol1 + d);
const rng = col + FIRST_ROW + ':' + col + lastRow;
totals[0].push('=' + WORK_CODES.map(function (c) { return 'COUNTIF(' + rng + ',"' + c + '")'; }).join('+'));
}
sheet.getRange(totalRow, dayCol1, 1, days).setFormulas(totals);
sheet.getRange(totalRow, 1, 1, aggCol - 1).setBackground('#E6F4F1');
// --- プルダウン(早/日/遅/休/有) ---
const rule = SpreadsheetApp.newDataValidation()
.requireValueInList(SHIFT_CODES, true).setAllowInvalid(true).build();
sheet.getRange(FIRST_ROW, dayCol1, STAFF.length, days).setDataValidation(rule);
// --- 人数不足の日を赤くする(条件付き書式) ---
const totalRange = sheet.getRange(totalRow, dayCol1, 1, days);
const cf = SpreadsheetApp.newConditionalFormatRule()
.whenNumberLessThan(MIN_STAFF)
.setBackground('#FECACA')
.setRanges([totalRange])
.build();
sheet.setConditionalFormatRules(sheet.getConditionalFormatRules().concat(cf));
// --- 見た目 ---
sheet.setColumnWidth(1, 110);
sheet.setColumnWidths(dayCol1, days, 34);
sheet.setColumnWidths(aggCol, 3, 48);
sheet.getRange(FIRST_ROW, dayCol1, STAFF.length + 1, days).setHorizontalAlignment('center');
sheet.setFrozenRows(HEAD_WD);
sheet.setFrozenColumns(1);
ss.toast(name + ' のシフト表を作りました(' + days + '日分)', '完了', 5);
}
/** アクティブなシフト表シートで、出勤合計がMIN_STAFF未満の日を一覧表示 */
function listShortage() {
const sheet = SpreadsheetApp.getActiveSheet();
const ui = SpreadsheetApp.getUi();
const colA = sheet.getRange(1, 1, sheet.getLastRow(), 1).getValues();
let totalRow = 0;
for (let i = 0; i < colA.length; i++) {
if (colA[i][0] === '出勤合計') { totalRow = i + 1; break; }
}
if (!totalRow) {
ui.alert('このシートに「出勤合計」行が見つかりません。シフト表のシートで実行してください。');
return;
}
const days = sheet.getLastColumn() - 4; // 名前列と右端3列(出勤/休/有休)を除く
const totals = sheet.getRange(totalRow, 2, 1, days).getValues()[0];
const nums = sheet.getRange(2, 2, 1, days).getValues()[0];
const short = [];
for (let d = 0; d < days; d++) {
if (Number(totals[d]) < MIN_STAFF) short.push(nums[d] + '日(' + totals[d] + '人)');
}
ui.alert(short.length
? '出勤が' + MIN_STAFF + '人未満の日:\n' + short.join(' / ')
: '人数不足の日はありません(最低' + MIN_STAFF + '人)。');
}
/** 列番号 → A1形式の列文字(1→A, 27→AA) */
function colLetter_(n) {
let s = '';
while (n > 0) {
const m = (n - 1) % 26;
s = String.fromCharCode(65 + m) + s;
n = Math.floor((n - 1) / 26);
}
return s;
}
自分のお店に合わせるカスタマイズ
書き換えるのはコード上部の「設定」4行だけです。
| 設定 | 意味 | 例 |
|---|---|---|
STAFF | 名簿(表の行の並び順) | ['田中', '鈴木', '佐藤'] — 人数は何人でもOK |
SHIFT_CODES | プルダウンの選択肢 | 2交代なら ['出', '休', '有'] のように減らしてもよい |
WORK_CODES | 「出勤」として数える記号 | 上の例なら ['出'] |
MIN_STAFF | 1日に最低必要な出勤人数 | この人数を下回る日の「出勤合計」が赤くなります |
変更を保存したら、翌月分から新しい設定で生成されます。すでに作ったシートはそのまま残るので、月の途中で設定を変えても過去の表は壊れません。
つまづきやすいポイント
初回の「承認が必要です」画面
初回実行時はGoogleの承認画面が出ます。「詳細」→「(プロジェクト名)に移動」から進んで許可してください。このコードが要求する権限はスプレッドシートの操作のみで、外部への送信処理はありません(貼り付けたコードがすべてなので、気になる場合は上のコードを確認してからで大丈夫です)。
記号を変えたら「集計に出ない」
集計は記号の文字そのものを数えています(COUNTIF)。SHIFT_CODES を変えたら WORK_CODES も必ずセットで見直してください。Excelテンプレート版も同じで、プルダウンと集計式の記号を揃える必要があります。
「シートが既にあります」と出る
同じ月のシートは二重に作らない仕様です。作り直したいときは、先にそのシートを削除(またはシート名を変更)してから実行してください。
祝日の色分けは入っていません
土日は自動で色分けされますが、祝日はお店ごとに扱いが違う(祝日も通常営業のお店が多い)ため、あえて自動化していません。必要なら該当列に手動で色を付けるのが早いです。
よくある質問
Q1. 承認画面が不安です。データはどこかに送られますか?
送られません。このスクリプトは自分のアカウント内のスプレッドシートだけを操作します。要求される権限もスプレッドシートの操作のみです。コードは上に全文載せているので、確認してから使えます。
Q2. Excelだけで使いたいのですが。
①のテンプレートをそのままExcelでお使いください。集計とプルダウンはExcelでも全部動きます。GoogleスプレッドシートにインポートしてもOKです。
Q3. スタッフが20人います。使えますか?
使えます。GAS版は STAFF に人数分の名前を並べるだけで、行数は自動で増えます。Excelテンプレート版は12行なので、足りない分は行をコピーして増やしてください(集計式も行ごとコピーされます)。
Q4. 希望シフトの収集や、スタッフへの共有も自動化できますか?
できます(Googleフォームでの希望収集→自動反映、確定シフトのメール送信など)。ただしお店ごとの運用に合わせた作り込みが必要になるため、この記事のコードには含めていません。下の相談窓口からどうぞ。
「うちの運用に合わせてほしい」はそのままご相談ください
希望シフトの収集、確定シフトの自動配信、勤怠・給与計算との連携——ここから先はお店ごとの運用に合わせた設計が必要な領域です。私はこうしたスプレッドシート×GASの小さな自動化から業務用Webアプリ、ソフトウェア開発まで受託で制作しています。「この作業、自動化できる?」の段階の相談で大丈夫です。相談・お見積もりは無料です。
📧 自動化の相談をする(無料)実際に公開・運用中のツールは 実績ページ でそのまま触れます。