Figmaのウィジェットは、Figma DesignやFigJamのキャンバスに配置して使う操作部品です。共同編集者が同じ表示と状態を共有するため、進捗の表示や投票などに使えます。ファイル内の処理を自動化するプラグインとは、表示場所と状態の管理方法が異なります。
この記事では、デスクトップアプリで開発を始める手順と、クリックで「確認待ち」「確認済み」を切り替える最小コードを紹介します。仕様は2026年10月3日に公式資料で確認しています。
プラグインとウィジェットの違い
| 観点 | プラグイン | ウィジェット |
|---|---|---|
| 起動と表示 | ユーザーが起動して処理する。必要なら別のHTML画面を開く | キャンバスに配置し、表示がファイルに残る |
| 得意な用途 | レイヤーの変換、検査、入出力 | 状態表示、投票、共同作業用の部品 |
| 画面 | iframeのUIは任意。UIなしの処理も作れる | TextやAutoLayoutなどWidget APIの部品で描画する |
| 状態 | 開発者が処理や保存先を設計する | useSyncedStateなどで共同編集者に同期する |
ウィジェットからPlugin APIも利用できます。ファイルを操作する処理と、キャンバス上の表示を組み合わせることが可能です。通常のプラグイン開発はFigmaプラグインの作り方で説明しています。
出典:Widget APIの概要、Plugin APIの概要。
開発環境を用意する
必要なのはFigmaのデスクトップアプリ、Node.jsとnpm、VS Codeなどのエディターです。コードはTypeScriptとJSXで書きます。ブラウザーでファイルを閲覧できても、ローカルのウィジェット開発はデスクトップアプリで進めます。
- デスクトップアプリでファイルを開き、メニューのWidgets → Development → New widgetを選びます。
- 名前を入力し、Simple widgetを選んで作業フォルダに保存します。
- 保存したフォルダをVS Codeで開き、ターミナルでnpm installを実行します。
- Run Build Taskからnpm: watchを起動します。
- widget-src/code.tsxを編集し、生成されたdist/code.jsをFigma側で読み込みます。
- Widgets → Developmentから開発中のウィジェットを挿入します。
生成されたJavaScriptを直接編集すると、次のビルドで変更が失われます。編集対象はTypeScriptのソースです。保存後も画面が変わらない場合は、ビルドのエラーとmanifest.jsonのmainが示すファイルを先に確認します。
出典:公式セットアップガイド。
manifest.jsonの設定
Figmaが生成したmanifest.jsonを基に編集します。特にidはFigmaから割り当てられる識別子なので、ほかの開発者のサンプルからコピーしません。
| 項目 | 設定と役割 |
|---|---|
| name | メニューに表示する名前 |
| id | Figmaが割り当てるID。既存ウィジェットの更新では同じIDを保つ |
| api / widgetApi | 利用するAPIの版。現行の基本設定はそれぞれ1.0.0 |
| containsWidget | trueにする |
| editorType | figma、figjam、または両方を指定する |
| main | 生成したJavaScriptへの相対パス。標準テンプレートではdist/code.js |
| documentAccess | 新規作成・新しい版の公開ではdynamic-pageが必要 |
| networkAccess | 外部通信の許可範囲。指定する場合はallowedDomainsが必要 |
外部通信をしないこの例では、networkAccessを次のように設定できます。
"networkAccess": {
"allowedDomains": ["none"]
}これはmanifest.jsonに追加する項目の抜粋です。ファイル全体をこの3行だけに置き換えないでください。networkAccess自体は公式のWidget Manifestで任意項目として扱われています。指定した場合のallowedDomainsには、少なくとも1つのパターンが必要です。
出典:Widget Manifest。
確認状態を切り替える最小コード
widget-src/code.tsxを次の内容にします。共有する値はcheckedだけで、外部通信やユーザー情報の取得はありません。
const { widget } = figma
const { AutoLayout, Text, useSyncedState } = widget
function ReviewStatus() {
const [checked, setChecked] = useSyncedState<boolean>("checked", false)
return (
<AutoLayout
direction="horizontal"
spacing={8}
padding={12}
cornerRadius={8}
fill={checked ? "#E8F5E9" : "#FFF3E0"}
onClick={() => setChecked(value => !value)}
>
<Text fontSize={16} fill="#222222">
{checked ? "確認済み" : "確認待ち"}
</Text>
</AutoLayout>
)
}
widget.register(ReviewStatus)registerに渡すのはコンポーネント関数です。widget.hで先に作った要素を渡す形ではありません。JSXの変換設定はテンプレートのjsxFactoryがfigma.widget.hになっていることを確認します。
このコードは公式型定義を使ったTypeScriptのビルドを確認しています。デスクトップアプリへの挿入と共同編集者間の同期は、本記事の実動作確認には含みません。
useSyncedStateとuseSyncedMapを使い分ける
useSyncedStateは表示状態や設定値のように、1つの値を更新する用途に向いています。複数の状態を定義するときは、それぞれに異なるキーを付けます。描画中に状態を更新せず、onClickなどのイベント内で更新します。
一方、投票数を単純にcount + 1で共有すると、同時操作で更新が重なり、片方の票が失われる場合があります。投票や参加者ごとの入力にはuseSyncedMapを使い、参加者ごとのキーで管理する設計を検討します。useSyncedMapも同じキーへの更新をすべて自動解決するものではありません。
上の例は1つの確認状態を共有する部品で、個人別の確認履歴や同時クリック数を記録する仕組みではありません。用途を広げる場合は、共有したい単位を先に決めます。
出典:Widget State、Widget State & Multiplayer、Undo/Redo。
表示されない・更新されないときの確認
- TypeScriptのエラーを解消し、mainで指定したJavaScriptが生成されているか確認する。
- editorTypeに、挿入先のfigmaまたはfigjamが含まれているか確認する。
- registerを1回呼び、コンポーネント関数を渡しているか確認する。
- 同期状態のキーを重複させず、更新をイベント内に置く。
- 外部通信を追加した場合は、通信先とnetworkAccessの許可範囲を照合する。
公開済みウィジェットで状態のキーを変更すると、以前の状態との対応が失われる可能性があります。改版時には古いファイル内のウィジェットも確認します。出典:Stability and Updates。
