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
- Create a new directory for your plugin project
- Initialize with
npm init -y - Add
@capacitor/coreas a dependency - Create
plugin.config.jsonwith plugin metadata - Set up TypeScript configuration (
tsconfig.json) - 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
@objcto expose Swift classes to Objective-C - Extend
CAPPluginto create plugin classes - Use
CAPPluginCallfor 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 publishto 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
- Install the plugin in your Ionic project using npm or yarn.
- Import and register the plugin in your Capacitor configuration.
- 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. |
|
| 3. Use Plugin | Import and use the plugin in your Ionic app. | |
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.