シフト表の自動作成:スプレッドシート×GASで曜日・集計・人数不足チェックまで【コピペコード・無料Excelテンプレつき】

毎月のシフト表づくりは、地味に時間を食う仕事です。先月の表をコピーして日付と曜日を直し、スタッフの希望を埋めて、「この日、早番が足りてない」を目で数えて探す——1回あたり1〜2時間、月に必ず発生します。しかも曜日のずれや人数の数え間違いは、そのまま現場の混乱になります。

このページでは、シフト表づくりを楽にする方法を2段階で紹介します。①今日から使える無料のExcelテンプレート(集計式・プルダウンつき)、②ボタン一つでその月のシフト表が丸ごと生成されるGAS(Google Apps Script)。GASのコードは全文コピペできる形で載せています。

私はAIを活用してこうした業務自動化ツールを作っています。この記事のコードとテンプレートは、公開前に実際にGoogleスプレッドシート上で動かして動作確認済みです(2026年7月)。

完成イメージ:自動生成されたシフト表

下の画像は、この記事のGASをスプレッドシートに貼って、メニューから「今月のシフト表を作る」を1回押した結果です(実際の動作確認時のスクリーンショット)。

GASで自動生成されたシフト表。曜日入りヘッダー、土日の色分け、プルダウン、日別の出勤合計と人数不足の赤色表示

▲ 1〜31日の日付と曜日・土日の色分け・記号のプルダウン・集計式まで、すべて自動で入ります。最下段の「出勤合計」が設定人数を下回る日は自動で赤くなります。

手でやると1時間かかる「月替わりの表の作り直し」が数秒になり、さらに:

Excelテンプレ派とGAS派、どちらを選ぶか

①Excelテンプレート②GAS(スプレッドシート)
向いている人まず今月分をすぐ整えたい/Excelで管理したい毎月の表づくり自体をなくしたい/Googleスプレッドシート派
できること記号を入れるだけで集計(出勤・休・有休・日別人数)左に加えて、月替わりの表の生成・曜日・土日色分け・人数不足の警告まで自動
導入の手間ダウンロードするだけコードを1回コピペ(5分)

迷ったら、まず①のテンプレートで運用を始めて、「月初の表づくりが面倒だ」と感じたら②に進むのがおすすめです。管理する項目はどちらも同じなので、あとから乗り換えても運用は変わりません。

①無料Excelテンプレートのダウンロード

縦=名前(12人分)× 横=1〜31日のマトリクスに、記号(=早番/=日勤/=遅番/=休み/=有休)をプルダウンで入れるだけのテンプレートです。無料・登録不要で、そのまま使えます。

📥 シフト表テンプレートをダウンロード(Excel・無料)

.xlsx形式(約14KB)。使用関数はCOUNTIFとIFのみで、Googleスプレッドシートにインポートしてもプルダウン・自動集計ごと動きます(動作確認済み)。A4横向きの印刷設定済み。個人・自社の業務での利用は自由です。

中身は3シートです。

記号を入れると、右端に各人の出勤日数・休・有休、最下段に早番/日勤/遅番の人数と出勤合計が自動で出ます。「休み」と「シフトなし」を区別したいときは、休みの日だけ「休」を入れ、そもそもシフトが無い日(パートの非稼働日など)は空欄にしておくのがコツです。

②GASでシフト表の作成まで自動化する(コピペコード)

テンプレートでも集計は自動になりますが、月が替わるたびの「表の作り直し」——日数の違い、曜日のずれ、土日の色分け——は残ります。ここを消すのがGASです。導入は次の5ステップ、1回だけです。

  1. Googleスプレッドシートを新規作成する(sheets.new をブラウザで開くと早いです)
  2. メニューの拡張機能 → Apps Script を開く
  3. エディタの中身を全部消して、下のコードを貼り付ける
  4. 上部の保存アイコン(またはCtrl+S)で保存する
  5. スプレッドシートのタブに戻って再読み込みする → メニューに「シフト表」が増えます

あとは シフト表 → 今月のシフト表を作る(または「来月」)を押すだけです。初回だけ承認画面が出ます(つまづきやすいポイント参照)。

/**
 * 月間シフト表の自動作成+集計(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_STAFF1日に最低必要な出勤人数この人数を下回る日の「出勤合計」が赤くなります

変更を保存したら、翌月分から新しい設定で生成されます。すでに作ったシートはそのまま残るので、月の途中で設定を変えても過去の表は壊れません。

つまづきやすいポイント

初回の「承認が必要です」画面

初回実行時はGoogleの承認画面が出ます。「詳細」→「(プロジェクト名)に移動」から進んで許可してください。このコードが要求する権限はスプレッドシートの操作のみで、外部への送信処理はありません(貼り付けたコードがすべてなので、気になる場合は上のコードを確認してからで大丈夫です)。

記号を変えたら「集計に出ない」

集計は記号の文字そのものを数えています(COUNTIF)。SHIFT_CODES を変えたら WORK_CODES も必ずセットで見直してください。Excelテンプレート版も同じで、プルダウンと集計式の記号を揃える必要があります。

「シートが既にあります」と出る

同じ月のシートは二重に作らない仕様です。作り直したいときは、先にそのシートを削除(またはシート名を変更)してから実行してください。

祝日の色分けは入っていません

土日は自動で色分けされますが、祝日はお店ごとに扱いが違う(祝日も通常営業のお店が多い)ため、あえて自動化していません。必要なら該当列に手動で色を付けるのが早いです。

ご注意: この表は「誰をどの日にどのシフトへ割り当てるか」を管理するためのものです。労働時間・休憩・休日・割増賃金など労働基準法上の管理(勤怠管理)はこの表だけではできません。出退勤時刻の記録は別途行ってください。

よくある質問

Q1. 承認画面が不安です。データはどこかに送られますか?

送られません。このスクリプトは自分のアカウント内のスプレッドシートだけを操作します。要求される権限もスプレッドシートの操作のみです。コードは上に全文載せているので、確認してから使えます。

Q2. Excelだけで使いたいのですが。

①のテンプレートをそのままExcelでお使いください。集計とプルダウンはExcelでも全部動きます。GoogleスプレッドシートにインポートしてもOKです。

Q3. スタッフが20人います。使えますか?

使えます。GAS版は STAFF に人数分の名前を並べるだけで、行数は自動で増えます。Excelテンプレート版は12行なので、足りない分は行をコピーして増やしてください(集計式も行ごとコピーされます)。

Q4. 希望シフトの収集や、スタッフへの共有も自動化できますか?

できます(Googleフォームでの希望収集→自動反映、確定シフトのメール送信など)。ただしお店ごとの運用に合わせた作り込みが必要になるため、この記事のコードには含めていません。下の相談窓口からどうぞ。

「うちの運用に合わせてほしい」はそのままご相談ください

希望シフトの収集、確定シフトの自動配信、勤怠・給与計算との連携——ここから先はお店ごとの運用に合わせた設計が必要な領域です。私はこうしたスプレッドシート×GASの小さな自動化から業務用Webアプリ、ソフトウェア開発まで受託で制作しています。「この作業、自動化できる?」の段階の相談で大丈夫です。相談・お見積もりは無料です。

📧 自動化の相談をする(無料)

実際に公開・運用中のツールは 実績ページ でそのまま触れます。

関連記事