Plugins can run native mobile code written in Kotlin (or Java) and Swift. The default plugin template includes an Android library project using Kotlin and a Swift package including an example mobile command showing how to trigger its execution from Rust code.
Follow the steps in the Plugin Development guide to initialize a new plugin project.
If you have an existing plugin and would like to add Android or iOS capabilities to it, you can use plugin android init
and plugin ios init
to bootstrap the mobile library projects and guide you through the changes needed.
The default plugin template splits the plugin’s implementation into two separate modules: desktop.rs
and mobile.rs
.
The desktop implementation uses Rust code to implement a functionality, while the mobile implementation sends a message to the native mobile code to execute a function and get a result back. If shared logic is needed across both implementations, it can be defined in lib.rs
:
impl <R : Runtime> < plugin - name ><R> {
pub fn do_something ( &self ) {
// do something that is a shared implementation between desktop and mobile
This implementation simplifies the process of sharing an API that can be used both by commands and Rust code.
A Tauri plugin for Android is defined as a Kotlin class that extends app.tauri.plugin.Plugin
and is annotated with app.tauri.annotation.TauriPlugin
. Each method annotated with app.tauri.annotation.Command
can be called by Rust or JavaScript.
Tauri uses Kotlin by default for the Android plugin implementation, but you can switch to Java if you prefer. After generating a plugin, right click the Kotlin plugin class in Android Studio and select the “Convert Kotlin file to Java file” option from the menu. Android Studio will guide you through the project migration to Java.
A Tauri plugin for iOS is defined as a Swift class that extends the Plugin
class from the Tauri
package. Each function with the @objc
attribute and the (_ invoke: Invoke)
parameter (for example @objc private func download(_ invoke: Invoke) { }
) can be called by Rust or JavaScript.
The plugin is defined as a Swift package so that you can use its package manager to manage dependencies.
Refer to the Plugin Configuration section of the Plugin Development guide for more details on developing plugin configurations.
The plugin instance on mobile has a getter for the plugin configuration:
import android.app.Activity
import android.webkit.WebView
import app.tauri.annotation.TauriPlugin
import app.tauri.annotation.InvokeArg
class ExamplePlugin( private val activity: Activity): Plugin(activity) {
private var timeout: Int? = 3000
override fun load (webView: WebView) {
getConfig (Config:: class .java). let {
this .timeout = it.timeout
struct Config: Decodable {
class ExamplePlugin: Plugin {
@objc public override func load ( webview : WKWebView ) {
let config = try parseConfig ( Config. self )
self . timeout = config. timeout
Plugins can hook into several lifecycle events:
load : When the plugin is loaded into the web view
onNewIntent : Android only, when the activity is re-launched
There are also the additional lifecycle events for plugins in the Plugin Development guide.
When : When the plugin is loaded into the web view
Why : Execute plugin initialization code
import android.app.Activity
import android.webkit.WebView
import app.tauri.annotation.TauriPlugin
class ExamplePlugin( private val activity: Activity): Plugin(activity) {
override fun load (webView: WebView) {
// perform plugin setup here
class ExamplePlugin: Plugin {
@objc public override func load ( webview : WKWebView ) {
let timeout = self . config [ " timeout " ] as? Int ?? 30
Note : This is only available on Android.
When : When the activity is re-launched. See Activity#onNewIntent for more information.
Why : Handle application re-launch such as when a notification is clicked or a deep link is accessed.
import android.app.Activity
import android.content.Intent
import app.tauri.annotation.TauriPlugin
class ExamplePlugin( private val activity: Activity): Plugin(activity) {
override fun onNewIntent (intent: Intent) {
// handle new intent event
There is a plugin class inside the respective mobile projects where commands can be defined that can be called by the Rust code:
import android.app.Activity
import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin
class ExamplePlugin( private val activity: Activity): Plugin(activity) {
fun openCamera (invoke: Invoke) {
ret. put ( "path" , "/path/to/photo.jpg" )
If you want to use a Kotlin suspend
function, you need to use a custom coroutine scope
import android.app.Activity
import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin
// Change to Dispatchers.IO if it is intended for fetching data
val scope = CoroutineScope (Dispatchers.Default + SupervisorJob ())
class ExamplePlugin( private val activity: Activity): Plugin(activity) {
fun openCamera (invoke: Invoke) {
private suspend fun openCameraInner (invoke: Invoke) {
ret. put ( "path" , "/path/to/photo.jpg" )
class ExamplePlugin: Plugin {
@objc public func openCamera ( _ invoke : Invoke ) throws {
invoke. resolve ( [ " path " : " /path/to/photo.jpg " ] )
Use the tauri::plugin::PluginHandle
to call a mobile command from Rust:
use serde :: {Deserialize, Serialize};
#[serde(rename_all = " camelCase " )]
pub struct CameraRequest {
impl <R : Runtime> < plugin - name ; pascal - case ><R> {
pub fn open_camera ( &self , payload : CameraRequest) -> crate :: Result<Photo> {
. run_mobile_plugin ( " openCamera " , payload )
Arguments are serialized to commands and can be parsed on the mobile plugin with the Invoke::parseArgs
function, taking a class describing the argument object.
On Android, the arguments are defined as a class annotated with @app.tauri.annotation.InvokeArg
. Inner objects must also be annotated:
import android.app.Activity
import android.webkit.WebView
import app.tauri.annotation.Command
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.TauriPlugin
internal class OpenAppArgs {
lateinit var name: String
internal class OpenArgs {
lateinit var requiredArg: String
var allowEdit: Boolean = false
var app: OpenAppArgs? = null
class ExamplePlugin( private val activity: Activity): Plugin(activity) {
fun openCamera (invoke: Invoke) {
val args = invoke. parseArgs (OpenArgs:: class .java)
Note
Optional arguments are defined as var <argumentName>: Type? = null
Arguments with default values are defined as var <argumentName>: Type = <default-value>
Required arguments are defined as lateinit var <argumentName>: Type
On iOS, the arguments are defined as a class that inherits Decodable
. Inner objects must also inherit the Decodable protocol:
class OpenAppArgs: Decodable {
class OpenArgs: Decodable {
class ExamplePlugin: Plugin {
@objc public func openCamera ( _ invoke : Invoke ) throws {
let args = try invoke. parseArgs ( OpenArgs. self )
invoke. resolve ( [ " path " : " /path/to/photo.jpg " ] )
Note
Optional arguments are defined as var <argumentName>: Type?
Arguments with default values are NOT supported.
Use a nullable type and set the default value on the command function instead.
Required arguments are defined as let <argumentName>: Type
If a plugin requires permissions from the end user, Tauri simplifies the process of checking and requesting permissions.
First define the list of permissions needed and an alias to identify each group in code. This is done inside the TauriPlugin
annotation:
Permission (strings = [Manifest.permission.POST_NOTIFICATIONS], alias = "postNotification" )
class ExamplePlugin( private val activity: Activity): Plugin(activity) { }
First override the checkPermissions
and requestPermissions
functions:
class ExamplePlugin: Plugin {
@objc open func checkPermissions ( _ invoke : Invoke ) {
invoke. resolve ( [ " postNotification " : " prompt " ] )
@objc public override func requestPermissions ( _ invoke : Invoke ) {
// request permissions here
// then resolve the request
invoke. resolve ( [ " postNotification " : " granted " ] )
Tauri automatically implements two commands for the plugin: checkPermissions
and requestPermissions
.
Those commands can be directly called from JavaScript or Rust:
import { invoke, PermissionState } from ' @tauri-apps/api/core '
postNotification : PermissionState
// check permission state
const permission = await invoke < Permissions > ( ' plugin:<plugin-name>|checkPermissions ' )
if ( permission . postNotification === ' prompt-with-rationale ' ) {
// show information to the user about why permission is needed
if ( permission . postNotification . startsWith ( ' prompt ' )) {
const state = await invoke < Permissions > ( ' plugin:<plugin-name>|requestPermissions ' , { permissions: [ ' postNotification ' ] } )
use serde :: {Serialize, Deserialize};
use tauri :: {plugin :: PermissionState, Runtime};
#[serde(rename_all = " camelCase " )]
struct PermissionResponse {
pub post_notification : PermissionState,
#[serde(rename_all = " camelCase " )]
struct RequestPermission {
impl <R : Runtime> Notification<R> {
pub fn request_post_notification_permission ( &self ) -> crate :: Result<PermissionState> {
. run_mobile_plugin :: <PermissionResponse>( " requestPermissions " , RequestPermission { post_notification : true })
. map ( | r | r . post_notification)
pub fn check_permissions ( &self ) -> crate :: Result<PermissionResponse> {
. run_mobile_plugin :: <PermissionResponse>( " checkPermissions " , ())
Plugins can emit events at any point of time using the trigger
function:
class ExamplePlugin( private val activity: Activity): Plugin(activity) {
override fun load (webView: WebView) {
trigger ( "load" , JSObject ())
override fun onNewIntent (intent: Intent) {
// handle new intent event
if (intent.action == Intent.ACTION_VIEW) {
val data = intent. data . toString ()
trigger ( "newIntent" , event)
fun openCamera (invoke: Invoke) {
payload. put ( "open" , true )
trigger ( "camera" , payload)
class ExamplePlugin: Plugin {
@objc public override func load ( webview : WKWebView ) {
trigger ( " load " , data : [ : ] )
@objc public func openCamera ( _ invoke : Invoke ) {
trigger ( " camera " , data : [ " open " : true ] )
The helper functions can then be called from the NPM package by using the addPluginListener
helper function:
import { addPluginListener, PluginListener } from ' @tauri-apps/api/core ' ;
export async function onRequest (
handler : ( url : string ) => void
) : Promise < PluginListener > {
return await addPluginListener (
© 2024 Tauri Contributors. CC-BY / MIT