0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Tauri 2.0 プラグイン開発の初期セットアップ手順と最小実装(モバイル向け)

Last updated at Posted at 2026-01-31

デスクトップのプラグインの作成方法も包含していますが,メインは iOS・Android としています

本記事に誤りを見つけた場合は,コメントにて優しく教えていただけると助かります

概要

本記事では Tauri 2.0 においてプラグインを作成する方法を紹介します

大筋は↓に沿って実装しています
https://v2.tauri.app/ja/develop/plugins/

はじめに

Tauri 2.0 ではモバイルアプリを作成できるようになりました :tada:
一方で,Tauri が提供するAPIのみでは不十分なこともあり,Swift や Kotlin (以降,ネイティブコード)を直接触りたいこともあると思います.
しかし,通常の Tauri アプリではネイティブコードを使用することはできません.
そこで,自作プラグインを作成することでネイティブコードを触れるようにします!!

環境

MacOS 14.6.1 (Sonoma)
M3 Pro

前提

Node.js や Rust,Android Studio,Xcode などがインストール済みであることを前提としています.
詳細については↓を参照してください

Tauri ではいろんなツールを使用できますが,今回は pnpm を使用しています.
他のツールを使用したい場合は↓を参考に,適宜置き換えてください

0. Tauri プロジェクトのセットアップ

既存の Tauri プロジェクトに追加する場合や,プラグインの開発だけの場合であれば,このセクションは不要です.
一応,私の実装検証環境が再現できるように記述しています

Tauri プロジェクトの作成

以下のコマンドで pnpm を使用してプロジェクトを作成できます

pnpm create tauri-app # pnpm の場合

参考: https://v2.tauri.app/ja/start/create-project/

コマンド実行時の記入・選択例

$ pnpm create tauri-app
Project name: test
Identifier (com.satooru.test) › dev.satooru.tauri-test-01
Choose which language to use for your frontend: TypeScript / JavaScript
Choose your package manager: pnpm
Choose your UI template: React
Choose your UI flavor: TypeScript

これでプロジェクトが作成できました :tada:

パッケージのインストール

忘れずにインストールしておきましょう

pnpm install

Tauri アプリの動作確認

デスクトップ

pnpm tauri dev

参考: https://v2.tauri.app/ja/start/create-project/

このような画面が開ければOKです
スクリーンショット 2026-01-31 18.32.02.png

スクリーンショット 2026-01-31 18.38.17.png

動作確認(iOS)

Tauri アプリ側で iOS アプリを初期化します.
基本的に一度だけで良いです

pnpm tauri ios init

Xcodeで署名をします.
Xcodeで src-tauri/gen/apple/<app-name>.xcodeproj を開きます

スクリーンショット 2026-02-01 1.11.47.png

TARGETS > test_iOS > Signing & Capabilities を開いて署名します
署名の手順は省略します
署名の手順は Preparing your app for distribution - Apple Developer などを参考にしてください

スクリーンショット 2026-02-01 1.12.59.png

エミュレータで動作確認をします

pnpm tauri ios dev
pnpm tauri ios dev "iPhone 16" # シミュレータ端末を指定する場合

このような画面になればOKです

動作確認(Android)

Tauri アプリ側で Android アプリを初期化します.
基本的に一度だけで良いです

pnpm tauri android init

シミュレータで動作確認をします

pnpm tauri android dev 

このような画面になればOKです

1. Tauri プラグインのセットアップ

セットアップ

以下のコマンドで新しくプラグインを作成できます
実行位置はどこでも問題ありませんが,使用したいプロジェクト直下で作成すると後のプラグインの使用が楽になります

npx @tauri-apps/cli plugin new <plugin-name>

Tauri プラグイン名には tauri-plugin- という接頭辞が推奨されています.
プラグイン名には自動で tauri-plugin- が着くようになっているため <plugin-name> に含める必要はありません

コマンド実行例

$ npx @tauri-apps/cli plugin new test

以降の tauti-plugin-test は各々のプラグイン名に置き換えて考えてください

ディレクトリの説明

プラグイン作成後は以下のようなディレクトリが作成されます

$ tree -L 2 ./tauri-plugin-test 
./tauri-plugin-test
├── Cargo.toml
├── README.md
├── build.rs
├── examples
│   └── tauri-app
├── guest-js
│   └── index.ts
├── package.json
├── permissions
│   └── default.toml
├── rollup.config.js
├── src
│   ├── commands.rs
│   ├── desktop.rs
│   ├── error.rs
│   ├── lib.rs
│   ├── mobile.rs
│   └── models.rs
└── tsconfig.json

examples

プラグインの使用例を実装します
自分用のプラグインであれば不要なので削除して問題ありません

guest-js

Tauri アプリ内の WebView 側から呼び出しやすくするための JavaScript(TypeScript) を実装します

例えば WebView からプラグインの Rust を呼び出すとき,invoke('plugin:<plugin-name>|<command-name>') と実行します.
これには型がついておらず使い勝手が悪い状態です.
これを Tauri アプリで書いても良いですが,プラグイン内の guest-js で書いてあげると責任が明確化して WebView での使い回しが良くなります

permissions

プラグインで実装されたコマンドの権限設定を記述します.

初期設定では default.toml があります.
Tauri アプリ側の src-tauri/capabilities/default.json から <plugin-name>:default を許可すると,default.toml に記述された権限が設定されます

後々コマンドを追加した場合 permissions/autogenerated/commands/*.toml が自動生成されます.
これにより,default.toml に権限 (許可: allow-<command-name>,拒否: deny-<command-name>) を設定できるようになります
デフォルトでは拒否されているので,許可する必要があります(後述)

src

Rust を記述します

プラグインの初期設定・コマンドの実装・ネイティブコードの呼び出し等々を実装します

2. WebView から Rust コマンドを呼び出す

プラグイン側

文字列を受け取り,文字数を返すシンプルなコマンドを Rust で実装して WebView から呼び出してみます

コマンドの実装

tauri-plugin-test/src/commands.rs に新しくコマンドを作成します

tauri-plugin-test/src/commands.rs
#[command]
pub(crate) async fn count_chars<R: Runtime>(_app: AppHandle<R>, payload: CountCharsRequest) -> Result<i32> 
{
    let count = payload.text_value.chars().count() as i32;
    Ok(count)
}

tauri-plugin-test/src/models.rs に WebView から受け取る値の型として CountCharsRequest を定義します

tauri-plugin-test/src/models.rs
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CountCharsRequest {
  pub text_value: String,
}

#[serde(rename_all = "camelCase")] のお陰で Rust 側では snale_case,WebView(TS)側では camelCase に自動で変換してくれます ヤッター
text_value という絶妙なフィールド名なのは,camelCase に変換されることを確認するためです)

commands.rs では use crate::models::*; で全てインポートされているので CountCharsRequest を改めてインポートする必要はありません

コマンドの解放

プラグインからコマンドを呼び出せるようにします

tauri-plugin-test/src/lib.rscount_chars コマンドを登録します

tauri-plugin-test/src/lib.rs
pub fn init<R: Runtime>() -> TauriPlugin<R> {
    Builder::new("test")
        .invoke_handler(tauri::generate_handler![
            commands::ping,
+           commands::count_chars
        ])
        .setup(|app, api| {
            #[cfg(mobile)]
            let test = mobile::init(app, api)?;
            #[cfg(desktop)]
            let test = desktop::init(app, api)?;
            app.manage(test);
            Ok(())
        })
        .build()
}

参考: https://v2.tauri.app/ja/develop/plugins/#コマンドの追加

コマンドの権限設定

権限を自動生成する設定を追加します.

tauri-plugin-test/build.rs に↓のようにコマンドを追加します

tauri-plugin-test/build.rs
const COMMANDS: &[&str] = &["ping", "count_chars"];

参考: https://v2.tauri.app/ja/develop/plugins/#コマンドのアクセス権

権限設定ファイルを生成します.
tauri-plugin-<plugin-name> ディレクトリで↓を実行します

cargo build

tauri-plugin-test/permissions/autogenerated/commands/count_chars.toml が生成されたのを確認できるはずです

Tauri アプリにプラグインを追加した場合,Tauri アプリをビルドするとプラグインもビルドされるため,上記の cargo build を実行する必要はありません.

Tauri アプリを再ビルドせずに権限設定ファイルを生成したい場合などには使うと良いかもしれません

デフォルトの権限リストに追加します.
tauri-plugin-test/permissions/default.toml に↓のように権限を追加します

tauri-plugin-test/permissions/default.toml
[default]
description = "Default permissions for the plugin"
permissions = ["allow-ping", "allow-count-chars"]

guest-js の実装

tauri-plugin-test/guest-js/index.ts にて↓の関数を追加します

tauri-plugin-test/guest-js/index.ts
export interface CountCharsResponse {
  count: number;
}
export async function countChars(text: string): Promise<CountCharsResponse> {
  return await invoke<CountCharsResponse>("plugin:test|count_chars", {
    payload: {
      textValue: text,
    },
  });
}

Rust側の CountCharsRequest では text_value と snake_case だったのに対し,ここでは textValue と camelCase になっているのが分かります

プラグイン側でのインストールも忘れずにしてください

cd tauri-plugin-test
pnpm install

guest-js を変更時は忘れずに↓を実行して guest-js のビルドをしてください

cd tauri-plugin-test
pnpm build

cargo build は Rust側のビルドのみをしています.
guest-js を変更時は毎回必ず pnpm build をする必要があります.

これでプラグイン側での設定は完了です :tada:

Tauri アプリ側

Rust側の依存関係にプラグインを追加

Cargo.tomldependencies に↓のように追加します

Cargo.toml
 [dependencies]
+ tauri-plugin-test = { path = "../tauri-plugin-test" }

WebView側の依存関係にプラグインを追加

package.jsondependencies に↓のように追加します

package.json
  "dependencies": {
+   "tauri-plugin-test-api": "file:./tauri-plugin-test"
  },

パッケージ名は tauri-plugin-<plugin-name>-api が推奨されています
また,可能であれば npmスコープを付けることを推奨されていますが,本記事では省略します

参考: https://v2.tauri.app/ja/develop/plugins/#命名規則

Tauri アプリ内でもインストールするのを忘れないようにしましょう

pnpm install

Tauri アプリ内でのプラグインの初期化

src-tauri/src/lib.rstauri_plugin_test を呼び出して初期化します

src-tauri/src/lib.rs
 #[cfg_attr(mobile, tauri::mobile_entry_point)]
 pub fn run() {
     tauri::Builder::default()
         .plugin(tauri_plugin_opener::init())
+        .plugin(tauri_plugin_test::init())
         .invoke_handler(tauri::generate_handler![greet])
         .run(tauri::generate_context!())
         .expect("error while running tauri application");
 }

権限の付与

src-tauri/capabilities/default.json でプラグインのコマンドを実行できるように権限を追加します

一度 Tarui アプリを立ち上げて実行しておきます.
これにより,test プラグインが初期化され schema などに権限名が追加されます.
schema に権限名がない(補完が効かない)場合や,実行時に権限名がないとエラーが出た場合は大体これのせいです

pnpm tauri dev
src-tauri/capabilities/default.json
 {
   "$schema": "../gen/schemas/desktop-schema.json",
   "identifier": "default",
   "description": "Capability for the main window",
   "windows": ["main"],
   "permissions": [
     "core:default",
     "opener:default",
+    "test:default"
   ]
 }

test:defaut ではなく test:allow-count-chars のようにコマンドごとに権限を与えることもできます

WebView から呼び出す

今回は React を選択したので React の例を挙げます

App.tsx
import { useState } from "react";
import { countChars } from "tauri-plugin-test-api";
import "./App.css";

function App() {
  const [text, setText] = useState("");
  const [count, setCount] = useState(0);

  async function onChange(e: React.ChangeEvent<HTMLInputElement>) {
    try {
      const value = e.currentTarget.value;
      setText(value);
      const charCountRes = await countChars(value);
      setCount(charCountRes.count);
    } catch (error) {
      console.error("Error counting characters:", error);
    }
  }

  return (
    <main className="container">
      <input type="text" onChange={onChange} value={text} />
      <p>Character count: {count}</p>
    </main>
  );
}

export default App;

動作確認

pnpm tauri ios dev
pnpm tauri ios dev "iPhone 16" # シミュレータ端末を指定する場合

無事に文字数が出ればOKです!! ワーイ
スクリーンショット 2026-02-01 0.22.28.png

うまくいかない場合は,node_modulesをインストールしなおしたり,権限を確認したりすると良いです
コンソールにエラーが出ているはずなので要チェックです

3. WebView からネイティブコードを呼び出す

Rustで実装した文字数を数えるコマンドを Swift や Kotlin で実装してみます.
ただし,WebView からネイティブコードを直接呼び出すことはできず,一度 Rust 側を経由する必要があります

Swift(iOS)

iOS機能の追加

↓のコマンドを実行して iOS 機能を追加します.
もし既に tauri-plugin-test/ios フォルダがある場合は作成されません.
作り直したい場合は tauri-plugin-test/ios を抹消する必要があります.
プラグイン作成後にいつの間にか作られていることがありますが,(多分)不要なファイルが沢山あるので作り直すとよいです

cd tauri-plugin-test
npx @tauri-apps/cli plugin ios init

参考: https://v2.tauri.app/ja/develop/plugins/develop-mobile/#プラグイン・プロジェクトの初期化

このコマンドを実行するとファイルにコードを追加しろと出てきますが,プラグイン作成時に既に書かれているものもあります.
また,指示されたファイルと作成時に書かれているファイルが違うものもあります.重複しないように気をつけてください

本記事に沿えば問題ないはずです

これにより tauri-plugin-test/ios フォルダが作成されます.
この中でも主に tauri-plugin-test/ios/Sources/ExamplePlugin.swift を触ります

ExamplePlugin という名前は可愛くないので自分のプラグイン名に変えるとよいですが,ここでは省略します

コマンドの実装

ExamplePlugin.swift に↓のようにコマンドを実装します

tauri-plugin-test/ios/Sources/ExamplePlugin.swift
+class CountCharsRequest: Decodable {
+  let text_value: String
+}

 class ExamplePlugin: Plugin {
   @objc public func ping(_ invoke: Invoke) throws {
     let args = try invoke.parseArgs(PingArgs.self)
     invoke.resolve(["value": args.value ?? ""])
   }
 
+  @objc public func countChars(_ invoke: Invoke) throws {
+    let args = try invoke.parseArgs(CountCharsRequest.self)
+    let count = args.textValue.count
+    invoke.resolve(["count": count])
+  }
+}

返り値がない場合でもメソッドの最後に invoke.resolve() を実行する必要があります.
さもなければ WebView 側で永遠に Promise が解決されない状態となります

iOS ディレクトリの指定(基本的に不要)

Tauri プラグインに iOS ディレクトリの位置を教えてあげます.
ただし,基本的にはプラグイン作成時に追加されているはずなので不要です

tauri-plugin-test/build.rs
 const COMMANDS: &[&str] = &["ping", "count_chars"];

 fn main() {
   tauri_plugin::Builder::new(COMMANDS)
     .android_path("android")
+    .ios_path("ios")
     .build();
 }

iOS 機能の初期化(基本的に不要)

iOS 専用のプラグイン初期化のコードを追加します.
ただし,基本的にはプラグイン作成時に追加されているはずなので不要です

tauri-plugin-test/src/mobile.rs
#[cfg(target_os = "ios")]
tauri::ios_plugin_binding!(init_plugin_test);

iOS 機能の登録(基本的に不要)

iOS 専用のプラグイン登録のコードを追加します.
ただし,基本的にはプラグイン作成時に追加されているはずなので不要です

 pub fn init<R: Runtime, C: DeserializeOwned>(
   _app: &AppHandle<R>,
   api: PluginApi<R, C>,
 ) -> crate::Result<Test<R>> {
   #[cfg(target_os = "android")]
   let handle = api.register_android_plugin("", "ExamplePlugin")?;
+  #[cfg(target_os = "ios")]
+  let handle = api.register_ios_plugin(init_plugin_test)?;
+  Ok(Test(handle))
}

Rust側でネイティブコードのコマンドの紐付け

Rust側でネイティブコードのコマンドを紐づけます

mobile.rs でネイティブコードのコマンドを呼び出すメソッドを追加します

tauri-plugin-test/src/mobile.rs
 impl<R: Runtime> Test<R> {
   pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
     self
       .0
       .run_mobile_plugin("ping", payload)
       .map_err(Into::into)
   }
 
+  pub fn count_chars(&self, payload: CountCharsRequest) -> crate::Result<i32> {
+    self
+      .0
+      .run_mobile_plugin("countChars", payload)
+      .map_err(Into::into)
+  }
 }

このとき,run_mobile_plugin の第一引数は,ネイティブコードのメソッド名と完全一致させる必要があります

Rust側のコマンドの変更

この変更をすると,デスクトップアプリ側では count_chars が見つからずエラーになります.
デスクトップ側では tauri-plugin-test/src/desktop.rs に count_chars メソッドを実装することで対応できますが,ここでは省略します

commands.rs を↓のように書き換えます.

tauri-plugin-test/src/commands.rs
#[command]
pub(crate) async fn count_chars<R: Runtime>(
    app: AppHandle<R>,
    payload: CountCharsRequest,
) -> Result<i32> {
    app.test().count_chars(payload)
}

動作確認(iOS)

pnpm tauri ios dev 

ちゃんと動けばOKです ワーイ

Android(Kotlin)

Android機能の追加

↓のコマンドを実行して Android 機能を追加します.
もし既に tauri-plugin-test/android フォルダがある場合は作成されません.
作り直したい場合は tauri-plugin-test/ios を抹消する必要があります.
プラグイン作成後にいつの間にか作られていることがありますが,(多分)不要なファイルが沢山あるので作り直すとよいです

cd tauri-plugin-test
npx @tauri-apps/cli plugin android init

コマンド実行時の入力例

$ npx @tauri-apps/cli plugin android init
? What should be the Android Package ID for your plugin?: dev.satooru.testplugin01

ここで入力したパッケージIDは後ほど使うので覚えておいてください
(忘れた場合でも tauri-plugin-test/android/build.gradle.kts に書かれています)

参考: https://v2.tauri.app/ja/develop/plugins/develop-mobile/#プラグイン・プロジェクトの初期化

このコマンドを実行するとファイルにコードを追加しろと出てきますが,プラグイン作成時に既に書かれているものもあります.
また,指示されたファイルと作成時に書かれているファイルが違うものもあります.重複しないように気をつけてください

本記事に沿えば問題ないはずです

これにより tauri-plugin-test/android フォルダが作成されます.
この中でも主に tauri-plugin-test/android/src/main/java/ExamplePlugin.kt を触ります

ExamplePlugin という名前は可愛くないので自分のプラグイン名に変えるとよいですが,ここでは省略します

コマンドの実装

ExamplePlugin.kt に↓のようにコマンドを実装します

tauri-plugin-test/ios/Sources/ExamplePlugin.swift
+@InvokeArg
+class CountCharsRequest {
+  var textValue: String? = null
+}
 
 @TauriPlugin
 class ExamplePlugin(private val activity: Activity): Plugin(activity) {
     private val implementation = Example()
 
     @Command
     fun ping(invoke: Invoke) {
         val args = invoke.parseArgs(PingArgs::class.java)
 
         val ret = JSObject()
         ret.put("value", implementation.pong(args.value ?: "default value :("))
         invoke.resolve(ret)
     }
 
+    @Command
+    fun countChars(invoke: Invoke) {
+        val args = invoke.parseArgs(CountCharsRequest::class.java)
+        val count = args.textValue?.length ?: 0
+
+        val ret = JSObject()
+        ret.put("count", count)
+        invoke.resolve(ret)
+    }
 }

※本来はロジックを Example() 側に実装して ExamplePlugin() は橋渡しに専念させるべき(らしい by ChatGPT)です

返り値がない場合でもメソッドの最後に invoke.resolve() を実行する必要があります.
さもなければ WebView 側で永遠に Promise が解決されない状態となります

Android ディレクトリの指定(基本的に不要)

Tauri プラグインに Android ディレクトリの位置を教えてあげます.
ただし,基本的にはプラグイン作成時に追加されているはずなので不要です

tauri-plugin-test/build.rs
 const COMMANDS: &[&str] = &["ping", "count_chars"];

 fn main() {
   tauri_plugin::Builder::new(COMMANDS)
+    .android_path("android")
     .ios_path("ios")
     .build();
 }

Android 機能の登録

iOS 専用のプラグイン登録のコードを追加します.
基本的にはプラグイン作成時に追加されていますが,register_android_plugin() の第一引数を書き換える必要があります
<package-id> には #Android機能の追加 時に入力したパッケージIDを指定してください

 pub fn init<R: Runtime, C: DeserializeOwned>(
   _app: &AppHandle<R>,
   api: PluginApi<R, C>,
 ) -> crate::Result<Test<R>> {
+  #[cfg(target_os = "android")]
+  let handle = api.register_android_plugin("<package-id>", "ExamplePlugin")?;
   #[cfg(target_os = "ios")]
   let handle = api.register_ios_plugin(init_plugin_test)?;
   Ok(Test(handle))
}

Rust側でネイティブコードのコマンドの紐付け

Swift(iOS) の方で対応済みの場合はスキップしてください
iOS をスキップした方向けに再掲しています

Rust側でネイティブコードのコマンドを紐づけます

mobile.rs でネイティブコードのコマンドを呼び出すメソッドを追加します

tauri-plugin-test/src/mobile.rs
 impl<R: Runtime> Test<R> {
   pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
     self
       .0
       .run_mobile_plugin("ping", payload)
       .map_err(Into::into)
   }
 
+  pub fn count_chars(&self, payload: CountCharsRequest) -> crate::Result<i32> {
+    self
+      .0
+      .run_mobile_plugin("countChars", payload)
+      .map_err(Into::into)
+  }
 }

このとき,run_mobile_plugin の第一引数は,ネイティブコードのメソッド名と完全一致させる必要があります

Rust側のコマンドの変更

Swift(iOS) の方で対応済みの場合はスキップしてください
iOS をスキップした方向けに再掲しています

この変更をすると,デスクトップアプリ側では count_chars が見つからずエラーになります.
デスクトップ側では tauri-plugin-test/src/desktop.rs に count_chars メソッドを実装することで対応できますが,ここでは省略します

commands.rs を↓のように書き換えます.

tauri-plugin-test/src/commands.rs
#[command]
pub(crate) async fn count_chars<R: Runtime>(
    app: AppHandle<R>,
    payload: CountCharsRequest,
) -> Result<i32> {
    app.test().count_chars(payload)
}

pnpm tauri android dev

無事に文字数が出ればOKです!! ヤッター

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?