plugin.xml declares the plugin's ID, the platform versions it supports, the JavaScript module and, for Android, the service name, permission and Java file:
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android"
id="cordova-plugin-booknest" version="1.0.0">
<name>BookNest</name>
<engines>
<engine name="cordova-android" version=">=14.0.0" />
</engines>
<js-module src="www/booknest.js" name="BookNest">
<clobbers target="BookNest" />
</js-module>
<platform name="android">
<config-file target="res/xml/config.xml" parent="/*">
<feature name="BookNest">
<param name="android-package" value="com.example.booknest.plugin.BookNestPlugin" />
</feature>
</config-file>
<config-file target="AndroidManifest.xml" parent="/manifest">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
</config-file>
<source-file src="src/android/BookNestPlugin.java"
target-dir="src/com/example/booknest/plugin" />
</platform>
</plugin>The service name in <feature>, BookNest, is what JavaScript passes to exec(), and <clobbers> publishes the module as window.BookNest. The module wraps exec() in Promises once, as exec() and Callbacks did, so callers use await:
// window.BookNest: a Promise-based interface to the native BookNest plugin
const exec = require('cordova/exec');
const call = (action, args = []) =>
new Promise((resolve, reject) => exec(resolve, reject, 'BookNest', action, args));
module.exports = {
info: () => call('info'),
remind: (title, text) => call('remind', [title, text]),
};cordova plugin add ./local-plugins/cordova-plugin-booknest installs a local folder like any other plugin, and package.json records it as file:local-plugins/cordova-plugin-booknest, so a teammate's cordova prepare installs it from the same folder.