Skip to main content

Creating Custom Capacitor Plugins

1. Setting Up Plugin Project

Example: Plugin Structure

// plugin.config.json
{
  "name": "my-custom-plugin",
  "id": "com.example.mycustomplugin",
  "version": "1.0.0"
}

// package.json
{
  "name": "my-custom-plugin",
  "version": "1.0.0",
  "main": "dist/index.js",
  "scripts": {
    "build": "npm run clean && tsc",
    "clean": "rm -rf dist"
  },
  "dependencies": {
    "@capacitor/core": "^6.0.0"
  },
  "devDependencies": {
    "typescript": "^5.0.0"
  }
}
File Description Notes
plugin.config.json Plugin configuration file Required for Capacitor plugin setup
package.json Node.js package manifest Defines dependencies and build scripts
src/index.ts Plugin entry point Exports plugin interface and methods
Note: Ensure package.json includes @capacitor/core as a dependency.

Setup Steps

  1. Create a new directory for your plugin project
  2. Initialize with npm init -y
  3. Add @capacitor/core as a dependency
  4. Create plugin.config.json with plugin metadata
  5. Set up TypeScript configuration (tsconfig.json)
  6. Implement plugin logic in src/index.ts

2. Defining Plugin Interface

Property Type Description Required
name string Name of the plugin Yes
version string Version of the plugin Yes
platforms string[] Supported platforms (e.g., 'ios', 'android') Yes
js string JavaScript file path No
ios object iOS-specific configuration No
android object Android-specific configuration No

Example: Plugin Configuration

{
  "name": "my-custom-plugin",
  "version": "1.0.0",
  "platforms": ["ios", "android"],
  "js": "./dist/my-custom-plugin.js",
  "ios": {
    "name": "MyCustomPlugin",
    "framework": "swift"
  },
  "android": {
    "name": "MyCustomPlugin",
    "framework": "java"
  }
}

3. Implementing Web Code

When creating a custom Capacitor plugin, the web implementation is responsible for defining the JavaScript/TypeScript API that will be used by your application. This code runs in the web context and is responsible for interacting with native functionality through Capacitor's bridge.

Property Description Usage
registerPlugin Registers a new plugin with Capacitor registerPlugin('MyPlugin', { ... })
plugin Access to the plugin instance const myPlugin = Plugins.MyPlugin;

Example: Basic Plugin Registration

import { registerPlugin } from '@capacitor/core';

const MyPlugin = registerPlugin('MyPlugin', {
  web: () => import('./web'),
});

export { MyPlugin };

Example: Web Implementation

import { WebPlugin } from '@capacitor/core';

export class MyWebPlugin extends WebPlugin {
  constructor() {
    super({
      name: 'MyPlugin',
      platforms: ['web'],
    });
  }

  async echo(options: { value: string }) {
    return { value: options.value };
  }
}

const MyPlugin = new MyWebPlugin();

export { MyPlugin };
Note: The web implementation must match the native plugin's API to ensure consistency across platforms.

4. Implementing iOS Code

Example: Basic iOS Plugin Structure

import Capacitor
import UIKit

@objc(CustomPlugin)
public class CustomPlugin: CAPPlugin {
    @objc func echo(_ call: CAPPluginCall) {
        let value = call.getString("value") ?? ""
        call.success([
            "value": value
        ])
    }
}
Method Description Parameters
echo Simple echo function to return input value value: String
Note: Ensure the plugin class is marked with @objc and matches the plugin name in the Capacitor config.

Example: Handling Asynchronous Operations

import Capacitor
import Foundation

@objc(CustomPlugin)
public class CustomPlugin: CAPPlugin {
    @objc func fetchData(_ call: CAPPluginCall) {
        let url = call.getString("url") ?? ""
        
        guard let url = URL(string: url) else {
            call.error("Invalid URL")
            return
        }
        
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                call.error("Network error: \(error.localizedDescription)")
                return
            }
            
            guard let data = data else {
                call.error("No data received")
                return
            }
            
            call.success([
                "data": data.base64EncodedString()
            ])
        }.resume()
    }
}

Key iOS Plugin Concepts

  • Use @objc to expose Swift classes to Objective-C
  • Extend CAPPlugin to create plugin classes
  • Use CAPPluginCall for handling plugin calls
  • Handle asynchronous operations using URLSession

5. Implementing Android Code

Example: Basic Android Plugin Structure

package com.example.myplugin;

import android.util.Log;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.annotation.CapacitorPlugin;

@CapacitorPlugin(name = "MyPlugin")
public class MyPlugin extends Plugin {

    private static final String TAG = "MyPlugin";

    @Override
    public void load() {
        super.load();
        Log.d(TAG, "Plugin loaded");
    }

    // Example method to be called from JS
    public void doSomething(JSObject options) {
        Log.d(TAG, "doSomething called with: " + options.toString());
    }
}
Method Description Notes
load() Called when the plugin is initialized Override to perform setup tasks
doSomething() Example method to handle JS calls Must be public and accept JSObject
Note: The plugin must be registered in android/app/src/main/java/... and added to the capacitor.config.json file.
Warning: Ensure all Android permissions are declared in AndroidManifest.xml.

Example: Adding Permissions

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

6. Registering Plugin Methods

Method Signature Description Notes
register register(pluginName: string, methods: MethodConfig[]): void Registers a new plugin with the Capacitor runtime Must be called before any plugin methods can be invoked
addMethod addMethod(methodName: string, implementation: Function): void Adds a new method to an existing plugin Used for dynamic plugin extension at runtime

Example: Registering a Custom Plugin

import { register } from '@capacitor/core';

const MyPlugin = {
  name: 'MyPlugin',
  methods: [
    {
      name: 'greet',
      callback: (options) => {
        return 'Hello, ' + options.name;
      }
    }
  ]
};

register(MyPlugin.name, MyPlugin.methods);
Note: The register function must be called once per plugin, typically during initialization.
Warning: Plugin methods must be registered before they are called to prevent runtime errors.

7. Handling Callbacks

Callback Type Description Usage
successCallback Function called on successful execution successCallback(result)
errorCallback Function called on execution failure errorCallback(error)

Example: Using Callbacks in Plugin

import { Capacitor } from '@capacitor/core';

interface MyPluginResult {
  value: string;
}

const handleSuccess = (result: MyPluginResult) => {
  console.log('Success:', result.value);
};

const handleError = (error: any) => {
  console.error('Error:', error);
};

// Simulate plugin call with callbacks
const myPluginCall = () => {
  // Simulated async operation
  setTimeout(() => {
    const success = Math.random() > 0.5;
    if (success) {
      handleSuccess({ value: 'Plugin executed successfully' });
    } else {
      handleError({ message: 'Plugin failed to execute' });
    }
  }, 1000);
};

myPluginCall();
Note: Callbacks are essential for handling asynchronous operations in Capacitor plugins.

8. Publishing Plugin

Example: Plugin Package Structure

{
  "name": "@ionic/my-plugin",
  "version": "1.0.0",
  "description": "A custom Capacitor plugin for Ionic",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist/",
    "ios/",
    "android/",
    "src/"
  ],
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build"
  },
  "keywords": [
    "capacitor",
    "plugin",
    "ionic"
  ],
  "author": "Ionic Team",
  "license": "MIT",
  "devDependencies": {
    "@capacitor/core": "^6.0.0",
    "@capacitor/cli": "^6.0.0"
  }
}
Field Description Notes
name @ionic/my-plugin Must follow npm package naming conventions
version 1.0.0 Semantic versioning (MAJOR.MINOR.PATCH)
main dist/index.js Entry point for the plugin
types dist/index.d.ts TypeScript definition file
files ["dist/", "ios/", "android/", "src/"] Files to include in the package
scripts {"build": "tsc", "prepublishOnly": "npm run build"} Build and prepublish scripts
Note: Ensure all native code (iOS/Android) is properly compiled and included in the package before publishing.

Example: Publishing to npm

# Login to npm
npm login

# Publish the plugin
npm publish --access public

Steps to Publish Plugin

  • Build the plugin using npm run build
  • Ensure all native code is compiled for iOS and Android
  • Update version in package.json
  • Run npm publish to publish to npm
Warning: Once published, the plugin version cannot be republished. Always test thoroughly before publishing.

9. Using Custom Plugin

To use a custom Capacitor plugin in your Ionic project, follow these steps:

Workflow

  1. Install the plugin in your Ionic project using npm or yarn.
  2. Import and register the plugin in your Capacitor configuration.
  3. Use the plugin's API methods in your Ionic app code.
Step Description Action
1. Install Plugin Install the plugin in your project directory. npm install @mycompany/myplugin
2. Register Plugin Add the plugin to your capacitor.config.ts.
import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  plugins: {
    MyPlugin: {
      // Configuration options
    }
  }
};

export default config;
3. Use Plugin Import and use the plugin in your Ionic app.
import { MyPlugin } from '@mycompany/myplugin';

const result = await MyPlugin.doSomething({
  param: 'value'
});
Note: Ensure that the plugin is compatible with your Ionic and Capacitor versions.

Example: Using Plugin in Ionic Component

import { Component } from '@angular/core';
import { MyPlugin } from '@mycompany/myplugin';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
})
export class HomePage {
  async doSomething() {
    const result = await MyPlugin.doSomething({
      param: 'example'
    });
    console.log(result);
  }
}

10. Debugging Plugin Code

Example: Logging in Plugin Code

import { Plugins } from '@capacitor/core';

const { MyPlugin } = Plugins;

// Log to console from plugin
export async function logMessage(message) {
  console.log('Plugin log:', message);
  return await MyPlugin.logMessage({ message });
}
Method Description Notes
logMessage Logs a message to the console Useful for debugging plugin behavior
Note: Use console.log for basic debugging in Capacitor plugins.
Warning: Avoid logging sensitive data in production builds.

Debugging Workflow

  1. Use console.log statements in plugin code
  2. Check logs in browser console or device logs
  3. Use capacitor.config.json to enable debug mode
  4. Test on actual devices for mobile-specific issues

Was this article helpful?