How to create a custom Cordova plugin in 2024

9 min readJan 27, 2024

Developing for IONIC with Cordova or directly in Cordova is a fast way to get a mobile application, the time to develop and the time to market is faster than developing a native App for Android and IOS. But when you need a native feature and you do not find a plugin made by the community that fits into your needs is a headache.

The first thing you could do is check the Cordova documentation, which is not clear at all. In this blog, I will explain step by step how I created a custom Cordova plugin in 2024. For this blog, I suppose that you already know about native development, it is important, because, you will have to write Android native code(Java) and IOS native code (Swift).

Use PlugMan to create the base

1. Install Plugman Globally:

  • Command: npm install -g plugman

This installs the Plugman utility globally on your system, enabling you to manage Cordova plugins from the command line.

2. Create the Plugin Structure:

  • Command: plugman create --name CordovaHelloWorld --plugin_id com-kalex-plugings-helloWorld --plugin_version 1.0.0 --path ./cordovaPlugin
  • plugman create: Initiates plugin creation.
  • --name CordovaHelloWorld: Assigns the plugin a friendly name.
  • --plugin_id com-kalex-plugings-helloWorld: Sets a unique identifier for the plugin.
  • --plugin_version 1.0.0: Specifies the initial version number.
  • --path ./cordovaPlugin: Designates the directory where the plugin structure will be generated.

This command constructs the basic plugin structure with essential files and folders within the specified path.

3. Add Platform Support:

  • Command (Android): plugman platform add --platform_name android
  • Command (iOS): plugman platform add --platform_name iOS

These commands enable the plugin to function on the specified platforms (Android and iOS in this case). They generate platform-specific code and configuration within the plugin’s structure.

Understand the base of Cordova plugin development

Key Components: A Cordova plugin typically consists of:

  • JavaScript Interface: A JavaScript file (or files) that provides a JavaScript API for developers to interact with the plugin’s functionality from within their Cordova app.
  • Platform-Specific Code: Native code (e.g., Java for Android, Objective-C for iOS) that implements the actual device interactions for each supported platform. This code resides in platform-specific subdirectories within the plugin structure.
  • Plugin.xml Metadata: A file containing essential information about the plugin, such as its name, ID, version, supported platforms, JavaScript files, and native code files.

Going deep into the native Cordova classes

As you can see in the native part, we have the inheritance of specific classes from the Cordova project, in Android we extends from CordovaPlugin, and in IOS we extends from CDVPlugin.

Android code
IOS code

But, what do these classes bring to the table?

The Android side, gives us access to the webView ,
Cordova and preferences.

As well, we have access to control the lifecycle methods of the activity

We can override these methods in our plugin class to control events of the life cycle in Android.

On the other hand, the IOS side gives us access to a webView, webViewEngine, and viewController. We also have access to some lifecycle methods.

- (void)onAppTerminate;

- (void)onMemoryWarning;

- (void)onReset;

-(void)dispose;

but the good ones come when using the Notification Center

So, with these methods we have access to the Activity and ViewController on each side, as well as access to the most common and important lifecycle methods in native development.

Write the JS bride to communicate with IONIC

In resume, this is the code for our JS bride

var exec = require("cordova/exec");

exports.sayHello = function (arg0, success, error) {
exec(success, error, "CordovaHelloWorld", "sayHello", [arg0]);
};

exports.enable = function (success, error) {
exec(success, error, "CordovaHelloWorld", "enable");
};

exports.disable = function (success, error) {
exec(success, error, "CordovaHelloWorld", "disable");
};

lest break down this one to understand what is happening.

1. Importing the exec Function:

  • var exec = require("cordova/exec");
  • The exec function is the primary way for JavaScript code in a Cordova app to communicate with native platform code (e.g., Java on Android or Objective-C on iOS).

2. Exporting Plugin Functions:

  • exports.sayHello = function (arg0, success, error) { ... }
  • This line defines a function named sayHello, exports it, and makes it accessible to other parts of your Cordova app.
  • The function takes three arguments:
  • arg0: An argument that will be passed to the native code.
  • success: A callback function that will be invoked if the native code execution is successful.
  • error: A callback function that will be invoked if there's an error during the native code execution.

3. Executing Native Code:

  • exec(success, error, "CordovaHelloWorld", "sayHello", [arg0]);
  • This line calls the exec function to execute native code.
  • The arguments passed to exec are:
  • success: The callback function to be invoked if the native code execution is successful.
  • error: The callback function to be invoked if there's an error.
  • “CordovaHelloWorld”: The name of the class that inheritance from Cordova (The classes we talk in the previous part ).
  • “sayHello”: The specific method within the native plugin that you want to call.
  • [arg0]: An array containing any arguments that need to be passed to the native method (in this case, the arg0 argument from the JavaScript function).

Write the Native code

Before starting with the code, my advice to create the native part is first to create the feature in a native project, test the native feature, and then paste the code to the plugin, including the imports.

Android code 🤖

Again and to resume the post :) , this is the code in the Android side.

public class CordovaHelloWorld extends CordovaPlugin {

@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("sayHello")) {
String message = args.getString(0);
this.sayHello(message, callbackContext);

return true;
}
return false;
}


private void sayHello(String message, CallbackContext callbackContext) {
if (message != null && message.length() > 0) {
Toast.makeText(webView.getContext(), message, Toast.LENGTH_LONG).show();
callbackContext.success(message);
} else {
callbackContext.error("Expected one non-empty string argument.");
}
}
}

Now, lest break down the code.

1. Class Definition:

  • public class CordovaHelloWorld extends CordovaPlugin { ... }
  • The CordovaPlugin class is the base class for custom Cordova plugins on Android, providing the structure for interacting with JavaScript code.

2. execute Method:

  • @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { ... }
  • This method is called when JavaScript code invokes a method from the plugin.
  • It receives:
  • action: The name of the method being called.
  • args: JSONArray of arguments passed from JavaScript.
  • callbackContext: A context object used to send results or errors back to JavaScript.
  • It checks the action and, if it's "sayHello", it calls the sayHello method.

3. sayHello Method:

  • private void sayHello(String message, CallbackContext callbackContext) { ... }
  • This method displays a Toast message with the provided message.
  • It checks if the message is valid and either:
  • Calls callbackContext.success(message) to send a success response back to JavaScript.
  • Calls callbackContext.error(...) to send an error response.

IOS code 🍏

By default the Cordova plugin is in Objective-c, but we can use Swift in our custom plugin, we need to add the famous notation @objc() and we need a kind of “bride”, called Bridging-Header.h Fortunately now day in Cordova 11 this file is generated automatically :) , lest go with the native code.

/********* CordovaHelloWorld.m Cordova Plugin Implementation *******/

import Foundation
import Cordova
import UIKit

@objc(CordovaHelloWorld)
class CordovaHelloWorld: CDVPlugin {

@objc func sayHello(_ command: CDVInvokedUrlCommand) {
var pluginResult = CDVPluginResult(
status: CDVCommandStatus_ERROR
)

let msg = command.arguments[0] as? String ?? ""

if msg.characters.count > 0 {
let toastController: UIAlertController =
UIAlertController(
title: "",
message: msg,
preferredStyle: .alert
)

self.viewController?.present(
toastController,
animated: true,
completion: nil
)

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
toastController.dismiss(
animated: true,
completion: nil
)
}

pluginResult = CDVPluginResult(
status: CDVCommandStatus_OK,
messageAs: msg
)
}

self.commandDelegate!.send(
pluginResult,
callbackId: command.callbackId
)
}
}

1. Imports and Class Definition:

  • @objc(CordovaHelloWorld) class CordovaHelloWorld: CDVPlugin { ... }: Defines an Objective-C class named CordovaHelloWorld that extends the CDVPlugin class, the base class for Cordova plugins on iOS.

2. sayHello Method:

  • @objc func sayHello(_ command: CDVInvokedUrlCommand) { ... }: This method is called when JavaScript invokes the "sayHello" method from the plugin.
  • var pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR): Initializes a CDVPluginResult object to send a response back to JavaScript, initially set to an error status.

3. Handling Message and Displaying Alert:

  • let msg = command.arguments[0] as? String ?? "": Retrieves the first argument passed from JavaScript (the message to display) and safely handles potential missing arguments.
  • if msg.characters.count > 0 { ... }: If the message is not empty:
  • Creates a UIAlertController to display as a temporary alert.
  • Presents the alert controller on the main view controller.
  • Uses DispatchQueue.main.asyncAfter to dismiss the alert automatically after 3 seconds.
  • Updates the pluginResult to a success status with the message.

4. Sending Response to JavaScript:

  • self.commandDelegate!.send(pluginResult, callbackId: command.callbackId): Sends the pluginResult back to JavaScript, indicating success or error and potentially returning the message.

Create the package.json

Here just run this command and Plugman will create it for you :)

sudo plugman createpackagejson "url to your plugin folder"

Create the IONIC wrapper

Now it's time to run our custom lib, to do this we gonna use the Awesome-Cordova-plugins repo, and clone it in your Desktop folder (for example).

git clone https://github.com/danielsogl/awesome-cordova-plugins.git

then run these commands, were “Cordova HelloWorld” is the name of the plugin

sudo npm install
gulp plugin:create -m -n CordovaHelloWorld

go to awesome-cordova-plugins/src/@awesome-cordova-plugins/plugins and search the folder that matches your plugin.

open the index.ts and edit it, the file is already well explained and documented, however, this is an example.

The next step is to build the Awesome-Cordova-plugins

npm run build

this will generate a folder dist

we just need to copy the folder of our plugin and paste it into the Awesome-Cordova-plugins in our project, With the image you will have a good reference.

Naturally, this manual way is not the best for production, in a professional environment, this IONIC wrapper generation is made by a pipeline and added to the ionic-native automatically when we install the dependencies in IONIC, but that is another topic.

Add your new plugin to the project

sudo ionic cordova plugin add <path to the plugin>

add the plugin and declare the plugin in the AppModule

now you can use the plugin in any component !!

import the plugin and inject it
execute the plugin functions

Test your new Cordova plugin

Run your IONIC app sudo ionic cordova run android or sudo ionic cordova run ios

There it is, congratulations 👏 , you have created a new Cordova plugin.

Thanks for reading I hope the article was enjoyable, useful and informative for you. You can reach me on LinkedInd and YouTube. Do not forget to follow me and like the post.

--

--

No responses yet