
カタログClient ScriptがService Portalでだけ動かない? UI Typeを実機の3画面で切り分ける
バックエンドのTry Itでは動く。なのにポータルでは同じスクリプトが沈黙する。原因の第一候補はUI Typeです。カタログのClient Scriptには、実行するUIを選ぶこの設定があります。Desktopのままだと、ポータルでは呼ばれもしません。同じアイテムを3画面(Try It・Employee Center・Service Portal)で動かし、Australia PDIで実測しました。
検証環境:Australia PDI / OOTB+検証用アイテム1件 / 2026-08-29 確認
カタログClient Scriptとは:カタログアイテムの注文フォームで動くクライアント側スクリプトです。保存先はcatalog_script_clientテーブルになります。通常フォーム用のClient Script(sys_script_client)とは別物です。
「1本書けばどの画面でも動く」という期待が最初の誤算
スクリプトを書いた担当者は、どの画面でも同じに動くことを期待します。しかしcatalog_script_clientには「UI Type」というフィールドがあります。スクリプトごとに実行するUIを選ぶ作りです。Desktopのまま保存しても、警告は出ません。
典型的な症状はこうです。型番を選ぶと別の欄が自動で埋まるはずが、ポータルでは空のまま。選択に応じて説明が変わるはずが、変わらない。どれも「呼ばれていない」症状です。
ポータル側の症状はたちが悪いものです。「呼ばれたのに失敗した」のではありません。「最初から呼ばれていない」のです。だからコンソールにエラーも残りません。調査で最初に消耗するのは、この無音です。手掛かりが無いように見えて、実は設定1つで説明が付きます。
切り分けの全体像を先に示します。

分かれ道は1つです。同じ操作をTry Itで再現できるか。動くならUIの違いが原因です。動かないならスクリプト本体を見直します。
カタログの画面制御は3系統。今回はClient Script系統の話
注文フォームの制御は3系統に分かれます。Client Script、Catalog UI Policy、変数自身の属性です。変数の属性は必須や既定値などの静的な性質を持ちます。UI Policyは宣言的な表示制御の担当です。Client Scriptはスクリプトによる自由な制御を受け持ちます。
どの系統の制御かで、開くテーブルが決まります。見分けの目安は動きの種類です。必須化や表示切り替えだけならUI Policyをまず疑います。値の計算や他フィールドへの書き込みならClient Scriptです。UI Policy側の仕組みはcatalog_ui_policyの記事に譲ります。両方を同じ項目に仕掛けたときの競合は競合の記事で再現済みです。
カタログ用が別テーブルなのには理由があります。制御の対象がテーブルのフィールドではなく「変数」だからです。保存先も適用条件の持ち方も、通常フォーム用とは別に設計されています。
公式の仕様:適用先は4つのチェック、実行UIはUI Type
catalog_script_clientのフォームには適用先のチェックが4つあります。製品同梱の公式説明(hint)はこうです。
Indicates that this Client Script will apply when ordering a Catalog Item
(訳: このClient Scriptがカタログアイテムの注文時に適用されることを示す)
Indicates that this Client Script will apply on a Requested Item task
(訳: このClient ScriptがRITM上で適用されることを示す)
Indicates that this Client Script will apply on a Catalog Task
(訳: このClient ScriptがCatalog Task上で適用されることを示す)
引用元: /sys_documentation_list.do?sysparm_query=name=catalog_script_client^hintISNOTEMPTY
4つ目のチェック(Applies on the Target Record)はRecord Producer向けです。作成先レコードのフォームで動かすかを決めます。
絞り込みは二層です。「どの画面で動かすか」はチェックボックスが決めます。「どのUIで動かすか」はUI Typeの担当です。UI Type自体にはhintがありません。だから挙動は後述の実測で確かめます。
OOTBの分布:All 94本、Desktop限定は値で数えると20本
このPDIのアクティブなアイテム用Client Scriptは115本でした。件数は出荷時の同梱分です。導入済みプラグインが違えば前後します。内訳を並べます。

大半はAll(すべてのUIで実行)です。それでもDesktop限定は無視できない数があります。OOTBでも「UIによって動かないスクリプト」は普通にある、ということです。中身はChange管理の画面補助など、バックエンド前提のものが目立ちました。管理者専用の機能なら、Desktop限定は正しい設定です。設定自体が悪いのではなく、利用者向けアイテムに紛れ込んだときに事故になります。

数えるときの罠:ラベルでなく値(0/1/10)で絞る
ここに実務の罠があります。UI Typeの値が0(Desktop相当)なのに、ラベルが空欄で表示されるスクリプトが4本ありました。ラベルで数えるとDesktop限定は16本に見えます。値で数えると20本です。4本の差は、そのまま調査漏れになります。
一覧の絞り込みはラベルでなく値(0=Desktop / 1=Mobile・Service Portal / 10=All)で行ってください。
ラベルが空になる理由までは追っていません。ただ実害は明確です。ラベル頼みの目視確認は、この4本を素通りします。値で絞れば取りこぼしはありません。
アイテム単位で数えると調査が速い
「このアイテムだけおかしい」という相談への近道があります。catalog_script_clientの一覧をcat_itemで絞ることです。そのアイテムのスクリプトが全部並びます。UI Typeの列(値)を足せば、ポータルで呼ばれない候補がその場で見えるはずです。適用先の4チェックも同じフォームにあります。1レコード開けば「どの画面で・どのUIで動くはずか」まで読み取れます。
検証用アイテム:UI Typeだけが違う2本を仕掛けた
切り分けを目に見える形にするため、検証用アイテムを1つ作りました。変数は3つです。Input Aに文字を入れると、2本のonChangeスクリプトが別々の結果欄に書き込みます。
1本目(Desktop限定): 結果欄に「動きました」と書くだけの単純なスクリプト
2本目(All): GlideRecordの存在とg_form.getControlの結果を書き込むスクリプト
1本目が「呼ばれたかどうか」の検出器です。2本目は「呼ばれた環境で何が使えるか」の検出器になります。役割を分けたのは、2つの疑問を1回の操作で同時に測るためです。

2本の違いはUI Typeと書く内容だけです。結果が画面に残るため、スクリーンショットで比較できます。alertやコンソールより見落としが起きにくい方法です。自分の環境で試すときも、この「結果を変数に書き込む」形がそのまま流用できます。作るのは変数3つとスクリプト2本だけです。10分あれば組めます。
実測:同じ操作で、Desktop限定だけがポータルで沈黙した
手順は3画面で共通にしました。Input Aに「portal-check」と入力します。Tabキーでフォーカスを外し、onChangeを発火させたら、結果欄をそのまま撮影する流れです。入力値も操作順も同じにしてあります。だから画面間の差は「スクリプトが呼ばれたかどうか」の差になります。
Try Itでは2本とも動く

両方の結果欄に値が入りました。バックエンドUIでは2本とも呼ばれています。この時点で、スクリプト本体に不備が無いことも確定です。以降の差はすべて画面側の条件になります。
Employee Center(/esc)ではDesktop限定だけ空のまま

All設定のスクリプトは動きました。Desktop限定の結果欄だけが空です。同じ操作・同じ書き方で、UI Typeの違いだけが結果を分けました。エラーは画面にもコンソールにも出ていません。冒頭に書いた「無音」の症状が、このとおり再現されています。
Service Portal(/sp)でも同じ結果になった

2つのポータルで挙動は一致しました。3画面の結果を1枚にまとめます。

UI TypeをAllへ直すと、同じスクリプトがポータルでも動いた
対処も同じ画面で見届けます。変更はスクリプトのフォームでUI TypeをAllに選び直し、保存するだけです。そのうえでEmployee Centerに戻り、同じ操作を繰り返しました。

空だった結果欄に、バックエンドと同じ出力が入りました。スクリプト本体は1文字も変えていません。変えたのはUI Typeの1フィールドだけです。反映に再起動やキャッシュ操作は不要でした。ページを開き直せば効きます。
今回の症状への対処はこれで完結です。「Desktop限定をAllに変える」の1手になります。
Allに変えてもバックエンド側は動き続けます。All設定がTry Itで動くことは、API確認スクリプト(All)の実測で確認済みです。実運用で直すときも、両方のUIで一度ずつ確かめてから次へ進みます。
補足を2つ添えます。第一に、選択肢のMobile / Service Portal(値1)は今回未検証です。ポータル専用にする場合の候補ですが、挙動は自分の環境で確かめてください。第二に、本番の直し方です。catalog_script_clientは更新セットで移送される設定レコードになります。開発環境で直し、動作を確かめてから更新セットで運ぶ流れです。本番のレコードを直接編集すると、開発環境との差分が残ります。直す場所は開発環境側に揃えるのが安全です。
APIの結論:存在チェックは通った。それでも書き方はGlideAjaxに寄せる
意外な実測結果が1つあります。ポータルでは「GlideRecordが使えない」と言われてきました。ところが存在チェックは3画面とも通過しています。GlideRecordのシンボルは存在しました。getControlも入力欄を返しています。
ただし、存在と実用は別です。公式ナレッジには、GlideRecordのクエリを使うスクリプトがService Portalで動かない事例が今もあります(KB0727754)。未対応APIで動かない事例の切り口もあります(KB0623310)。同期クエリや戻り値依存の書き方まで動く保証は、存在チェックからは導けません。今回の結果がこのバージョン固有の可能性も残ります。通説を上書きするにも、実測1回では足りないということです。
「じゃあ結局、何をどう書けばいいのか」。持ち帰り用に、書き方ごとの判断を1枚にまとめます。

結論は明快です。新規はGlideAjax+コールバックで書きます。既存のGlideRecord依存スクリプトは「未検証」扱いにして、移行候補に載せてください。処理はサーバ側のScript Includeに寄せるのが定石です。クライアント側は、返ってきた結果を画面へ反映するだけにします。この形ならUIの違いに挙動が左右される余地が小さくなります。
GlideAjaxの構成も1行で押さえておきます。サーバ側はクライアント呼び出し可能なScript Includeです。クライアント側はGlideAjaxで名前と関数を指定し、コールバックで結果を受け取ります。同期の待ち合わせが無いため、ポータルでもバックエンドでも同じに動きます。
棚卸しの手順も短く書いておきます。catalog_script_clientの一覧にスクリプト本文の列を足してください。そのうえでGlideRecord・getXMLWait・document・windowを検索します。ヒットした分だけを公式一覧(KB1626868)と突き合わせれば十分です。全文を目視するより先に、機械的に候補を絞る段取りになります。
切り分けの起点は「Try Itで再現するか」
冒頭の図の分岐を、原因別の対処に落とします。

Try Itで動くなら、まずUI Typeです。値が0(Desktop)なら、それが答えです。値が10(All)なのに動かないときだけ、使用APIを疑います。Try Itでも動かないなら、UIは無関係です。変数名・onChangeの対象・スクリプト本体を見直します。適用先チェックの外れも、この段階で一緒に確かめます。
再発防止は開発規約に1行足すことです。「カタログのClient ScriptはUI TypeをAllにする」。バックエンド専用の意図があるときだけ、Desktopを明示的に選びます。レビューでは一覧にUI Type列(値)を足して見る、も添えておきます。
なお「アイテム自体がポータルに出てこない」なら、スクリプト以前の問題です。公開設定の切り分けはカタログアイテムが見えない記事が受け持ちます。
実機を見て気づいたこと
過去には逆方向の既知不具合もありました。Desktop設定のスクリプトがポータルで実行されてしまう不具合です(KB0862353)。つまりUI Typeの挙動は、バージョンによって設定どおりでない時期がありました。設定値を信じ込まない方が安全です。疑わしいときは、今回のように結果欄へ書き込むスクリプトを一時的に置いて実測します。数分で白黒が付きます。
onChangeの発火タイミングは3画面で同じでした。文字を入力しただけでは発火しません。フォーカスを外した時点で動きます。「入力したのに反応しない」ように見える時間は、どの画面にもあります。動作確認ではTabキーで欄を移動してから判定してください。入力したまま眺めていると、動くスクリプトまで動かないように見えます。
Employee Center(/esc)と旧来のService Portal(/sp)は、両方が有効なまま並んでいました。ポータル定義(sp_portal)では別レコードです。利用者の画面が2系統ある環境では、切り分けも両方で試します。今回は両ポータルで症状が同じでした。UI Type起因と判断する材料が1つ増えたことになります。
検証アイテムは無効化して残しました。同じ形の計測用アイテムを開発環境に1つ置いておくのはお勧めです。UIまわりで迷ったら、それを両画面で開くだけで切り分けが始められます。

用語
catalog_script_client … カタログ用Client Scriptの保存先テーブル
UI Type … スクリプトを実行するUIを選ぶ設定。値は0=Desktop / 1=Mobile・Service Portal / 10=All
Try It … バックエンドUIで注文フォームを開く管理者向けボタン
Employee Center(/esc) … 現行の標準ポータル。旧来の/spと並存する
sp_portal … ポータル定義の保存先テーブル
適用先チェック … applies_catalogなど4つのチェックボックス。動かす画面を決める
GlideAjax … クライアントからサーバ側スクリプトを呼ぶ非同期API。ポータルで推奨の書き方
Record Producer … カタログ経由で任意テーブルのレコードを作る入力フォーム。4つ目の適用先チェックが対象
付録:自分の環境で確認するときのURL
/catalog_script_client_list.do?sysparm_query=applies_to=item^active=true^ui_type=0 … Desktop限定(値0)の一覧
/catalog_script_client_list.do?sysparm_query=cat_item=<アイテムのsys_id> … 特定アイテムのスクリプト
/com.glideapp.servicecatalog_cat_item_view.do?v=1&sysparm_id=<アイテムのsys_id> … Try It相当画面
/esc?id=sc_cat_item&sys_id=<アイテムのsys_id> … Employee Centerでの同じアイテム
/sp?id=sc_cat_item&sys_id=<アイテムのsys_id> … Service Portalでの同じアイテム
/catalog_ui_policy_list.do?sysparm_query=catalog_item=<アイテムのsys_id> … UI Policy側の制御
/sp_portal_list.do … ポータル定義の一覧
/sys_script_client_list.do?sysparm_query=ui_type=0 … 通常フォーム用スクリプトのDesktop限定(同じ考え方で棚卸しできる)
引用した公式説明を自分で開く
この記事で引用した説明文は、ServiceNowが製品に同梱しているフィールドのドキュメントです。
適用先チェック4つの公式説明(引用元):/sys_documentation_list.do?sysparm_query=name=catalog_script_client^hintISNOTEMPTY
参考リンク
KB1626868: Supported client script types and APIs — ポータルで使えるAPIの公式一覧(本筋)
KB0693343: Client scripts are not running on Service Portal — 動かないときの公式の切り分け(本筋)
KB0623310: Some Catalog Client Scripts do not work in Service Portal because using unsupported APIs — 未対応API起因の事例
KB0727754: Catalog client scripts that set a reference field do not work in Service Portal — GlideRecordクエリが絡む具体例
KB0862353: Catalog Client Scripts set to UI type Desktop are running in Portal — 逆方向の既知不具合
検証範囲とおことわり
挙動と件数はAustralia PDI(OOTB)で2026-08-29に確認したものです。検証用に作成したのはアイテム1件・変数3件・Client Script 2本です。UI Typeの変更も自作スクリプトだけに行いました。確認後はアイテムとスクリプト2本を無効化しています。変数はアイテムの無効化に伴い露出しません。既存のOOTB設定には手を入れていない状態です。
GlideRecordとgetControlの確認は「存在するか」「入力欄を返すか」までです。同期クエリの完走・戻り値の中身・DOM操作は未検証です。UI TypeのMobile / Service Portal(値1)も未検証になります。onLoad・onSubmitタイプ、Variable Set側のスクリプト、モバイルアプリの挙動も今回の範囲外です。
関連記事
ServiceNowでカタログアイテムをゼロから作る手順:申請フォームが動くまで — 検証用アイテムを自分で作って試したい人の前提知識
注文フォームの制御は通常フォームと別テーブル(catalog_ui_policy) — もう1つの画面制御系統であるUI Policy側の仕組み
ServiceNowのカタログアイテムが見えない・申請できない:sc_cat_itemの公開設定を実機で確認する — アイテム自体が出てこないときの切り分け編