frappe-errors-clientscripts について
Frappe のクライアントスクリプト向けトラブルシューティングガイドです。よくあるエラーの原因と修正方法を対応付け、非同期呼び出し、子テーブル、イベントタイミング、ブラウザーでのデバッグを扱います。
主な機能
- 01エラーメッセージから原因を特定
- 02よくある問題の修正パターン
- 03非同期呼び出しのエラー処理
- 04子テーブルデータへのアクセス方法
- 05ブラウザーデバッグツールの紹介
活用シーン
- 01フォームフィールド未定義エラーの診断
- 02frappe.call の無言の失敗を修正
- 03子テーブル行へのアクセス問題の調査
- 04イベントループや保存時検証の問題を解決
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-errors-clientscriptsインストール後、Claude Code で/frappe-errors-clientscriptsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/anti-patterns.mdexamples.mdpatterns.md詳細
クライアントスクリプトのエラー — 診断と解決
関連項目:frappe-syntax-clientscripts(構文)、frappe-impl-clientscripts(ワークフロー)、frappe-errors-serverscripts(サーバー側)。
エラー診断フローチャート
ERROR IN CLIENT SCRIPT
│
├─► TypeError: Cannot read properties of undefined
│ ├─► "frm.doc.fieldname" → Field does not exist on DocType
│ ├─► "r.message.value" → Server returned null/error
│ └─► "row.fieldname" in child table → Row not fetched correctly
│
├─► frappe.call fails silently
│ ├─► Missing error callback → Add error handler
│ ├─► 403 Forbidden → Method not whitelisted (@frappe.whitelist)
│ ├─► 417 Expectation Failed → Server-side frappe.throw()
│ └─► 401 Unauthorized → Session expired or CSRF token invalid
│
├─► Uncaught (in promise) → Missing try/catch on async frappe.call
│
├─► Field appears blank after set_value → Timing issue (setup vs refresh)
│
├─► cur_frm is undefined → Using cur_frm in list/report context
│
└─► frappe.throw() does not prevent save → Used outside validate event
エラーメッセージ → 原因 → 修正方法の表
| エラーメッセージ | 原因 | 修正方法 |
|---|---|---|
TypeError: Cannot read properties of undefined (reading 'fieldname') | DocTypeにフィールドが存在しない、またはdocが読み込まれていない | フィールドにアクセスする前に、必ずfrm.docの存在を確認してください |
TypeError: frm.set_value is not a function | 未定義のcur_frmショートカットを使用している | イベントハンドラーから渡されるfrmパラメーターを必ず使用してください |
Uncaught (in promise) | frappe.callからの非同期リジェクションが処理されていない | 非同期呼び出しは必ずtry/catchで囲んでください |
CSRFTokenError / 403 with CSRF | セッションタイムアウト後のトークン不一致 | CSRFを自動的に処理するfrappe.call()を必ず使用してください |
Not permitted / frappe.callで403 | サーバーメソッドに@frappe.whitelist()がない | APIメソッドには必ず@frappe.whitelist()デコレーターを追加してください |
frappe.throw() not preventing save | validateイベント以外でfrappe.throw()を使用している | frappe.throw()は必ずvalidate内でのみ使用してください |
set_query内のfield not found: xyz | fieldnameの誤字、またはフィールドが子テーブルにない | DocTypeの定義を確認し、正確なfieldnameを確認してください |
row.item_code is undefined | 子テーブルの行へのアクセス方法が誤っている — localsが同期されていない | 子テーブルのイベントではfrappe.get_doc(cdt, cdn)を使用してください |
frm.set_value not working | フォームが完全に読み込まれる前にsetup内で呼び出されている | フィールド設定のロジックをrefreshイベントに移してください |
Maximum call stack exceeded | 循環トリガー — フィールド変更で自身のハンドラーが実行される | frm.flagsガードを使って再帰を防いでください |
重大なエラーパターン
1. cur_frmとfrm:初心者が最もよくする間違い
// ❌ WRONG — cur_frm is undefined in many contexts
frappe.ui.form.on('Sales Order', {
customer(frm) {
cur_frm.set_value('territory', 'Default'); // BREAKS in list view
}
});
// ✅ CORRECT — ALWAYS use the frm parameter
frappe.ui.form.on('Sales Order', {
customer(frm) {
frm.set_value('territory', 'Default');
}
});
ルール:cur_frmは絶対に使用しないでください。すべてのイベントハンドラーに渡されるfrmパラメーターを必ず使用してください。
2. Async/Await:try/catchがない場合のサイレントな失敗
// ❌ WRONG — Unhandled rejection crashes silently
frappe.ui.form.on('Sales Order', {
async customer(frm) {
let r = await frappe.call({
method: 'myapp.api.get_data',
args: { customer: frm.doc.customer }
});
frm.set_value('credit_limit', r.message.limit); // r.message may be null
}
});
// ✅ CORRECT — try/catch with null check
frappe.ui.form.on('Sales Order', {
async customer(frm) {
if (!frm.doc.customer) return;
try {
let r = await frappe.call({
method: 'myapp.api.get_data',
args: { customer: frm.doc.customer }
});
if (r.message) {
frm.set_value('credit_limit', r.message.limit || 0);
}
} catch (error) {
console.error('Customer fetch failed:', error);
frappe.show_alert({
message: __('Could not load customer details'),
indicator: 'red'
}, 5);
}
}
});
3. 子テーブルへのアクセス:誤ったパターン
// ❌ WRONG — frm.doc.items[0] may not reflect latest state
frappe.ui.form.on('Sales Order Item', {
item_code(frm, cdt, cdn) {
let row = frm.doc.items.find(r => r.name === cdn); // fragile
row.rate = 100; // Does not trigger UI refresh
}
});
// ✅ CORRECT — Use frappe.get_doc and frappe.model.set_value
frappe.ui.form.on('Sales Order Item', {
item_code(frm, cdt, cdn) {
let row = frappe.get_doc(cdt, cdn);
if (!row.item_code) return;
frappe.model.set_value(cdt, cdn, 'rate', 100); // Triggers refresh
}
});
4. タイミング:setupとrefresh
// ❌ WRONG — set_value in setup, form not ready
frappe.ui.form.on('Sales Order', {
setup(frm) {
frm.set_value('company', 'My Company'); // May not work
}
});
// ✅ CORRECT — set_query in setup, set_value in refresh/onload
frappe.ui.form.on('Sales Order', {
setup(frm) {
// Filters belong in setup
frm.set_query('customer', () => ({ filters: { disabled: 0 } }));
},
refresh(frm) {
// Value changes belong in refresh (or onload for new docs)
if (frm.is_new()) {
frm.set_value('company', 'My Company');
}
}
});
5. frappe.throw()の適用範囲:validate内でのみ有効
// ❌ WRONG — throw in customer change does NOT prevent save
frappe.ui.form.on('Sales Order', {
customer(frm) {
if (!frm.doc.customer) {
frappe.throw(__('Customer required')); // Stops script, NOT save
}
}
});
// ✅ CORRECT — throw in validate prevents save
frappe.ui.form.on('Sales Order', {
customer(frm) {
if (!frm.doc.customer) {
frappe.msgprint({ message: __('Customer required'), indicator: 'orange' });
}
},
validate(frm) {
if (!frm.doc.customer) {
frappe.throw(__('Customer is required')); // Prevents save
}
}
});
6. フラグによる再帰ガード
// ❌ WRONG — discount change triggers amount recalc, which triggers discount...
frappe.ui.form.on('Sales Order', {
discount_percent(frm) {
frm.set_value('grand_total', calculate(frm)); // Fires on_change loop
}
});
// ✅ CORRECT — Use flags to break the cycle
frappe.ui.form.on('Sales Order', {
discount_percent(frm) {
if (frm.flags.skip_recalc) return;
frm.flags.skip_recalc = true;
frm.set_value('grand_total', calculate(frm));
frm.flags.skip_recalc = false;
}
});
デバッグツール
| ツール | 使用方法 | 用途 |
|---|---|---|
| ブラウザーコンソール(F12) | console.log(frm.doc) | フォームの状態を確認する |
console.table() | console.table(frm.doc.items) | 子テーブルの行を表示する |
JSON.parse(JSON.stringify(frm.doc)) | スナップショット用のディープクローン | コンソールでの循環参照を回避する |
frappe.boot.developer_mode | 開発者モードが有効か確認する | 条件付きデバッグログ |
frappe.ui.toolbar.clear_cache() | クライアントキャッシュを消去する | スクリプト変更のデプロイ後 |
| ネットワークタブ(F12) | XHRリクエストをフィルターする | frappe.callのペイロードを確認する |
frappe.show_alert({message: 'debug', indicator: 'blue'}, 5) | UI上で視覚的にデバッグする | コンソールを使わず手早くフィードバックを得る |
必ず守ること/絶対にしないこと
必ず守ること
frmパラメーターを使用する —cur_frmは絶対に使用しないでください[v14+]- 非同期のfrappe.callをtry/catchで囲む — 未処理のリジェクションはサイレントに失敗します
- ユーザーに表示するすべての文字列に
__()を使用する — 翻訳に必須です frappe.throw()を呼び出す前に、複数の検証エラーをまとめる- イベント内で子テーブルの行にアクセスするには、
frappe.get_doc(cdt, cdn)を使用する - 保存を防ぐには、
frappe.throw()をvalidate内でのみ使用する - サーバーレスポンスのプロパティにアクセスする前に、
r.messageがnullでないことを確認する - 子テーブルのイベントでは、
frappe.model.set_value(cdt, cdn, field, value)を使用する
絶対にしないこと
alert()、confirm()、prompt()は絶対に使用しないでください — frappe.msgprint / frappe.confirm を使用します- スタックトレースをユーザーに絶対に表示しないでください — コンソールに記録し、わかりやすいメッセージを表示します
cur_frmは絶対に使用しないでください — 信頼性が低く、多くのコンテキストで未定義です- 本番環境に
console.logを絶対に残さないでください —frappe.boot.developer_modeの条件付きチェックを使用します - 同じ関数内で
.then()とawaitを絶対に混在させないでください — どちらか一方のパターンを選びます setup内でfrm.set_valueを絶対に呼び出さないでください — フォームの準備ができていません。refreshまたはonloadを使用します- コールバック形式の
frappe.callでは、errorコールバックを絶対に無視しないでください
参考ファイル
| ファイル | 内容 |
|---|---|
references/examples.md | 診断付きの実際のエラーシナリオ |
references/anti-patterns.md | よくある誤りと修正前・修正後の例 |
references/patterns.md | 防御的なエラー処理のパターン |
コメント
Markdown 対応