Extensions テーブルで拡張機能を DB 管理
拡張機能(拡張スクリプト・拡張 SQL・拡張 CSS など)は、通常 App_Data/Parameters/ExtendedXxx/ フォルダに JSON ファイルを置いて管理しますが、データベースの Extensions テーブルで管理することもできます。複数インスタンスで同じ拡張設定を共有したいマルチクラスタ環境・コンテナ環境で便利で、API 経由の CRUD による自動化もできます。
対応バージョン
Pleasanter 1.4.7.0 以降
テーブル構造
| カラム名 | 型 | 説明 |
|---|---|---|
| ExtensionId | int | 拡張機能 ID(自動採番) |
| TenantId | int | テナント ID |
| ExtensionType | nvarchar(128) | 拡張機能種別 |
| ExtensionName | nvarchar(256) | 拡張機能名 |
| ExtensionSettings | nvarchar(max) | 拡張機能設定(JSON 形式) |
| Body | nvarchar(max) | 本文(スクリプト・SQL・CSS など) |
| Description | nvarchar(max) | 説明 |
| Disabled | bit | 無効フラグ(true で無効化) |
このほかに、ほかのテーブルと同じ共通列(Ver・Comments・Creator・Updator・CreatedTime・UpdatedTime)があります。起動時とパラメータリロード時に、Disabled が false のレコードを ExtensionName 順に読み込みます(ExtensionInitializer.cs#L16-L21)。
ExtensionType の値
| ExtensionType | 説明 |
|---|---|
| Fields | 拡張フィールド(カスタム項目) |
| Html | 拡張 HTML(画面への HTML 差し込み) |
| NavigationMenu | 拡張ナビゲーションメニュー |
| Script | 拡張スクリプト(クライアント JS) |
| ServerScript | 拡張サーバースクリプト |
| Sql | 拡張 SQL |
| Style | 拡張スタイル(CSS) |
| Plugin | 拡張プラグイン |
| CustomApps | ユーザーテンプレート |
CustomApps は拡張機能の読み込み(ExtensionInitializer)の対象外で、サイトのテンプレート機能がサイトパッケージの JSON を保存・読み出すために使います(SiteUtilities.cs#L3170)。拡張機能として登録する種別は、ほかの 8 種類です。
ExtensionSettings
ExtensionSettings には、ExtensionType に応じた JSON を設定します。次のフィルタリング設定はすべての種別で共通です。
| プロパティ | 型 | 説明 |
|---|---|---|
| SiteIdList | List<long> | 適用対象のサイト ID リスト(空 = 全サイト) |
| UserIdList | List<int> | 適用対象のユーザー ID リスト |
| DeptIdList | List<int> | 適用対象の部署 ID リスト |
| GroupIdList | List<int> | 適用対象のグループ ID リスト |
| Actions | List<string> | 適用アクション(edit, new など。小文字で書く) |
| Controllers | List<string> | 適用コントローラ(items など。小文字で書く) |
| IdList | List<long> | 適用対象のレコード ID リスト |
- リストが空の場合は「条件なし(全適用)」として扱われます。
- 否定条件は値の先頭に
-を付けます(例:["-5"]は ID=5 を除外)。 ExtensionSettingsを空のオブジェクト{}にすると、全サイト・全画面で適用されます。Actions・Controllersは、小文字にしたアクション名・コントローラ名と完全一致で比較されます(Context.cs)。"Edit"のように大文字を含めると一致しません。["Edit","New"]ではなく["edit","new"]と書きます。
本文は Body と ExtensionSettings のどちらに書くか
確認したソースでは、読み込み時に ExtensionSettings を種別ごとの設定クラスとして読み込み、そのうえで Body が空でなければ本文を Body で上書きします(ExtensionInitializer.cs)。
| ExtensionType | Body の扱い |
|---|---|
| Script | Script(スクリプト本文)になる |
| Style | Style(CSS 本文)になる |
| Sql | CommandText(SQL 本文)になる |
| ServerScript | Body(サーバースクリプト本文)になる |
| Html | 表示用の HTML(言語は ExtensionSettings の Language)になる |
| Fields / NavigationMenu / Plugin | 使われない。設定はすべて ExtensionSettings に書く |
つまり、本文は Body に書くのが基本で、Body を空にすれば ExtensionSettings 内の Script・Style・CommandText などがそのまま使われます。両方に書いた場合は Body が優先されます。また、ExtensionName が空でなければ ExtensionSettings の Name より優先されます。
WARNING
ExtensionSettings が空文字列や不正な JSON だと設定として読み込めず、そのレコードは無視されます。条件が不要な場合も {} を入れてください。また、読み込み時の条件は Disabled だけで、TenantId による絞り込みはしていません(ExtensionInitializer.cs)。確認したソースでは、登録したレコードはテナントに関係なく適用されます。
SQL で直接登録する例
特定サイトに JavaScript を追加
INSERT INTO Extensions (
TenantId,
ExtensionType,
ExtensionName,
ExtensionSettings,
Body,
Description,
Disabled
) VALUES (
1,
'Script',
'MyCustomScript',
'{"SiteIdList":[12345,67890],"Actions":["edit","new"]}',
'$(document).ready(function() { console.log("カスタムスクリプト読み込み完了"); });',
'サイトID 12345, 67890 の編集・新規画面で実行されるスクリプト',
0
);INSERT INTO "Extensions" (
"TenantId",
"ExtensionType",
"ExtensionName",
"ExtensionSettings",
"Body",
"Description",
"Disabled"
) VALUES (
1,
'Script',
'MyCustomScript',
'{"SiteIdList":[12345,67890],"Actions":["edit","new"]}',
'$(document).ready(function() { console.log("カスタムスクリプト読み込み完了"); });',
'サイトID 12345, 67890 の編集・新規画面で実行されるスクリプト',
false
);INSERT INTO Extensions (
TenantId,
ExtensionType,
ExtensionName,
ExtensionSettings,
Body,
Description,
Disabled
) VALUES (
1,
'Script',
'MyCustomScript',
'{"SiteIdList":[12345,67890],"Actions":["edit","new"]}',
'$(document).ready(function() { console.log("カスタムスクリプト読み込み完了"); });',
'サイトID 12345, 67890 の編集・新規画面で実行されるスクリプト',
0
);全サイト共通の CSS
INSERT INTO Extensions (
TenantId,
ExtensionType,
ExtensionName,
ExtensionSettings,
Body,
Description,
Disabled
) VALUES (
1,
'Style',
'GlobalStyle',
'{}',
'.field-label { font-weight: bold; color: #333; }',
'全サイト共通のカスタムスタイル',
0
);INSERT INTO "Extensions" (
"TenantId",
"ExtensionType",
"ExtensionName",
"ExtensionSettings",
"Body",
"Description",
"Disabled"
) VALUES (
1,
'Style',
'GlobalStyle',
'{}',
'.field-label { font-weight: bold; color: #333; }',
'全サイト共通のカスタムスタイル',
false
);INSERT INTO Extensions (
TenantId,
ExtensionType,
ExtensionName,
ExtensionSettings,
Body,
Description,
Disabled
) VALUES (
1,
'Style',
'GlobalStyle',
'{}',
'.field-label { font-weight: bold; color: #333; }',
'全サイト共通のカスタムスタイル',
0
);特定ユーザー向けサーバースクリプト
INSERT INTO Extensions (
TenantId,
ExtensionType,
ExtensionName,
ExtensionSettings,
Body,
Description,
Disabled
) VALUES (
1,
'ServerScript',
'AdminOnlyValidation',
'{"UserIdList":[1,2],"BeforeUpdate":true}',
'if (model.Status < 200) { model.Status = 200; }',
'管理者のみ更新前に実行されるバリデーション',
0
);INSERT INTO "Extensions" (
"TenantId",
"ExtensionType",
"ExtensionName",
"ExtensionSettings",
"Body",
"Description",
"Disabled"
) VALUES (
1,
'ServerScript',
'AdminOnlyValidation',
'{"UserIdList":[1,2],"BeforeUpdate":true}',
'if (model.Status < 200) { model.Status = 200; }',
'管理者のみ更新前に実行されるバリデーション',
false
);INSERT INTO Extensions (
TenantId,
ExtensionType,
ExtensionName,
ExtensionSettings,
Body,
Description,
Disabled
) VALUES (
1,
'ServerScript',
'AdminOnlyValidation',
'{"UserIdList":[1,2],"BeforeUpdate":true}',
'if (model.Status < 200) { model.Status = 200; }',
'管理者のみ更新前に実行されるバリデーション',
0
);API から実行できる拡張 SQL
ExtensionSettings に "Api": true を設定すると、拡張 SQL を API から実行できます。
INSERT INTO Extensions (
TenantId,
ExtensionType,
ExtensionName,
ExtensionSettings,
Body,
Description,
Disabled
) VALUES (
1,
'Sql',
'GetSiteList',
'{"Api":true}',
'SELECT SiteId, Title FROM Sites WHERE TenantId = @_T ORDER BY SiteId',
'サイト一覧を取得する拡張SQL',
0
);INSERT INTO "Extensions" (
"TenantId",
"ExtensionType",
"ExtensionName",
"ExtensionSettings",
"Body",
"Description",
"Disabled"
) VALUES (
1,
'Sql',
'GetSiteList',
'{"Api":true}',
'SELECT "SiteId", "Title" FROM "Sites" WHERE "TenantId" = @ipT ORDER BY "SiteId"',
'サイト一覧を取得する拡張SQL',
false
);INSERT INTO Extensions (
TenantId,
ExtensionType,
ExtensionName,
ExtensionSettings,
Body,
Description,
Disabled
) VALUES (
1,
'Sql',
'GetSiteList',
'{"Api":true}',
'SELECT SiteId, Title FROM Sites WHERE TenantId = @ipT ORDER BY SiteId',
'サイト一覧を取得する拡張SQL',
0
);@_T(PostgreSQL・MySQL では @ipT)はログインユーザー(API キーのユーザー)のテナント ID で、拡張 SQL では常に使えます。接頭辞は DBMS で変わり、Parameter.json の SqlParameterPrefix が空の既定なら SQL Server は @_、PostgreSQL・MySQL は @ip です(Parameter.cs、SqlIo.cs)。PostgreSQL ではテーブル名・列名が二重引用符付きの大文字小文字を区別した名前で作られるため(CreateTable.sql)、"Extensions" のように二重引用符で囲みます(囲まないと小文字の名前として扱われ、テーブルが見つかりません)。Disabled は PostgreSQL では boolean 型なので false を入れます(PostgreSqlDataTypes.cs)。@TenantId というパラメータは渡されないため、@TenantId と書くと動きません。
/api/extended/sql から呼び出します。
POST /api/extended/sql
{
"ApiVersion": 1.1,
"ApiKey": "your-api-key",
"Name": "GetSiteList"
}Extensions API
Extensions テーブルのレコードは API で CRUD 操作できます。
前提条件
- テナント管理画面で「Extensions API を許可する」を有効にしている(Tenants テーブルの
AllowExtensionsApi) - API キーで認証する
- 特権ユーザーである
- ライセンスの
Environmentが 1・2 の環境ではない
コントローラは最初に AllowExtensionsApi を見て、無効なら 403 を返します。次に、認証済みでかつ特権ユーザーでなければ 401 を返します(ExtensionsController.cs#L35-L46)。取得・作成・更新・削除の各処理は、さらに Parameters.Environment() が 1 または 2 のとき 403 を返します(ExtensionValidators.cs#L18-L21、Validators.cs#L97-L114)。Environment() はライセンス情報の Environment の値で、ライセンスがない環境は 0、試用ライセンスだけの環境は 3 です(Parameters.cs#L169-L178)。
エンドポイント
| 操作 | メソッド | エンドポイント |
|---|---|---|
| 全件取得 | POST | /api/extensions/get |
| 個別取得 | POST | /api/extensions/{id}/get |
| 作成 | POST | /api/extensions/create |
| 更新 | POST | /api/extensions/{id}/update |
| 削除 | POST | /api/extensions/{id}/delete |
全件取得
テナント内の Extension レコードを取得します。
POST /api/extensions/get
{
"ApiVersion": 1.1,
"ApiKey": "your-api-key"
}レスポンス例:
{
"StatusCode": 200,
"Response": {
"Data": [
{
"ApiVersion": 1.1,
"ExtensionId": 1,
"TenantId": 1,
"Ver": 1,
"ExtensionType": "Script",
"ExtensionName": "MyCustomScript",
"ExtensionSettings": "{\"SiteIdList\":[12345]}",
"Body": "console.log('Hello');",
"Description": "カスタムスクリプト",
"Disabled": false,
"Creator": 1,
"Updator": 1,
"CreatedTime": "2026-02-19T10:00:00",
"UpdatedTime": "2026-02-19T10:00:00"
}
]
}
}View を指定すると、条件に合うレコードだけを取得できます。
POST /api/extensions/get
{
"ApiVersion": 1.1,
"ApiKey": "your-api-key",
"View": {
"ColumnFilterHash": {
"ExtensionType": "Script"
}
}
}個別取得
URL に ExtensionId を指定して 1 件取得します。
POST /api/extensions/1/get
{
"ApiVersion": 1.1,
"ApiKey": "your-api-key"
}作成
作成時は ExtensionSettings を JSON オブジェクトで渡します。
POST /api/extensions/create
{
"ApiVersion": 1.1,
"ApiKey": "your-api-key",
"ExtensionType": "Script",
"ExtensionName": "MyNewScript",
"ExtensionSettings": {
"SiteIdList": [12345],
"Actions": ["edit"]
},
"Body": "console.log('Hello from Extensions API');",
"Description": "API経由で作成したスクリプト",
"Disabled": false
}作成・更新では、受け取った ExtensionSettings を ExtensionType に対応する設定クラスとして読めるかを確かめます(ExtensionApiModel.cs#L61-L97)。読めない場合は不正な JSON として扱われ、エラーになります(ExtensionUtilities.cs#L348-L350)。
レスポンス例:
{
"Id": 2,
"StatusCode": 200,
"Message": "MyNewScript を作成しました。"
}更新
URL に ExtensionId を指定します。変更するフィールドだけを指定でき、指定しなかったフィールドは既存の値が維持されます。
POST /api/extensions/1/update
{
"ApiVersion": 1.1,
"ApiKey": "your-api-key",
"Body": "console.log('Updated script');",
"Description": "更新されたスクリプト",
"Disabled": false
}レスポンス例:
{
"Id": 1,
"StatusCode": 200,
"Message": "MyNewScript を更新しました。"
}削除
POST /api/extensions/1/delete
{
"ApiVersion": 1.1,
"ApiKey": "your-api-key"
}レスポンス例:
{
"Id": 1,
"StatusCode": 200,
"Message": "MyNewScript を削除しました。"
}エラーレスポンス
{
"StatusCode": 403,
"Message": "この操作を行う権限がありません。"
}| StatusCode | 説明 |
|---|---|
| 200 | 成功 |
| 400 | 不正なリクエスト(JSON 形式エラー等) |
| 401 | 認証できない、または特権ユーザーでない |
| 403 | テナントで Extensions API が許可されていない、または Environment が 1・2 の環境 |
| 404 | 指定した ExtensionId が存在しない |
ファイルベースの設定との関係
Extensions テーブルのレコードと App_Data/Parameters/ExtendedXxx/ のファイルは共存します。
| 管理方法 | 設定場所 | 登録タイミング |
|---|---|---|
| ファイル | App_Data/Parameters/ExtendedXxx/*.json | アプリ起動時に読込 |
| データベース | Extensions テーブル | アプリ起動時に読込 |
両方に設定がある場合は、それぞれ独立してリストに追加されます。同名の拡張があると両方とも適用されるため、意図しない重複に注意してください。
WARNING
Extensions テーブルの変更を反映するには、アプリケーションの再起動またはパラメータリロードが必要です。
再起動せずに反映するには、特権ユーザーでログインした状態で次の URL にアクセスします。成功すると空白のページが表示され、Extensions テーブルを含むパラメータが再読み込みされます(ParametersInitializer.cs)。
https://{プリザンターのURL}/admins/reloadparametersマルチクラスタ環境での比較
| 観点 | ファイルベース | データベース(Extensions テーブル) |
|---|---|---|
| 設定の配布 | 各インスタンスにファイルを配置する必要あり | データベースを共有すれば自動的に同期 |
| 設定変更の反映 | 各インスタンスでファイルを更新&再起動 | DB を更新して各インスタンスを再起動 |
| バージョン管理 | Git などで管理 | DB レコードとして管理(履歴は別途必要) |
| 運用自動化 | デプロイツールとの連携が必要 | API 経由で設定変更が可能 |