JavaScript

GROWIプラグインとは?3種類の違いとscript開発・導入手順を実装目線で解説

GROWIのプラグインは、本体のソースコードを書き換えずに表示や記法を差し替えるための仕組みです。公式ドキュメントは種類ごとの手順書に分かれているため、「どこまで拡張できるのか」「インストールボタンを押したあとGROWIが何をしているのか」は読み取りにくい構成になっています。この記事では、GROWI本体のソースコード(growilabs/growi)と公式のプラグイン開発ドキュメントを突き合わせ、3種類の役割分担・package.jsonの必須定義・管理画面インストールの内部処理・読み込みに失敗したときの切り分けまでを実装目線で整理します。

前提とするバージョンは、2026年7月27日にリリースされたGROWI v8.0.0時点です。プラグインの定義スキーマはschemaVersion 4で、v8.0.0のリリースノートにプラグイン関連の変更は含まれていません。

まとめ:GROWIプラグイン導入と開発の要点

  • 選べるプラグインはスクリプト・テーマ・テンプレートの3種類。サーバーサイドの処理を差し込む口はスキーマv4に存在しません
  • package.jsongrowiPluginディレクティブが必要で、schemaVersionが4未満だとインストール時に検証エラーになります
  • インストールはGitHubのアーカイブzipを取得して展開する処理で、npmへの公開もgit cloneも介在しません
  • スクリプトプラグインの入口はclient-entry.tsx固定。ファイル名を変えるとGROWIがマニフェストから解決できず読み込まれません
  • ページ表示と編集プレビューはフックが別なので、片方だけ差し替えるとプレビューに反映されません

以降では、種類ごとの守備範囲、インストール時にGROWIが辿る経路、実際に書くファイルの中身の順に見ていきます。

GROWIプラグイン3種類の役割分担

公式ドキュメントが開発手順を用意しているのは、スクリプト・テーマ・テンプレートの3種類です。GROWI本体の型定義(packages/core/src/consts/growi-plugin.ts)にはstyleを加えた4つの値が並びますが、style単独の開発手順はドキュメントに無く、CSSの読み込み経路もスクリプトと同じマニフェストのエントリを共用しています。実務で選ぶのは3種類と考えて差し支えありません。

種類 成果物 変えられるもの ビルド
script JavaScript 記法・描画・DOM操作 Vite(TypeScript)
theme CSS 配色・レイアウト Vite(SCSS)
template Markdown 新規ページの雛形 不要

1つのプラグインで複数の種類を兼ねることもできます。typesは配列なので、テーマとスクリプトを同梱して見た目と挙動を同時に変える構成が取れます。

スクリプトプラグイン:ブラウザ側のJavaScriptによる差し替え

スクリプトプラグインは、GROWIが読み込んだあとのブラウザ上で動きます。用途として多いのは、Markdownの描画に使うReactコンポーネントの入れ替えです。GROWIの公式org(growilabs)が公開しているgrowi-plugin-datatablesは、Markdownの表を描画するtableコンポーネントをDataTables対応版で包み替えることで、ソートや検索が効く表に変えています。

記法そのものを増やす場合も同じ入口を使います。GROWIはMarkdownのレンダラーオプション生成関数を外部へ公開しているため、remarkやrehypeのプラグインを追加したオプションを返せば、独自記法をページ本文で解釈させられます。

テーマプラグイン:SCSSによる配色とレイアウトの上書き

テーマプラグインは、ビルドしたCSSをGROWIに読み込ませて外観を変えます。単なるCSSファイルの配置ではなく、package.jsonに配色のメタデータを宣言する点が特徴です。管理画面のテーマ選択画面に並ぶプレビューの色は、このメタデータから描かれます。

テンプレートプラグイン:Markdown雛形の追加

テンプレートプラグインはビルドを伴いません。決められたディレクトリ構成でMarkdownファイルとメタ情報のJSONを置くだけで、新規ページ作成時に選べる雛形として登録されます。議事録や障害報告のように書式を揃えたい文書がある組織で効きます。ロケールごとにファイルを分けられるため、日本語と英語の雛形を同じテンプレートIDで並べられます。

プラグインで変えられない範囲

スキーマv4のプラグインがGROWIに追加できるのは、ブラウザへ配るJavaScriptとCSS、そしてMarkdownのテンプレートファイルだけです。サーバー側の処理を差し込む口はありません。

そのため、保存時に本文をバリデーションする、ページの権限判定を独自ルールに変える、更新をトリガーに外部システムへ通知するといった要件は、プラグインでは実現できません。これらはGROWI本体への機能追加か、外部からAPIやWebhookで連携する構成に振り分けます。調査の手戻りを避けるため、要件がサーバー側の振る舞いに触れるかどうかを最初に切り分けてください。他社SaaSの拡張機構との比較検討には、マニフェスト構成と配布方法を整理したkintoneプラグイン開発とは|manifest構成から配布・内製外注の判断までも参考になります。

管理画面からのインストール手順

プラグインの導入は管理者権限で行います。管理画面のプラグイン画面を開き、GitHubリポジトリのURLを貼り付けてインストールを実行するだけです。ブランチ名を指定する欄があり、未入力ならmainが使われます。インストール後はカード形式で一覧に並び、有効化と無効化、削除をカードから操作します。同じリポジトリから再度インストールすると、既存のプラグインは上書きされます。

タグの指定は管理画面からはできません。インストールフォームの実装ではタグを送信する行がコメントアウトされたまま残っており、実際に送られるのはURLとブランチ名だけです。サーバー側も、リクエストの型とAPI定義にタグの項目は残っているものの、インストール処理がその値をURL生成側へ渡していません。APIを直接叩いてタグを送っても無視されます。バージョンを固定して検証したいときは、検証用のブランチを切って指定してください。

URLを渡したあとにGROWIが辿る経路

この処理の中身を知っておくと、失敗したときの切り分けが早くなります。GROWIは受け取ったURLから組織名とリポジトリ名を取り出し、GitHubのアーカイブzipのURLを組み立ててHTTPで取得します。git cloneではなく、次の形式のzipを1回ダウンロードするだけです。

https://github.com/<組織名>/<リポジトリ名>/archive/refs/heads/<ブランチ名>.zip

# タグを指定した場合
https://github.com/<組織名>/<リポジトリ名>/archive/refs/tags/<タグ名>.zip

タグ側のURLは、組み立て処理にだけ実装が残っている経路です。取得したzipはGROWIのtmp/plugins配下へ展開され、<組織名>/<リポジトリ名>のパスで保存されます。展開後のファイルは/static/plugins以下で静的配信され、ブラウザはここからプラグインのJavaScriptやCSSを読み込みます。展開先のパスは結合後に基準ディレクトリの外へ出ていないか検証されるため、リポジトリ名に細工したパス脱出は弾かれます。

スクリプトプラグインの開発手順

ここからは実際に書くファイルを順に見ます。公式ドキュメントの手順は、package.jsonの編集、依存のインストール、実装、Viteの設定、ビルドの5段階です。パッケージマネージャはドキュメント上pnpmが使われています。

package.jsonのgrowiPluginディレクティブ

GROWIがプラグインとして認識する条件は、package.jsongrowiPluginというキーが存在することです。

{
  "name": "growi-plugin-my-feature",
  "version": "1.0.0",
  "description": "Example GROWI plugin",
  "type": "module",
  "keywords": ["growi", "growi-plugin"],
  "license": "MIT",
  "scripts": {
    "build": "tsc && vite build"
  },
  "devDependencies": {
    "@growi/pluginkit": "^1.2.7",
    "@types/react": "^19.0.0",
    "@vitejs/plugin-react": "^5.0.0",
    "typescript": "^5.9.0",
    "vite": "^8.2.0"
  },
  "growiPlugin": {
    "schemaVersion": "4",
    "types": ["script"]
  }
}

検証を担うのは@growi/pluginkitです。growiPluginが無い、schemaVersionが数値に変換できないか4未満、typesが無い。このいずれかに当たれば検証エラーとなり、インストールは中断。schemaVersionは数値へ変換してから比較されるため、文字列の"4"でも通ります。実在するプラグインもほとんどが文字列表記です。

client-entry.tsxでのactivate登録

スクリプトプラグインの入口はclient-entry.tsxです。この中でactivatedeactivateを定義し、プラグイン名をキーにしてwindow.pluginActivatorsへ登録します。GROWIは起動時にこのオブジェクトを走査し、登録された関数を呼び出します。

import { wrapMyTable } from './src/MyTable';

declare const growiFacade: any;

const activate = (): void => {
  if (growiFacade == null || growiFacade.markdownRenderer == null) {
    return;
  }

  const { optionsGenerators } = growiFacade.markdownRenderer;
  const original = optionsGenerators.customGenerateViewOptions;

  optionsGenerators.customGenerateViewOptions = (...args: any[]) => {
    const options = original
      ? original(...args)
      : optionsGenerators.generateViewOptions(...args);

    // 描画に使うReactコンポーネントを差し替える
    options.components.table = wrapMyTable(options.components.table);

    return options;
  };
};

const deactivate = (): void => {
};

if ((window as any).pluginActivators == null) {
  (window as any).pluginActivators = {};
}
(window as any).pluginActivators['growi-plugin-my-feature'] = {
  activate,
  deactivate,
};

export {};

GROWI本体側の実装を読むと、走査時に呼ばれるのはactivateだけです。deactivateは型としては要求されるものの、本体のアクティベート処理からは呼ばれません。無効化の後始末をdeactivateに書いて動作を期待すると、そこだけ実行されないまま残ります。状態を戻す処理が要るならactivateの中で完結させる設計にしてください。

実装から参照するgrowiFacadeは、GROWIがブラウザのグローバルに用意する受け渡し口です。markdownRenderer.optionsGeneratorsに描画オプションの生成関数が入り、Reactの実体もgrowiFacade.reactとして渡されます。ただし公式ドキュメントが案内しているのは、直接参照ではなく@growi/pluginkitgrowiReact関数を挟む方法です。この関数はNODE_ENVproductionのときだけwindow.growiFacade.reactを返し、開発時は引数で渡したReactをそのまま返します。開発中にfacadeが未初期化でも壊れず、本番ではGROWI本体とReactインスタンスを共有できます。Hooksを使うコンポーネントを配る場合、この使い分けを外すとインスタンスの二重化で不可解な不具合を踏みます。

vite.config.tsのマニフェスト出力

ビルド設定で外せないのは、マニフェストの出力とエントリの指定の2点です。

import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [react()],
  build: {
    manifest: true,
    rollupOptions: {
      input: ['/client-entry.tsx'],
    },
  },
});

GROWIはビルド成果物のマニフェストを読み、client-entry.tsxというキーに対応するファイル名を引いて配信URLを組み立てます。エントリのファイル名を変えるとキーも変わり、GROWIはプラグインのJavaScriptを見つけられません。マニフェストの出力先はViteの世代で変わります。Vite 4系はdist/manifest.json、5系以降はdist/.vite/manifest.jsonです。GROWIは後者を優先して探し、無ければ前者を読むため、どちらの世代でも動きます(2026年8月時点のViteの最新は8系です)。GROWIが読むのはViteが出力するマニフェスト形式なので、プラグイン側でバンドラを別のものへ差し替える余地はありません。GROWI以外のプロジェクトでバンドラを比較検討するなら、esbuildとは?Go製の高速バンドラの使い方と採用判断を実装目線で解説に変換速度と設定の考え方をまとめています。

ページ表示と編集プレビューの二重差し替え

GROWIは本文表示用と編集画面のプレビュー用で、別々のオプション生成関数を参照します。前者がcustomGenerateViewOptions、後者がcustomGeneratePreviewOptionsです。

declare const growiFacade: any;

const activate = (): void => {
  if (growiFacade == null || growiFacade.markdownRenderer == null) {
    return;
  }

  const { optionsGenerators } = growiFacade.markdownRenderer;

  // ページ表示用
  const originalView = optionsGenerators.customGenerateViewOptions;
  optionsGenerators.customGenerateViewOptions = (...args: any[]) => {
    const options = originalView
      ? originalView(...args)
      : optionsGenerators.generateViewOptions(...args);
    return applyMyPlugin(options);
  };

  // 編集画面のプレビュー用(こちらを省くとプレビューだけ素のまま)
  const originalPreview = optionsGenerators.customGeneratePreviewOptions;
  optionsGenerators.customGeneratePreviewOptions = (...args: any[]) => {
    const options = originalPreview
      ? originalPreview(...args)
      : optionsGenerators.generatePreviewOptions(...args);
    return applyMyPlugin(options);
  };
};

片方だけを差し替えると、公開されたページでは独自記法が効いているのに、編集画面のプレビューだけ素のMarkdownのまま表示されます。書き手からは「壊れている」ように見えるため、記法を追加するプラグインでは両方を差し替えるのが既定と考えてください。差し替え処理を共通の関数へ切り出しておけば、二重管理にはなりません。

組み上げたロジックにテストを付けるなら、Viteの設定をそのまま流用できるテストランナーが扱いやすくなります。実在するGROWIプラグインでも、テーマ系とテンプレート系の双方がVitestを採用しています。導入の判断材料はVitestとは?Viteネイティブなテスト基盤の仕組みと採用判断を実装目線で解説にまとめています。

テーマプラグインのthemes定義

テーマプラグインでは、growiPluginの中にthemes配列を置き、テーマ1件ごとに配色を宣言します。GROWI本体はこのオブジェクトを型ガードで検証しており、必須キーが1つでも欠けるとテーマとして扱われません。次に挙げるのは、公式orgが公開しているテーマプラグインgrowilabs/growi-plugin-theme-vivid-internet(v1.2.0)の実際の定義です。

"growiPlugin": {
  "schemaVersion": "4",
  "types": ["theme"],
  "themes": [
    {
      "name": "vivid-internet",
      "manifestKey": "src/styles/style.scss",
      "schemeType": "light",
      "lightBg": "#ff0000",
      "darkBg": "#0000ff",
      "lightSidebar": "#ffff00",
      "darkSidebar": "#ff8800",
      "lightIcon": "#ff0000",
      "darkIcon": "#000000",
      "createBtn": "#00ff00"
    }
  ]
}

必須なのはnamemanifestKeyschemeTypelightBgdarkBglightSidebardarkSidebarlightIcondarkIconcreateBtnの10キーです。isPresetThemeだけが任意で、それ以外は1つでも欠けると型ガードを通りません。schemeTypeにはライト・ダーク・両対応のいずれかを指定します。

manifestKeyに書くのはビルド前のパス

テーマで最も間違えやすいのがmanifestKeyです。ここに書くのはビルド後に生成されたCSSファイル名ではなく、ビルド前のソースファイルのパスです。先の例ではsrc/styles/style.scssが指定されています。GROWIはこの値をキーにしてマニフェストを引き、ハッシュ付きの実CSSファイル名へ解決します。ビルド後の名前を書くとマニフェストのキーと一致せず、テーマは選択肢に現れません。

テンプレートプラグインのディレクトリ構成

テンプレートプラグインはディレクトリ構成が仕様そのものです。package.jsonには対応するロケールを列挙します。

"growiPlugin": {
  "schemaVersion": "4",
  "types": ["template"],
  "locales": ["ja_JP", "en_US", "zh_CN"]
}

ファイルはdist配下に、テンプレートIDとロケールの2階層で並べます。

growi-plugin-templates-for-office/
├── package.json      … locales に対応ロケールを列挙
└── dist/
    ├── announcement/       … テンプレートID(ディレクトリ名がそのままID)
    │   ├── ja_JP/
    │   │   ├── meta.json   … title は必須、desc は任意
    │   │   └── template.md … 本文の雛形
    │   ├── en_US/
    │   └── zh_CN/
    └── blog-content-plan/

GROWIはlocalesに書かれたロケールを順に走査し、各ディレクトリにtemplate.mdmeta.jsonが存在するかを確認します。meta.jsontitleは必須で、欠けているとそのロケールは無効として記録されます。descは任意です。公式ドキュメントが挙げるロケールはja_JPen_USzh_CNfr_FRの4つです。1つのプラグインに複数のテンプレートIDを含められるので、議事録・障害報告・提案書といった書式群をまとめて配布できます。

既定になるのは最初に有効と判定されたロケール

並び順には意味があります。走査で最初に有効と判定されたロケールが、そのテンプレートの既定になります。日本語を先に出したいなら、localesの先頭をja_JPにしてください。上の例がその並びです。判定は有効性の検査を通ったものが対象なので、先頭のロケールでmeta.jsontitleが欠けていれば、そのロケールは飛ばされて次が既定になります。なお、ディレクトリを並べる順序そのものは走査に影響しません。

読み込みに失敗するときの切り分け

インストールは成功したのにプラグインが効かない、という状況では、原因がビルド成果物側にあります。切り分けの前に、GROWIがどこでログを残すかを押さえておいてください。マニフェストが2箇所とも見つからないときは、GROWIはログを一切残さずそのプラグインの読み込みを飛ばします。マニフェストはあるのにエントリのキーを引けず例外になった場合だけ警告ログが出て、テーマの解決に失敗した場合はエラーログになります。つまり成果物そのものが届いていないケースでは、画面にもログにも何も出ません。

症状 疑う箇所 確認方法
インストール時にエラー growiPlugin定義 schemaVersionが4以上か
有効化しても無反応 マニフェスト未出力 build.manifestの設定
JSが配信されない エントリ名の相違 client-entry.tsxの有無
プレビューだけ効かない Preview側の未差し替え customGeneratePreviewOptions
テーマが選択肢に出ない themesの必須キー欠落 10キーの充足
テンプレートが出ない meta.jsonのtitle欠落 ロケール別ディレクトリ

見落としやすいのがビルド成果物のコミットです。GROWIが取得するのはソースではなくGitHubのアーカイブzipなので、dist.gitignoreに入ったままだと、GROWI側には成果物が届きません。実在する公開プラグインがdistをコミットしているのはこのためです。CIでビルドする運用にする場合も、インストール対象ブランチの先端に成果物が含まれている必要があります。

GitHub公開とプラグイン一覧への掲載

公開はGitHubへのpushで完了します。npmへの公開は不要です。加えてリポジトリのトピックにgrowi-pluginを追加すると、GROWIの公式プラグイン一覧ページへ自動的に掲載され、他の利用者が見つけられるようになります。掲載されたプラグインの導入手順は、一覧からリポジトリURLをコピーして管理画面に貼り付けるだけです。

配布にあたっては、ライセンスの明示とビルド成果物の同梱を先に済ませてください。本文で挙げた3つの公開プラグインは、いずれもpackage.jsonでMITを宣言しています。ただしリポジトリ直下にLICENSEファイルを置いているのはgrowi-plugin-datatablesだけで、残る2つはGitHub上のライセンス表示が空のままです。GitHubに認識させるにはファイルの設置が要ります。なお、GROWIのGitHub組織はweseekからgrowilabsへ移っています。古い記事や社内ドキュメントに残るweseekのURLはリダイレクトで到達できますが、新しくリンクを張るなら現行の組織名に合わせておくのが安全です。

そもそもGROWIを含むOSSの社内wikiをどう選び、どう構築するかという段階であれば、社内wikiの作り方:OSS構築の手順と権限・検索設計、スクラッチ開発の判断基準で権限設計や検索の考え方から整理できます。

よくある質問

GROWIプラグインはnpmで公開する必要がありますか

不要です。GROWIはGitHubのアーカイブzipを直接ダウンロードして展開するため、パッケージレジストリを経由しません。公開はGitHubリポジトリのpushで完結し、トピックにgrowi-pluginを付けると公式の一覧ページに載ります。

プラグインでサーバー側の処理を変更できますか

できません。スキーマv4のプラグインが提供できるのは、ブラウザで実行されるJavaScript、CSS、Markdownのテンプレートファイルに限られます。保存時のバリデーションや権限判定の変更は、GROWI本体への機能追加が必要です。更新を契機とした外部通知であれば、GROWIが備えるページ操作用のREST API(apiv3)とWebhookを使い、プラグインではなく外部側で組む形になります。

テンプレートが選択肢に出ないときは何を確認しますか

package.jsonlocalesに書いたロケール名と、dist配下のディレクトリ名が一致しているかをまず確認します。次に各ロケールのディレクトリにtemplate.mdmeta.jsonが揃っているか、meta.jsontitleが入っているかを見ます。titleが無いロケールは無効として扱われます。

インストール時にブランチやタグを指定できますか

ブランチは指定できます。管理画面のインストールフォームに入力欄があり、未入力ならmainが使われます。タグはサーバー側に受け口があるものの、フォーム側の送信処理がコメントアウトされているため管理画面からは選べません。バージョンを固定して検証したい場合は、タグではなく検証用のブランチを切って指定してください。

GROWI v8へ上げるとプラグインは作り直しになりますか

2026年7月27日リリースのv8.0.0のリリースノートに、プラグイン関連の変更は記載されていません。schemaVersion 4の定義はそのままです。ただしv8.0.0ではGROWI本体のモノレポがネイティブESMへ移行しているため、本体の内部実装に依存した実装をしている場合は、実際のバージョンで動作を確認してから展開してください。

関連記事

資料請求

RELATED POSTS 関連記事