「今月、口座にいくら残ってる?」と聞かれて、銀行アプリを3つ開き直した経験はありませんか。
家計の数字が自分のスマホの中にしかないと、家族と話すたびに私が読み上げ係になります。
そこで2026年8月に、Google Apps Script(GAS)で家計ダッシュボードを作りました。口座残高・支出の内訳・水道料金の推移を1枚の画面にまとめ、家族のGoogleアカウントでだけ開けるようにしています。
作り方と、共有の設定でうっかり「リンクを知っている人なら誰でも見られる」状態にしていた失敗まで、そのまま書きます。
この記事でわかること
- GASの家計ダッシュボードの全体構成(JSON → Code.gs → Webアプリ)
- doGet と google.script.run で画面とデータをつなぐ4ステップ
- 家族だけに見せるデプロイ設定(USER_ACCESSING + メールの許可リスト)
- iframe・アクセス設定・画像まわりでハマった3つの落とし穴
PARTS LIST /目次
GASの家計ダッシュボードとは?スプシ家計簿との違い
GASの家計ダッシュボードとは、Googleのサーバー上で動くWebアプリとして家計の数字を表示し、URLを開くだけで家族が同じ画面を見られる仕組みです。
スプレッドシートの家計簿との違いは「見る人が触れない」点にあります。表計算のセルが見えていると、うっかり数式を消されたり、自分でも列の意味を忘れたりします。見るための画面と、入力するためのデータを切り分けたのが今回の作りです。
家計ダッシュボードの定義と、私が作った画面
私の家計ダッシュボードは、1つのURLに次のパネルを並べています。
- 口座残高(銀行口座3つと証券口座1つ)
- 収入と支出の月間目安(支出は16カテゴリ)
- 水道料金の推移(2か月ごとの請求額)
- 普通預金の月ごとの残高、入金と出金、日次の収支(実績+予定)
開いたときの画面がこちらです(金額と口座名は、記事用に差し替えた架空のサンプルです)。

一番上に総資産と月の収支の目安、その下に口座ごとのカード、さらに収入と支出の横棒グラフが続きます。家族が最初に知りたい「いま全部でいくらあるか」を、スクロールせずに見られる位置に置きました。
ほかに、URLの末尾に ?page=loan を付けると住宅ローンの繰上返済シミュレーション、?page=album で家族のアルバムが開きます。画面の本体 index.html はおよそ1,050行。グラフ描画のライブラリは使っていません。
スプレッドシート共有ではなくWebアプリにした理由
最初に考えたのは、スプレッドシートを家族に共有するだけの案でした。手軽さでいえば、こちらが圧倒的に上です。
ただ、私の場合は口座残高の入力をClaude Codeに任せたかった。証券口座の残高を公式に取れるAPIがないので、更新はどうしても手作業になります。それなら「水道の請求が来た、金額はこれ」と話しかけてテキストファイルを書き換えてもらうほうが、ブラウザでセルを探すより速い。
スプレッドシートを共有
すぐに始められる。ただし家族も編集できてしまい、グラフの見た目はシートの機能の範囲まで。
GASのWebアプリ
最初の準備に手間がかかる。そのかわり見る人は触れず、データはテキストのまま手元で管理できる。
見るだけの家族に編集権限は要りません。この判断が、後半のアクセス設定の話につながります。
全体の構成:JSON→Code.gs→Webアプリの流れ
構成は「手元のJSON → 生成した Code.gs → GASのWebアプリ」の一方通行です。スプレッドシートもデータベースも使っていません。
金額を書き換えるのは常に手元のJSONで、GASの中のコードは毎回それから作り直します。流れを図にすると、こうなります。
flowchart LR
A["data/*.json<br>金額の唯一の情報源"] -->|"build_code_gs.py"| B["Code.gs<br>データを埋め込んだ関数"]
B -->|"clasp push"| C["GASプロジェクト"]
C -->|"clasp deploy -i"| D["Webアプリ<br>/exec のURL"]
D -->|"google.script.run"| E["index.html<br>家族のスマホ"]
金額は手元のJSONを唯一の情報源にする
金額のデータは data/ フォルダのJSONファイルに分けています。口座残高と支出、普通預金の日次明細、水道料金、住宅ローンの契約内容、といった具合です。
明細の1件はこんな形です(値はダミー)。
{ "date": "2026-10-25", "memo": "給与", "delta": 250000, "balance": 812000, "actual": true }
actual が false の行は、引き落とし予定などの未確定分。実績と予定を同じ配列に入れておくと、グラフ上で「この先いくらまで減るか」を点線で見せられます。
Python のスクリプト(build_code_gs.py)がこれらを読み、GASの関数の中に var data = … の形で埋め込んだ Code.gs を書き出します。生成後の Code.gs は約310KB。人が開くファイルではないので、冒頭に「自動生成。直接編集しない」と書いてあります。
clasp 3.3.0でローカル開発する準備
GASのコードをブラウザのエディタではなく手元で書くために、Google公式のCLIツール clasp を使います。動作を確認した版は clasp 3.3.0 です。
step
1clasp を入れてログインする
npm install -g @google/clasp のあと clasp login でGoogleアカウントを認可します。
step
2GASのプロジェクトを作る
フォルダの中で clasp create --type standalone --title "household-dashboard" --rootDir . を実行すると、.clasp.json にスクリプトIDが書かれます。
step
3.clasp.json を Git に入れない
スクリプトIDは自分のプロジェクトを指すので、.gitignore に入れ、ダミー値の .clasp.json.example だけを残しておきます。
GAS家計ダッシュボードの作り方4ステップ
GASのWebアプリは、HTMLを返す doGet と、画面から呼ばれるデータ用の関数の2つがあれば動きます。
公式のガイドでも、Webアプリの条件は「doGet か doPost があり、HtmlOutput か TextOutput を返すこと」だけです。ここに、グラフの描画とローカル確認用のダミーデータを足した4ステップで組み立てます。
doGetでHTMLを返す
doGet はURLを開いたときに呼ばれる関数です。?page=loan のような引数で、返すHTMLファイルを切り替えています。
var PAGE_FILES = { loan: 'loan-simulation', album: 'album' };
function doGet(e) {
var page = e && e.parameter && e.parameter.page;
var file = PAGE_FILES[page] || 'index';
return HtmlService.createHtmlOutputFromFile(file)
.setTitle('家計ダッシュボード')
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
}
最後の addMetaTag は、私の版では入れ忘れていた1行です。GASではHTMLファイルに直接書いた meta タグが無視される仕様で、スマホ表示用の viewport はここで付けるしかありません。
google.script.runで金額データを渡す
画面側のJavaScriptから、サーバー側の関数を呼ぶ仕組みが google.script.run です。成功時と失敗時の処理を登録してから関数名を呼びます。
google.script.run
.withSuccessHandler(json => render(JSON.parse(json)))
.withFailureHandler(error => showFallback())
.getDashboardData();
サーバー側の getDashboardData() は、埋め込んだデータを JSON.stringify して返すだけ。オブジェクトのまま返さず文字列にしているのは、google.script.run が Date 型などの値を受け渡せないためです。文字列なら、どんな形のデータでも確実に届きます。
グラフはライブラリなしのSVGで描く
残高の推移や水道料金の棒グラフは、<svg> 要素に線や長方形をJavaScriptで足して描いています。Chart.js などのライブラリは入れていません。
理由は2つです。GASのHTMLは1ファイルに収めるのが楽なこと。それと、家計のグラフは折れ線と棒の2種類で足りること。マウスを載せたときの吹き出し(ツールチップ)も自前で、日付・金額・メモを出しています。

水道料金のパネルは、2か月ごとの請求額を棒で並べ、平均を点線で重ねています。最新の請求だけ濃い色にしているので、「今回は高いのか」がひと目でわかります。

月次の残高は、2つの口座を積み上げた棒グラフです。最後の月の薄い色は、まだ記帳されていない口座の残高を前月から繰り越した分。実績と推定を色の濃さで分けておくと、家族に「この数字はまだ確定じゃない」と説明する手間が省けます。
ローカルではダミーデータで動かす
index.html をブラウザで直接開いても動くように、google.script.run が無い環境ではダミーデータで描画します。
if (window.google && google.script && google.script.run) {
google.script.run.withSuccessHandler(/* 本番のデータ */).getDashboardData();
} else {
render(getDummyData());
}
見た目の調整のたびにデプロイしなくて済み、グラフの細かな位置合わせもブラウザの再読み込みだけで確かめられます。本番でデータ取得に失敗したときも同じダミーに切り替え、「仮表示中」の帯を出します。
家族と共有するデプロイ設定|誰に見せるかを決める
家族と共有するときに一番気を使うのは、コードではなくデプロイ時の「誰として実行するか」と「誰がアクセスできるか」の2つの設定です。
家計のURLは、口座残高をそのまま載せた紙を玄関に貼るのと同じ重さがあります。私はここを一度間違えました。
「アクセスできるユーザー」4つの選択肢
設定は appsscript.json の webapp に書きます。公式のマニフェストのリファレンスにある選択肢は次の4つです。
もう1つの executeAs は、USER_DEPLOYING(自分として実行)か USER_ACCESSING(開いた人として実行)かを選びます。
どれを選ぶかは、見せる相手がGoogleアカウントを持っているかどうかで、ほぼ決まります。迷ったときの選び方を図にしました。
flowchart TD
A["家計ダッシュボードを<br>誰に見せる?"] --> B{"自分だけ?"}
B -->|"はい"| C["access: MYSELF"]
B -->|"いいえ"| D{"家族全員が<br>Googleアカウントを持っている?"}
D -->|"はい"| E["access: ANYONE<br>executeAs: USER_ACCESSING<br>+メールの許可リスト"]
D -->|"いいえ"| F["アカウントを作ってもらう<br>(ANYONE_ANONYMOUS は避ける)"]
F --> E
公開した直後の設定は ANYONE_ANONYMOUS でした。家族にURLを送れば、ログインなしでそのまま開ける設定です。
ただ、このURLは家族とのやりとりの中に残り続けます。誰かに転送された時点で、口座残高も住宅ローンの残りも丸見えになる。そこで、家族のGoogleアカウントだけが開ける設定に切り替えました。
メールの許可リストで家族だけに絞る
いまの設定は access: ANYONE と executeAs: USER_ACCESSING の組み合わせです。開いた人のアカウントで動くので、Session.getActiveUser().getEmail() でその人のメールアドレスが取れます。
var ALLOWED_EMAILS = ['[email protected]', '[email protected]'];
function isAllowedUser() {
var email = (Session.getActiveUser().getEmail() || '').toLowerCase();
return email !== '' && ALLOWED_EMAILS.indexOf(email) !== -1;
}
function getDashboardData() {
if (!isAllowedUser()) throw new Error('アクセス権限がありません');
return JSON.stringify(DATA);
}
チェックは doGet だけでなく、データを返す関数ごとに入れています。google.script.run から呼べる関数は、画面を経由せずに直接呼ぶこともできるからです。
URLを開いてから金額が表示されるまでに、許可リストの確認は2回入ります。
sequenceDiagram
participant U as 家族のブラウザ
participant G as GAS(doGet)
participant F as getDashboardData
U->>G: URLを開く(Googleにログイン済み)
G->>G: isAllowedUser()でメールを確認
alt 許可リストにない
G-->>U: 「表示する権限がありません」
else 許可リストにある
G-->>U: index.html を返す
U->>F: google.script.run で呼ぶ
F->>F: もう一度メールを確認
F-->>U: 金額のJSON(文字列)
end
許可リストにないアカウントで開くと、金額の代わりに次の1行だけが表示されます。

メールが空のときは拒否する
公式のリファレンスでは、セキュリティポリシーで利用者の情報にアクセスできない場合、getEmail() は空の文字列を返すとされています。空なら許可しない、と明示しておくと安全側に倒れます。
家族は初回だけ、Googleの承認画面でアプリの実行を許可します。2回目以降はURLを開くだけです。
URLを変えずに更新する clasp deploy -i
金額を変えたら、Code.gs を作り直して push し、同じデプロイIDを指定してデプロイします。
python3 scripts/build_code_gs.py
clasp push --force
clasp deploy -i <デプロイID> -d "10月の残高を反映"
-i を付けずに clasp deploy すると新しいデプロイが増え、URLも変わります。家族に送ったURLを使い続けたいなら、デプロイIDの指定は外せません。
GASダッシュボードでハマった3つの落とし穴
3つとも、コードの書き間違いではなく「GASのWebアプリがどこでどう動いているか」を知らなかったことが原因でした。
ローカルの index.html では再現しないので、デプロイして初めて気づきます。順に書いていきます。
リンクで別ページに飛べない(iframeのsandbox)
ダッシュボードからローン画面へのリンクを <a href="?page=loan"> で置いたところ、クリックのされ方によっては遷移しないことがありました。GASのWebアプリは sandbox 化された iframe の中で動いていて、トップのページを移動する操作に制限がかかるためです。
いまは、遷移先のHTMLを google.script.run で文字列として取ってきて、document.write で画面を丸ごと差し替えています。力技ですが、確実に動きます。
sequenceDiagram
participant P as 画面(iframe内)
participant G as GAS(getPageHtml)
P->>G: google.script.run.getPageHtml("loan")
G-->>P: loan-simulation.html の中身(文字列)
P->>P: document.open() → write() → close()
Note over P: URLは変わらず、画面だけが入れ替わる
副作用として、ブラウザの「戻る」では前の画面に戻れません。私は各ページの先頭に、ダッシュボードへ戻るリンクを置いて対処しています。
ANYONEとANYONE_ANONYMOUSの取り違え
管理画面の日本語表記だけを見ると、どちらが「ログイン必須」なのか一瞬わかりません。
家族だけに見せたいときに ANYONE_ANONYMOUS を選ぶと、許可リストのコードを書いても意味がなくなります。ログインしていない人のメールアドレスは取れないからです。appsscript.json の値を目で確かめてからデプロイしてください。
私の手元の README には、いまも旧設定の ANYONE_ANONYMOUS の説明が残っていました。設定ファイルと説明書きがずれていると、半年後の自分がそれを信じて戻しかねません。
画像を置けないのでbase64で埋め込む
GASのWebアプリはHTMLを返す仕組みで、写真のような画像ファイルをそのまま配信できません。アルバムのページでは、写真を base64 の文字列にしてHTMLへ埋め込んでいます。
結果、album.html は約1.2MBになりました。写真を足すたびにスクリプトで作り直す運用です。枚数が増えるなら、Googleドライブに置いて共有リンクを貼るほうが素直だと考えています。
使ってみて分かった良い点と不満
2026年8月に作り始めて約2か月半。続けて使えているのは、更新の手間を最小にしたからで、裏を返せば不満も更新まわりに集まっています。
良かった点
- 家族はURLを開くだけで、アプリのインストールもパスワードも要らない
- 金額が手元のJSONだけにあるので、どこを直せばいいか迷わない
- Claude Codeに「水道の請求を追加して」と頼めば、JSONの追記から再生成までやってもらえる
- Googleアカウントがあれば無料で動く
スマホで開くと、パネルが縦1列に並びます。下の画像は viewport が効いた状態の表示で、総資産と口座ごとの残高が最初の画面に収まります。

水道料金の履歴は、2026年10月24日にメモのファイルからこのJSONに移しました。別の場所にあった記録も、画面に出したくなったらJSONに寄せればパネルが1つ増えるだけです。
不満:楽天証券は手で更新するしかない
一番の不満は、残高を自動で取ってこられないことです。楽天証券には残高を取得する公式のAPIがなく、証券口座の数字は手で直す前提の作りにしています。
もう1つは、更新のたびに「生成 → push → deploy」の3コマンドが要ること。金額を1つ直すだけでも同じ手順なので、月末などにまとめて反映するほうが楽です。家計簿アプリのような金融機関との自動連携は、この作りでは手に入りません。
よくある質問
GASで家計ダッシュボードを作るときに、引っかかりやすい点をQ&Aにまとめました。アクセス設定と更新の手順についての質問が中心です。細かい手順は各見出しの本文もあわせて確認してください。
Q1. GASの家計ダッシュボードは無料で作れますか?
A1. 個人のGoogleアカウントがあれば無料で作れます。GASのWebアプリはGoogleのサーバーで動くので、自宅にサーバーを用意する必要もありません。
Q2. スプレッドシートを使わずに作れますか?
A2. 作れます。データをJSONで用意してスクリプトの中に埋め込めば、スプレッドシートは不要です。家族に入力してもらいたい場合は、スプレッドシートを読み込む作りのほうが向いています。
Q3. 家族がGoogleアカウントを持っていない場合はどうしますか?
A3. ログインなしで開ける設定は ANYONE_ANONYMOUS だけですが、URLを知る全員が見られる状態になります。家計の数字を載せるなら、家族にGoogleアカウントを作ってもらい、許可リストで絞るほうが安全です。
Q4. 更新したのに画面の内容が変わらないのはなぜですか?
A4. push しただけで、デプロイしていない可能性があります。/exec のURLはデプロイした版のコードを動かすためです。clasp deploy -i にデプロイIDを付けて実行すると、同じURLのまま新しい版に切り替わります。
Q5. スマホで表示すると文字が小さいのはなぜですか?
A5. GASではHTMLファイルに書いた meta タグが無視されるためです。doGet の中で、返すHTMLに addMetaTag で viewport を付けてください。値は width=device-width, initial-scale=1 で、スマホの幅に合わせて表示されます。
Q6. 家計のデータをGASに置いても大丈夫ですか?
A6. データはGoogleのサーバー上のスクリプトに入ります。access を ANYONE、executeAs を USER_ACCESSING にし、メールの許可リストで開ける人を絞るのが私の落としどころです。ANYONE_ANONYMOUS での公開は避けてください。
GASの家計ダッシュボードを始める前に決めること
作る前に決めておきたいのは、コードの書き方より「誰が見て、誰が更新するか」の2点です。
見るのが家族で、更新するのが自分だけなら、今回のように手元のJSONを唯一の情報源にする作りが合います。家族も入力するなら、スプレッドシートを裏に置いたほうが続きます。
【要点まとめ】
- 構成は JSON → Code.gs → Webアプリの一方通行にする
- 画面は doGet、データは google.script.run で渡す
- 共有は access: ANYONE + executeAs: USER_ACCESSING + メールの許可リスト
- 更新は clasp deploy -i で同じURLのまま反映する
自作のダッシュボードをPythonで手元に置く方法は、別の記事にまとめています。
最初の公開は access を MYSELF にしておけば、設定を間違えても誰にも見られません。
続きを追う:X(@Tom_Java_Tech) / RSS
途中経過は、また書きます。

