diff --git a/shared/template/.gitignore b/shared/template/.gitignore
new file mode 100644
index 00000000..1b0b469d
--- /dev/null
+++ b/shared/template/.gitignore
@@ -0,0 +1 @@
+/.tauri
diff --git a/shared/template/android/.gitignore b/shared/template/android/.gitignore
new file mode 100644
index 00000000..c0f21ec2
--- /dev/null
+++ b/shared/template/android/.gitignore
@@ -0,0 +1,2 @@
+/build
+/.tauri
diff --git a/shared/template/android/build.gradle.kts b/shared/template/android/build.gradle.kts
new file mode 100644
index 00000000..804384c3
--- /dev/null
+++ b/shared/template/android/build.gradle.kts
@@ -0,0 +1,45 @@
+plugins {
+ id("com.android.library")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "{{android_package_id}}"
+ compileSdk = 32
+
+ defaultConfig {
+ minSdk = 24
+ targetSdk = 32
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+}
+
+dependencies {
+
+ implementation("androidx.core:core-ktx:1.9.0")
+ implementation("androidx.appcompat:appcompat:1.6.0")
+ implementation("com.google.android.material:material:1.7.0")
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ implementation(project(":tauri-android"))
+}
diff --git a/shared/template/android/proguard-rules.pro b/shared/template/android/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/shared/template/android/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/shared/template/android/settings.gradle b/shared/template/android/settings.gradle
new file mode 100644
index 00000000..14a752e4
--- /dev/null
+++ b/shared/template/android/settings.gradle
@@ -0,0 +1,2 @@
+include ':tauri-android'
+project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
diff --git a/shared/template/android/src/androidTest/java/ExampleInstrumentedTest.kt b/shared/template/android/src/androidTest/java/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..24e699cc
--- /dev/null
+++ b/shared/template/android/src/androidTest/java/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package {{android_package_id}}
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("{{android_package_id}}", appContext.packageName)
+ }
+}
diff --git a/shared/template/android/src/main/AndroidManifest.xml b/shared/template/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..9a40236b
--- /dev/null
+++ b/shared/template/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/shared/template/android/src/main/java/ExamplePlugin.kt b/shared/template/android/src/main/java/ExamplePlugin.kt
new file mode 100644
index 00000000..8ac527d8
--- /dev/null
+++ b/shared/template/android/src/main/java/ExamplePlugin.kt
@@ -0,0 +1,19 @@
+package {{android_package_id}}
+
+import android.app.Activity
+import app.tauri.annotation.Command
+import app.tauri.annotation.TauriPlugin
+import app.tauri.plugin.JSObject
+import app.tauri.plugin.Plugin
+import app.tauri.plugin.Invoke
+
+@TauriPlugin
+class ExamplePlugin(private val activity: Activity): Plugin(activity) {
+ @Command
+ fun ping(invoke: Invoke) {
+ val value = invoke.getString("value") ?: ""
+ val ret = JSObject()
+ ret.put("value", value)
+ invoke.resolve(ret)
+ }
+}
diff --git a/shared/template/android/src/test/java/ExampleUnitTest.kt b/shared/template/android/src/test/java/ExampleUnitTest.kt
new file mode 100644
index 00000000..50792fe6
--- /dev/null
+++ b/shared/template/android/src/test/java/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package {{android_package_id}}
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/shared/template/build.rs b/shared/template/build.rs
new file mode 100644
index 00000000..86ac3f0a
--- /dev/null
+++ b/shared/template/build.rs
@@ -0,0 +1,12 @@
+use std::process::exit;
+
+fn main() {
+ if let Err(error) = tauri_build::mobile::PluginBuilder::new()
+ .android_path("android")
+ .ios_path("ios")
+ .run()
+ {
+ println!("{error:#}");
+ exit(1);
+ }
+}
diff --git a/shared/template/ios/.gitignore b/shared/template/ios/.gitignore
new file mode 100644
index 00000000..5922fdaa
--- /dev/null
+++ b/shared/template/ios/.gitignore
@@ -0,0 +1,10 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
+xcuserdata/
+DerivedData/
+.swiftpm/config/registries.json
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
+.netrc
+Package.resolved
diff --git a/shared/template/ios/Package.swift b/shared/template/ios/Package.swift
new file mode 100644
index 00000000..ff9991fa
--- /dev/null
+++ b/shared/template/ios/Package.swift
@@ -0,0 +1,31 @@
+// swift-tools-version:5.3
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "tauri-plugin-{{ plugin_name }}",
+ platforms: [
+ .iOS(.v13),
+ ],
+ products: [
+ // Products define the executables and libraries a package produces, and make them visible to other packages.
+ .library(
+ name: "tauri-plugin-{{ plugin_name }}",
+ type: .static,
+ targets: ["tauri-plugin-{{ plugin_name }}"]),
+ ],
+ dependencies: [
+ .package(name: "Tauri", path: "../.tauri/tauri-api")
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+ // Targets can depend on other targets in this package, and on products in packages this package depends on.
+ .target(
+ name: "tauri-plugin-{{ plugin_name }}",
+ dependencies: [
+ .byName(name: "Tauri")
+ ],
+ path: "Sources")
+ ]
+)
diff --git a/shared/template/ios/README.md b/shared/template/ios/README.md
new file mode 100644
index 00000000..f4900bdd
--- /dev/null
+++ b/shared/template/ios/README.md
@@ -0,0 +1,3 @@
+# Tauri Plugin {{ plugin_name_original }}
+
+A description of this package.
diff --git a/shared/template/ios/Sources/ExamplePlugin.swift b/shared/template/ios/Sources/ExamplePlugin.swift
new file mode 100644
index 00000000..86140b74
--- /dev/null
+++ b/shared/template/ios/Sources/ExamplePlugin.swift
@@ -0,0 +1,16 @@
+import UIKit
+import WebKit
+import Tauri
+import SwiftRs
+
+class ExamplePlugin: Plugin {
+ @objc public func ping(_ invoke: Invoke) throws {
+ let value = invoke.getString("value")
+ invoke.resolve(["value": value as Any])
+ }
+}
+
+@_cdecl("init_plugin_{{ plugin_name_snake_case }}")
+func initPlugin(name: SRString, webview: WKWebView?) {
+ Tauri.registerPlugin(webview: webview, name: name.toString(), plugin: ExamplePlugin())
+}
diff --git a/shared/template/ios/Tests/PluginTests/PluginTests.swift b/shared/template/ios/Tests/PluginTests/PluginTests.swift
new file mode 100644
index 00000000..4f8e9ace
--- /dev/null
+++ b/shared/template/ios/Tests/PluginTests/PluginTests.swift
@@ -0,0 +1,8 @@
+import XCTest
+@testable import ExamplePlugin
+
+final class ExamplePluginTests: XCTestCase {
+ func testExample() throws {
+ let plugin = ExamplePlugin()
+ }
+}
diff --git a/shared/template/src/commands.rs b/shared/template/src/commands.rs
new file mode 100644
index 00000000..d0daba61
--- /dev/null
+++ b/shared/template/src/commands.rs
@@ -0,0 +1,17 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use tauri::{AppHandle, command, Runtime, State, Window};
+
+use crate::{MyState, Result};
+
+#[command]
+pub(crate) async fn execute(
+ _app: AppHandle,
+ _window: Window,
+ state: State<'_, MyState>,
+) -> Result {
+ state.0.lock().unwrap().insert("key".into(), "value".into());
+ Ok("success".to_string())
+}
diff --git a/shared/template/src/desktop.rs b/shared/template/src/desktop.rs
new file mode 100644
index 00000000..28866220
--- /dev/null
+++ b/shared/template/src/desktop.rs
@@ -0,0 +1,26 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use serde::de::DeserializeOwned;
+use tauri::{plugin::PluginApi, AppHandle, Runtime};
+
+use crate::models::*;
+
+pub fn init(
+ app: &AppHandle,
+ _api: PluginApi,
+) -> crate::Result<{{ plugin_name_pascal_case }}> {
+ Ok({{ plugin_name_pascal_case }}(app.clone()))
+}
+
+/// Access to the {{ plugin_name }} APIs.
+pub struct {{ plugin_name_pascal_case }}(AppHandle);
+
+impl {{ plugin_name_pascal_case }} {
+ pub fn ping(&self, payload: PingRequest) -> crate::Result {
+ Ok(PingResponse {
+ value: payload.value,
+ })
+ }
+}
diff --git a/shared/template/src/error.rs b/shared/template/src/error.rs
new file mode 100644
index 00000000..339e763b
--- /dev/null
+++ b/shared/template/src/error.rs
@@ -0,0 +1,25 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use serde::{ser::Serializer, Serialize};
+
+pub type Result = std::result::Result;
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error(transparent)]
+ Io(#[from] std::io::Error),
+ #[cfg(mobile)]
+ #[error(transparent)]
+ PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
+}
+
+impl Serialize for Error {
+ fn serialize(&self, serializer: S) -> std::result::Result
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(self.to_string().as_ref())
+ }
+}
diff --git a/shared/template/src/lib.rs b/shared/template/src/lib.rs
index e69de29b..c65cf18e 100644
--- a/shared/template/src/lib.rs
+++ b/shared/template/src/lib.rs
@@ -0,0 +1,60 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use tauri::{
+ plugin::{Builder, TauriPlugin},
+ Manager, Runtime,
+};
+
+use std::{collections::HashMap, sync::Mutex};
+
+pub use models::*;
+
+#[cfg(desktop)]
+mod desktop;
+#[cfg(mobile)]
+mod mobile;
+
+mod commands;
+mod error;
+mod models;
+
+pub use error::{Error, Result};
+
+#[cfg(desktop)]
+use desktop::{{ plugin_name_pascal_case }};
+#[cfg(mobile)]
+use mobile::{{ plugin_name_pascal_case }};
+
+#[derive(Default)]
+struct MyState(Mutex>);
+
+/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the {{ plugin_name }} APIs.
+pub trait {{ plugin_name_pascal_case }}Ext {
+ fn {{ plugin_name_snake_case }}(&self) -> &{{ plugin_name_pascal_case }};
+}
+
+impl> crate::{{ plugin_name_pascal_case }}Ext for T {
+ fn {{ plugin_name_snake_case }}(&self) -> &{{ plugin_name_pascal_case }} {
+ self.state::<{{ plugin_name_pascal_case }}>().inner()
+ }
+}
+
+/// Initializes the plugin.
+pub fn init() -> TauriPlugin {
+ Builder::new("{{ plugin_name }}")
+ .invoke_handler(tauri::generate_handler![commands::execute])
+ .setup(|app, api| {
+ #[cfg(mobile)]
+ let {{ plugin_name_snake_case }} = mobile::init(app, api)?;
+ #[cfg(desktop)]
+ let {{ plugin_name_snake_case }} = desktop::init(app, api)?;
+ app.manage({{ plugin_name_snake_case }});
+
+ // manage state so it is accessible by the commands
+ app.manage(MyState::default());
+ Ok(())
+ })
+ .build()
+}
diff --git a/shared/template/src/mobile.rs b/shared/template/src/mobile.rs
new file mode 100644
index 00000000..fa358be7
--- /dev/null
+++ b/shared/template/src/mobile.rs
@@ -0,0 +1,41 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use serde::de::DeserializeOwned;
+use tauri::{
+ plugin::{PluginApi, PluginHandle},
+ AppHandle, Runtime,
+};
+
+use crate::models::*;
+
+#[cfg(target_os = "android")]
+const PLUGIN_IDENTIFIER: &str = "{{ android_package_id }}";
+
+#[cfg(target_os = "ios")]
+tauri::ios_plugin_binding!(init_plugin_{{ plugin_name }});
+
+// initializes the Kotlin or Swift plugin classes
+pub fn init(
+ _app: &AppHandle,
+ api: PluginApi,
+) -> crate::Result<{{ plugin_name_pascal_case }}> {
+ #[cfg(target_os = "android")]
+ let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ExamplePlugin")?;
+ #[cfg(target_os = "ios")]
+ let handle = api.register_ios_plugin(init_plugin_{{ plugin_name }})?;
+ Ok({{ plugin_name_pascal_case }}(handle))
+}
+
+/// Access to the {{ plugin_name }} APIs.
+pub struct {{ plugin_name_pascal_case }}(PluginHandle);
+
+impl {{ plugin_name_pascal_case }} {
+ pub fn ping(&self, payload: PingRequest) -> crate::Result {
+ self
+ .0
+ .run_mobile_plugin("ping", payload)
+ .map_err(Into::into)
+ }
+}
diff --git a/shared/template/src/models.rs b/shared/template/src/models.rs
new file mode 100644
index 00000000..d50dcf91
--- /dev/null
+++ b/shared/template/src/models.rs
@@ -0,0 +1,17 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PingRequest {
+ pub value: Option,
+}
+
+#[derive(Debug, Clone, Default, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PingResponse {
+ pub value: Option,
+}