「n8nの使い方入門:最初のワークフローを作って動かす」で、問い合わせをGoogleスプレッドシート「問い合わせ一覧」に1行足す「問い合わせ受付」を作りました。この記事では、そのシートを本番で使い続ける書き方を固めます。足す・読む・書き換える・消す操作を使い分け、やり直しや再送でも同じ問い合わせが2行入らない形にし、日付や電話番号が化けない設定と、Googleの上限に当たらない組み方まで入れます。前提は「問い合わせ一覧に追加」ノードが動いていること。版は2026年10月時点の安定版2.44.7(Google Sheetsノードは4.7)です。
この記事の内容
先に決めること
シートを記録に使うか、台帳に使うか
足していくだけの記録と、同じ行を書き換える台帳では組み方が違います。問い合わせ一覧は記録ですが、「対応状況」を書き換えた時点で台帳の性格も持ちます。先に、n8nが書き換えてよい列を決めます。問い合わせ一覧なら「対応状況」だけで、ほかの列は人が直さない約束にします。実際に本番で動かしていると、壊れるのはたいてい人とn8nが同じ列を書き換えているシートです。
行を何で見分けるか
行番号には頼りません。人が並べ替えたり行を挿入・削除したりすると、行番号はすぐ変わります。代わりに、問い合わせごとに決まって変わらない値を「受付ID」として列に持たせます。メールアドレスは使えません。同じ人が2回問い合わせると、2件目が1件目を上書きします。メールアドレスで照合してよいのは、1社1行の顧客台帳のような表だけです。
誰の権限で書くか
GoogleのCredential(認証情報)は、OAuth2(Googleアカウントでログインしてつなぐ)とサービスアカウント(Google Cloudに作るロボット用のアカウント)の2通りです。社員30名ほどでGoogle Workspaceなら、共用アカウント(automation@example.co.jp)のOAuth2で足ります。
値をGoogleに解釈させるか
ノードの「Cell Format」の初期値「Let Google Sheets format」は、手で打ったのと同じように値を解釈します。「Let n8n format」は送った値をそのまま入れます。問い合わせ一覧は後者にします。理由は「日付・数値・空欄の扱い」で書きます。
Credentialを作る:OAuth2とサービスアカウント
| 項目 | OAuth2 | サービスアカウント |
|---|---|---|
| 誰として書くか | ログインしたGoogleアカウント | サービスアカウント(〜@〜.iam.gserviceaccount.com) |
| ノードの「Authentication」 | 「OAuth2 (recommended)」 | 「Service Account」 |
| n8nのCredentialの種類 | Google Sheets OAuth2 API | Google Service Account API |
| n8n Cloudでの作り方 | 「Sign in with Google」でログインするだけ | Google Cloudで鍵を作って貼る |
| 触れるシート | そのアカウントが開けるシート全部 | 共有されたシートだけ |
| ファイルの置き場所 | マイドライブでも共有ドライブでも | 共有ドライブ |
| 止まるきっかけ | アカウントの停止・削除、ExternalかつTestingの状態なら7日で失効 | 鍵の削除、共有の解除 |
| シートの変更履歴に出る名前 | そのアカウント | サービスアカウントのメールアドレス |
公式ドキュメントもOAuth2を推奨しています。サービスアカウントを選ぶのは、共用アカウントを作れない社内規程があるときや、n8nが触れるシートを共有で1枚ずつ絞りたいときです。OAuth2の作り方と7日で切れる条件は「n8nの使い方入門:最初のワークフローを作って動かす」、共有の範囲や担当者が辞めたときの付け替えは「n8nの認証情報(Credential):OAuth2・APIキー・共有と権限」に書きます。ここではサービスアカウントだけ作ります。
サービスアカウントで作る
- Google Cloud Consoleで、n8n用のプロジェクトが選ばれていることを確かめる
- 「APIs & Services」の「Library」で Google Sheets API を探し、「ENABLE」を押す
- 同じ画面で Google Drive API も「ENABLE」にする
- 「APIs & Services」の「Credentials」で「+ Create credentials」から「Service account」を選ぶ
- 「Service account name」に
n8n-sheetsと入れ、「Create and continue」を押す - ロールは付けずに「Done」を押す
- できたサービスアカウントを開き、「Keys」タブで「Add key」から「Create new key」を選ぶ
- 「JSON」を選んで「CREATE」を押す。鍵のファイルがダウンロードされる
- n8nでGoogle Sheetsノードを開き、「Authentication」を「Service Account」にする
- Credentialの欄で「Create new credential」を選ぶ
- JSONの
client_emailの値を「Service Account Email」に貼る - JSONの
private_keyの値を、前後の引用符を除いて「Private Key」に貼り、「Save」を押す - Googleドライブで、問い合わせ一覧を置いた共有ドライブのメンバー管理を開き、
client_emailのアドレスを「投稿者」以上で追加する(ファイル単位の共有なら「編集者」)
「Region」などは初期値のままです。n8nの公式ドキュメントによると、2025年4月15日より後に作ったサービスアカウントはマイドライブにアクセスできず、共有ドライブだけを扱えます。ドメイン全体の委任で回避できますが公式も勧めていないので、共有ドライブへ移して共有します。JSONはシートを書き換えられる鍵なので、パスワードと同じ場所に保管します。

操作の一覧と使い分け
「Resource」は、シートの中を扱う「Sheet Within Document」と、ファイルそのものを作る・消す「Document」の2つです。普段使うのは前者の8つの操作です。
| 操作(Operation) | ノードパネルでの名前 | やること | 問い合わせ受付での使いどころ |
|---|---|---|---|
| Append Row | Append row in sheet | 末尾に行を足す | 照合する値を持てない記録 |
| Append or Update Row | Append or update row in sheet | 照合する列が同じ行があれば書き換え、無ければ足す | 問い合わせ一覧への書き込み(この記事の形) |
| Update Row | Update row in sheet | 照合する列が同じ行だけ書き換える。無ければ何もしない | 対応状況の書き換え |
| Get Row(s) | Get row(s) in sheet | 行を読む。「Filters」で絞れる | 未対応の件数、会社名で履歴を探す |
| Delete Rows or Columns | Delete rows or columns from sheet | 指定した行番号から行(または列)を消す | 原則使わない |
| Clear | Clear sheet | 中身を消す。シート(タブ)は残る | 毎回作り直す作業用シート |
| Create | Create sheet | シート(タブ)を作る | 年ごとの記録シート |
| Delete | Delete sheet | シート(タブ)を丸ごと消す | 使わない |
一番間違えやすいのが「Delete」で、行ではなくシートそのものを消します。
「Append Row」は、やり直しや再送のたびに行が増えるので、照合する値を作れないときだけ使います。

手順:重複しない「問い合わせ受付」にする
準備:受付IDの列を足す
- 「問い合わせ一覧」を開き、H1のセルに「受付ID」と入れる
- 見出しの前後に空白が無いことを確かめる(n8nは空白まで区別して読む)
書き込みを Append or Update Row に替える
- 「問い合わせ一覧に追加」を開く
- 「Operation」を「Append or Update Row」に替える
- 「Mapping Column Mode」をいったん「Map Automatically」にしてから「Map Each Column Manually」に戻し、H列の「受付ID」を読み込ませる
- 「Column to match on」で「受付ID」を選ぶ
- 「Values to Send」の「受付ID」に下の式を入れる
- 「受付日時」の式を下のものに替える
- ほかの6列に入門の記事と同じ値が入っていることを確かめる
- 「Add option」から「Cell Format」を足し、「Let n8n format」を選ぶ
- 「Add option」から「Minimise API Calls」を足してオンにする
- 問い合わせフォームのノードにピン留めしたデータがある状態で、「Execute step」を2回押す
- シートの行が1行だけ増え、2回目で増えていないことを確かめる
受付ID:
{{ $json.submittedAt }}_{{ $json.email }}
受付日時:
{{ DateTime.fromISO($json.submittedAt).toFormat('yyyy-MM-dd HH:mm') }}
受付IDは、ミリ秒まで入った送信日時とメールアドレスをつないだ値で、2026-10-26T14:05:12.345+09:00_yamada@example.com のようになります。同じ送信からは何度作っても同じ値、別の送信なら違う値になるのが条件です。
実行の番号 $execution.id は使いません。失敗した実行をやり直すと番号が変わり、同じ問い合わせが別のIDで2行目に入ります。受付日時を $now から作るのをやめたのも同じ理由です。自社サイトのフォームが送信ごとの番号を送ってくれるなら、その番号を受付IDにするのが一番確実です。照合に使った列は「(using to match)」と表示され、書き換えの対象から外れます。

1回の実行の中の重複を落とす
Append or Update Row は、実行の最初にシートを1回だけ読み、その時点の受付IDと照らし合わせます。同じ実行の中に、シートにまだ無い同じ受付IDが2件あると、どちらも「無い」と判定されて2行足されます。過去の問い合わせをCSVからまとめて入れるときや、Webhookで一度に複数件届くときに起きます。
- Append or Update Row の手前の線の + を押し、「Remove Duplicates」を足す
- 「Operation」は「Remove Items Repeated Within Current Input」のまま
- 「Compare」を「Selected Fields」にする
- 「Fields To Compare」に
submittedAt, emailと入れる
送り元が数秒おきに再送してくるサービスでは、別々の実行がほぼ同時にシートを読むので、シートの照合だけでは防ぎきれません。そのときは「Operation」を「Remove Items Processed in Previous Executions」にし、「Keep Items Where」を「Value Is New」、「Value to Dedupe On」に受付IDの式を入れます。通した値はn8nのデータベースに残り、「History Size」の初期値は10,000件です。

対応状況を書き換える(Update Row)
受付IDと新しい対応状況が前のノードから届く例です。Slackのボタンで「対応済み」を押す作り方は「n8nとSlack連携:通知・投稿・ボタンで承認・スラッシュコマンド」にあります。
- Google Sheetsノードを足し、「Update row in sheet」を選ぶ
- 「Document」と「Sheet」は「From list」で問い合わせ一覧とシート1を選ぶ
- 「Mapping Column Mode」は「Map Each Column Manually」
- 「Column to match on」で「受付ID」を選ぶ
- 「Values to Update」の「受付ID」に
{{ $json.receptionId }}、「対応状況」に{{ $json.status }}を入れる - ほかの6列は、欄の右のごみ箱のボタンで外す
6は飛ばせません。ノードの版4.7では、空のまま残した欄は空欄として書き込まれ、その行の会社名や内容が消えます。
「Column to match on」には見出しに無い row_number(n8nが付ける行番号)も出ますが、読んでから書くまでに人が並べ替えると別の行を書き換えるので、受付IDがある表では使いません。「Map Each Column Manually」のとき、照合する行が無いとUpdate Rowは何もせず、後ろのノードも動きません。書き換わるのは上から最初の1行だけです。

読み出す(Get Row(s))
毎朝「未対応」の件数をSlackに送る例です。毎朝決まった時刻に動かす設定は「n8nの定期実行とサブワークフロー:Schedule Triggerと部品化」に書きます。
- Google Sheetsノードを足し、「Get row(s) in sheet」を選ぶ
- 「Document」と「Sheet」に問い合わせ一覧とシート1を選ぶ
- 「Filters」の「Add Filter」を押し、「Column」を「対応状況」、「Value」を「未対応」にする
- ノード名を「未対応を読む」に変える
- 「Settings」で「Always Output Data」をオンにする
- 後ろのSlackノードの「Settings」で「Execute Once」をオンにし、本文に下の式を入れる
未対応の問い合わせ: {{ $('未対応を読む').all().filter(i => i.json.row_number).length }}件
読んだ行は1行が1件のitemになります。row_number はn8nが付ける行番号で、データは2から始まります。
[
{
"row_number": 5,
"受付日時": "2026-10-26 14:05",
"会社名": "株式会社サンプル",
"お名前": "山田 太郎",
"メールアドレス": "yamada@example.com",
"種類": "お見積もり",
"内容": "料金表を送ってください",
"対応状況": "未対応",
"受付ID": "2026-10-26T14:05:12.345+09:00_yamada@example.com"
}
]
「Always Output Data」がオフだと、未対応が0件の朝は出力が空になり、Slackが動かずに0件の知らせも届きません。オンにすると空のitemが1件出るので、件数は row_number を持つitemだけ数えています。
Filtersは空白まで区別する完全一致です。条件を2つ以上足すと「Combine Filters」の初期値「AND」で全部満たす行になります。4.7では一致した行を全部返し、先頭の1行だけなら「Return only First Matching Row」をオンにします。「〜を含む」は全部読んで「Filter」ノードで絞ります。


行を消す
行は消さずに、対応状況を「迷惑」「重複」のように書き換えて残すのを基本にします。どうしても消すときは「Delete Rows or Columns」で、「To Delete」を「Rows」、「Start Row Number」に消す行の番号、「Number of Rows to Delete」に行数を入れます。複数の行を1行ずつ消すときは必ず下から消します。上から消すと下の行番号が繰り上がり、別の行を消します。
日付・数値・空欄の扱い
同じ値を送っても、「Cell Format」でシートに入る値が変わります。
| 送る値 | Let Google Sheets format(初期値) | Let n8n format |
|---|---|---|
2026-10-26 14:05 | 日時の値になり、表示は列の表示形式とシートの地域の設定で決まる | その文字のまま |
0312345678(電話番号) | 数値になり、先頭の0が消える | その文字のまま |
= で始まる問い合わせ本文 | 数式として計算される | その文字のまま |
式の結果が数値の 1000 | 数値 | 数値 |
| 空の値 | 空欄 | 空欄 |
「Let n8n format」を選んだのは2行目と3行目のためです。0が消えた電話番号にはかけ直せず、誰でも書けるフォームの本文が数式として動くのも避けたいからです。受付日時は yyyy-MM-dd HH:mm の文字で入るので、並べ替えれば日時の順になり、2026-10 で始まる行を数えれば月の件数になります。
フォームから届く数値は文字なので、「Let n8n format」では文字のまま入り、SUMで足されません。数値の列は {{ Number($json.employees) }} のように直して送ります。
読む側の初期値は、Get Row(s)の「Output Formatting」で「General Formatting」が「Values (unformatted)」(数値は数値のまま、カンマや通貨記号は外れる)、「Date Formatting」が「Formatted Text」(見えているとおりの文字)です。「Serial Number」にすると、日付は1899年12月30日からの日数(2026年10月26日なら46321)で返ります。
困るのは、人が手で日付を打つ列です。「Formatted Text」はシートの地域の設定と表示形式どおりの文字で返すので、誰かが設定を変えた日からn8n側の読み取りが合わなくなります。その列は「表示形式」→「数字」→「カスタム日時」で yyyy-mm-dd の形に固定します。読んだ文字をLuxonで日時に戻す書き方は「n8nの式(Expression)とデータの扱い:$json・ノードの参照・日付・JSON」にまとめます。
空欄にも癖があります。Get Row(s)では、行の途中の空欄は空の文字 "" で返りますが、行の右端から続く空欄は項目そのものが出てきません。対応状況が右端の列で空の行では、$json['対応状況'] が undefined になり、IFノードの比較が思った方向に倒れません。式では {{ $json['対応状況'] ?? '' }} と書くか、右端に必ず値が入る列(この記事なら受付ID)を置きます。
APIの上限に当たらない書き方
Google Sheets APIの上限は1分あたりの回数で、読み出しと書き込みそれぞれ、プロジェクト全体で1分300回、1ユーザーあたり1分60回です。超えると 429: Too many requests が返り、1分ごとに回数が戻ります。1日の上限はありません。共用アカウント1つでつないでいると、その60回を全ワークフローで分け合います。
Google Sheetsノードは、itemが1件でも100件でも1回の実行でまとめて送ります。2.44.7のソースでは、Append Rowはシートを1回読んで書き込みを2回(「Minimise API Calls」がオンなら1回)、Update Rowは読み1回と書き込み1回です。上限に当たるのは、ほとんどが1件ずつ流す組み方をしたときです。
| やり方 | 効く場面 | 設定 |
|---|---|---|
| まとめて1回で書く | CSVからの取り込み、毎朝の一括更新 | Loop Over Itemsで1件ずつにせず、全部のitemをそのままGoogle Sheetsノードへ渡す |
| 読むのを1回にする | Get Row(s)の前のノードから複数件のitemが来る | Get Row(s)の「Settings」で「Execute Once」をオン |
| 書き込みの回数を減らす | 途中に空行・空列が無い記録用のシート | 「Add option」から「Minimise API Calls」をオン |
| 待ちを入れる | 小分けにしないと書けない処理 | 「Loop Over Items」の「Batch Size」を50にし、ループの最後に「Wait」で60秒待つ |
| 失敗したらやり直す | ときどき429が出る | 「Settings」の「Retry On Fail」をオン、「Max. Tries」を5、「Wait Between Tries (ms)」を5000 |
Get Row(s)は届いたitemの数だけシートを読むので、10件届けば10回読みます。
「Retry On Fail」は待ち最大5秒・最大5回で、1分単位の上限を待ちきれないことがあります。件数の多い処理は「待ちを入れる」の形にし、それでも落ちた実行は「n8nを止めずに運用する:アップデート・バックアップ・エラー通知・脆弱性」のエラー通知で拾います。
「Minimise API Calls」は上限対策以外にも効きます。オフのとき、n8nはシートを読んで「最後の行の次」の行番号を決めてから書くので、ほぼ同時に2件届くと同じ行を選び、後の1件が前の1件を上書きすることがあります。オンにするとGoogle側の追記の仕組みで末尾に足すので、この取り合いが起きません。途中に空行や空列があると位置がずれるので、記録用のシートは隙間を作らない運用にします。
シートを台帳にしすぎない判断
スプレッドシートは1ファイル2,000万セルまたは100MBまで入りますが、n8nで困るのはずっと手前です。Append RowもAppend or Update Rowも書く前にシート全体を読むので、行が増えるほど実行が重くなります。問い合わせが1日数件なら年に千数百行で、数年はシートのままで困りません。運用しているn8nで移したのは、次のどれかが起きたときです。
| 起きていること | 移す先 |
|---|---|
| 同じ行を3人以上が書き換え、誰がいつ直したか追えない | kintone・Salesforce(変更の履歴と権限が残る) |
| 1件の問い合わせに対応の記録が何件も付く | kintone・Salesforce(親子の関係を持てる) |
| 行ごとに見せる人を分けたい | kintone・Salesforce |
| n8nの中だけで使う照合用の値(処理済みのIDなど) | n8nのData tables |
| 行が数万になり、1回の実行に時間がかかる | 年ごとに別のシートへ移す。集計が要るならデータベース |
Data tablesはn8nの中にある表で、「Data tables」タブから作ります。Data Tableノードに「Upsert row(s)」や「If row does not exist」があり、照合用に向いています。上限は初期値でインスタンス全体200MiB、セルフホストは N8N_DATA_TABLES_MAX_SIZE_BYTES で変えられます。
顧客管理へ入れる手順は「n8nとkintone・Salesforce・HubSpot連携:問い合わせを顧客管理に入れる」で書きます。
応用
年ごとにシートを分ける
「Sheet」を「By Name」にして {{ $now.toFormat('yyyy') }}年 と書けば、「2026年」のシートに入ります。シートは「Create」で年明けに作っておきます。Sheetの式は最初のitemで1回だけ計算され、残りのitemも同じシートに入るので、年をまたぐ複数件は年ごとに分けてから別々のノードで書きます。
シートの変更をきっかけに動かす
「Google Sheets Trigger」を使い、「Trigger On」で「Row Added」「Row Updated」「Row Added or Updated」から選びます。Credentialは書き込み用とは別の「Google Sheets Trigger OAuth2 API」です。一定の間隔で前回と比べる仕組みなので、編集した瞬間には動きません。日時が数値で届くときは「DateTime Render」を「Formatted String」にします。
ノードに無い操作をする
セルの色付け、並べ替え、行の挿入はノードにありません。HTTP Requestノードで「Authentication」を「Predefined Credential Type」にし、Google Sheets OAuth2 APIのCredentialを選べば、Google Sheets APIを直接呼べます。HTTP Requestの基本は「n8nで外部サービスとつなぐ:Webhook・HTTP Request・API」に書きました。
できないことと、その代わり
| できないこと | 代わりの手 |
|---|---|
| 2つ以上の列の組み合わせで照合する(4.7の照合は1列) | 組み合わせた値を1列(受付IDなど)に入れて照合する |
| 部分一致や大小の比較で読む | 全部読んで「Filter」ノードで絞る |
| 同じ値の行をまとめて書き換える | 書き換わるのは最初の1行だけ。同じ値の行を作らない設計にする |
| 書き込み中にほかの実行を待たせる(ロック) | 同時の書き込みが多い表はkintone・Salesforce・Data tablesへ |
| 人の編集の瞬間に動かす | Google Apps Scriptのインストール型の編集トリガーからWebhookを呼ぶ |
| 2025年4月15日より後に作ったサービスアカウントでマイドライブのファイルを使う | 共有ドライブに置く |
よく詰まるところ
| 症状 | 原因 | 直し方 |
|---|---|---|
| 同じ問い合わせが2行入る | Append Rowで、やり直しや再送のたびに足している | Append or Update Rowにし、受付IDで照合する |
| Append or Update Rowなのに2行入る | 同じ実行の中に同じ受付IDが2件ある。またはメールの大文字・小文字や空白で値が揺れている | 手前にRemove Duplicates。照合する値は .trim().toLowerCase() で揃える |
| 照合する列が数値で、毎回行が増える | Append or Update Rowはシートに見えている文字(例: 1,234)と送った値(1234)を比べる | 照合する列は文字の値にし、「Let n8n format」で書く |
| Update Rowのあと、会社名や内容が消えた | 4.7では空の欄が空欄として書き込まれる | 書き換えない列は欄ごと外す |
| 「Column names were updated after the node’s setup」 | ノードを作ったあとで見出しを変えた | 「Mapping Column Mode」を選び直して列を読み込み直す |
| 送っていない列(submittedAt・formModeなど)が勝手に増えた | 「Map Automatically」で、「Handling extra fields in input」が初期値の「Insert in New Column(s)」 | 「Ignore Them」にする |
| 日付が46321のような数値で届く | 「Date Formatting」が「Serial Number」、またはトリガーの日時の表示が数値 | 「Formatted Text」にする。トリガーは「DateTime Render」を「Formatted String」に |
対応状況が空の行で、式が undefined になる | 右端から続く空欄は項目ごと出てこない | ?? '' を付けるか、右端に必ず値が入る列を置く |
| 429、または「The service is receiving too many requests from you」 | 1ユーザー1分60回の上限 | まとめて書く、Get Row(s)を「Execute Once」に、Loop Over ItemsとWaitで待つ |
| サービスアカウントで「Document」の一覧に出ない、権限エラーになる | 共有していない、またはマイドライブに置いている | 共有ドライブに置き、client_email を「投稿者」以上で追加する |
| 「Delete」でシートが丸ごと消えた | 「Delete」は行ではなくシートを消す操作 | スプレッドシートの「ファイル」→「変更履歴」から戻す。行は「Delete Rows or Columns」で消す |
次にやること
- n8nの認証情報(Credential):OAuth2・APIキー・共有と権限
- n8nの式(Expression)とデータの扱い:$json・ノードの参照・日付・JSON
- n8nとkintone・Salesforce・HubSpot連携:問い合わせを顧客管理に入れる
- n8nの使い方入門:最初のワークフローを作って動かす