Capacitor Plugin Authoring
Specialization
Design and implement custom CapacitorJS plugins that bridge native iOS Swift and Android Kotlin capabilities to a web layer through a typed TypeScript interface, with deterministic safety, permission, and error-handling patterns.
Scope Boundaries
In scope:
- TypeScript plugin interface and implementation definition.
- iOS Swift plugin class authoring and Capacitor bridge registration.
- Android Kotlin plugin class authoring and Capacitor bridge registration.
- Permission declaration and runtime request patterns for both platforms.
- Bridge-safe data serialization and error propagation.
- Plugin packaging for local use or npm publication.
Out of scope:
- Using existing official or community plugins (see
capacitor-native-apis).
- App-level setup and platform registration (see
capacitor-setup).
- Plugin CI publishing pipelines beyond local validation.
Trigger Conditions
- A native capability is needed that no official or community plugin covers.
- An existing custom plugin has bridge safety, permission, or compatibility issues.
- A plugin must wrap a proprietary native SDK.
- A plugin implementation must be reviewed for correctness against the current Capacitor bridge contract.
Inputs
- Plugin name and capability description.
- Target platforms: iOS, Android, or both.
- Required native SDK or system API references.
- Permission requirements for each platform.
- TypeScript method signatures and data shapes expected by the web layer.
- Evaluation date in ISO format (
YYYY-MM-DD).
Required Outputs
- TypeScript plugin interface and
registerPlugin call.
- iOS Swift implementation class with Capacitor
CAPPlugin subclass and method decorators.
- Android Kotlin implementation class with
@PluginMethod annotations.
- Permission declarations for both
Info.plist (iOS) and AndroidManifest.xml (Android).
- Error propagation pattern from native to web layer.
- Local registration instructions and smoke-test checklist.
Depth Modes
| Level |
Intent |
Stop Rule |
| L1 Orientation |
Understand the plugin contract |
One method bridges native to web correctly |
| L2 Practical Plugin |
Ship one safe custom plugin |
TypeScript, iOS, and Android implementations exist and sync |
| L3 Hardened Plugin |
Production-safe plugin |
Permissions, errors, threading, and edge cases are handled |
| L4 Expert Plugin |
Reusable plugin standard |
Plugin is packageable, versioned, and has documented contracts |
Deterministic Workflow
- Define the TypeScript interface: method names, parameter types, return types, and error shapes.
- Create the plugin directory following
@capacitor/plugin-generator output conventions.
- Implement
registerPlugin<MyPlugin>('MyPlugin', { web: () => new MyPluginWeb() }) in index.ts.
- Author the iOS Swift class: subclass
CAPPlugin, annotate methods with @objc, call call.resolve() or call.reject().
- Register the iOS plugin in the native project's
AppDelegate or via auto-discovery with +load.
- Author the Android Kotlin class: extend
Plugin, annotate methods with @PluginMethod(returnType = PluginMethod.RETURN_PROMISE).
- Register the Android plugin in
MainActivity via registerPlugin(MyPlugin::class.java).
- Declare all required permissions in
Info.plist (iOS) and AndroidManifest.xml (Android).
- Implement runtime permission requests using
checkPermissions and requestPermissions bridge methods.
- Validate bridge-safe serialization: all data crossing the bridge must be JSON-serializable primitives or
JSObject.
- Run
npx cap sync and smoke-test all methods on a physical or emulated device.
Bridge Safety Rules
- Never pass non-serializable native objects (UIImage, Bitmap, etc.) directly across the bridge; convert to base64 strings or file URIs.
- Always call
call.resolve() or call.reject() exactly once per method invocation; never both.
- Never block the main thread in a plugin method; dispatch to a background thread for blocking work.
- Use
notifyListeners() only for events that may fire multiple times; do not use it for single-call responses.
- Validate all input from
call.getString() / call.getObject() before use; treat bridge input as untrusted.
Permission Patterns
- Declare usage descriptions in
Info.plist for every permission type used on iOS.
- Request runtime permissions before accessing restricted APIs on both platforms.
- Implement the
checkPermissions and requestPermissions Capacitor standard methods when the plugin accesses protected resources.
- Return explicit permission state strings (
granted, denied, prompt) from checkPermissions.
Error Propagation Pattern
// TypeScript
export interface MyPlugin {
doWork(options: { input: string }): Promise<{ result: string }>;
}
// Native — always reject with a code and message
call.reject("INPUT_INVALID", "Input must not be empty", null);
// Or resolve
call.resolve(JSObject().apply { put("result", value) });
Quality Gates
- All bridge methods return
Promise; no synchronous bridge calls exist.
- No main-thread blocking in native implementations.
- All native-to-web data is JSON-serializable.
- Permissions are declared before first use and runtime requests are handled gracefully.
call.resolve() and call.reject() are mutually exclusive in all code paths.
Done Criteria
- TypeScript interface, iOS, and Android implementations are complete.
- All methods are smoke-tested on physical or emulated iOS and Android targets.
- Permissions are declared and runtime requests are verified.
- Error paths are tested and produce correct
reject behavior.
- Bridge safety checklist passes without exceptions.
Source: sextondjc/agentic-marketplace — distributed by TomeVault.
1---2name: capacitor-plugin-authoring3description: Use when designing or implementing custom CapacitorJS plugins including TypeScript interface definition, iOS Swift bridge, Android Kotlin bridge, and plugin registration with safety and permission controls.4---56# Capacitor Plugin Authoring78## Specialization910Design and implement custom CapacitorJS plugins that bridge native iOS Swift and Android Kotlin capabilities to a web layer through a typed TypeScript interface, with deterministic safety, permission, and error-handling patterns.1112## Scope Boundaries1314In scope:1516- TypeScript plugin interface and implementation definition.17- iOS Swift plugin class authoring and Capacitor bridge registration.18- Android Kotlin plugin class authoring and Capacitor bridge registration.19- Permission declaration and runtime request patterns for both platforms.20- Bridge-safe data serialization and error propagation.21- Plugin packaging for local use or npm publication.2223Out of scope:2425- Using existing official or community plugins (see `capacitor-native-apis`).26- App-level setup and platform registration (see `capacitor-setup`).27- Plugin CI publishing pipelines beyond local validation.2829## Trigger Conditions3031- A native capability is needed that no official or community plugin covers.32- An existing custom plugin has bridge safety, permission, or compatibility issues.33- A plugin must wrap a proprietary native SDK.34- A plugin implementation must be reviewed for correctness against the current Capacitor bridge contract.3536## Inputs3738- Plugin name and capability description.39- Target platforms: iOS, Android, or both.40- Required native SDK or system API references.41- Permission requirements for each platform.42- TypeScript method signatures and data shapes expected by the web layer.43- Evaluation date in ISO format (`YYYY-MM-DD`).4445## Required Outputs4647- TypeScript plugin interface and `registerPlugin` call.48- iOS Swift implementation class with Capacitor `CAPPlugin` subclass and method decorators.49- Android Kotlin implementation class with `@PluginMethod` annotations.50- Permission declarations for both `Info.plist` (iOS) and `AndroidManifest.xml` (Android).51- Error propagation pattern from native to web layer.52- Local registration instructions and smoke-test checklist.5354## Depth Modes5556| Level | Intent | Stop Rule |57|---|---|---|58| L1 Orientation | Understand the plugin contract | One method bridges native to web correctly |59| L2 Practical Plugin | Ship one safe custom plugin | TypeScript, iOS, and Android implementations exist and sync |60| L3 Hardened Plugin | Production-safe plugin | Permissions, errors, threading, and edge cases are handled |61| L4 Expert Plugin | Reusable plugin standard | Plugin is packageable, versioned, and has documented contracts |6263## Deterministic Workflow64651. Define the TypeScript interface: method names, parameter types, return types, and error shapes.662. Create the plugin directory following `@capacitor/plugin-generator` output conventions.673. Implement `registerPlugin<MyPlugin>('MyPlugin', { web: () => new MyPluginWeb() })` in `index.ts`.684. Author the iOS Swift class: subclass `CAPPlugin`, annotate methods with `@objc`, call `call.resolve()` or `call.reject()`.695. Register the iOS plugin in the native project's `AppDelegate` or via auto-discovery with `+load`.706. Author the Android Kotlin class: extend `Plugin`, annotate methods with `@PluginMethod(returnType = PluginMethod.RETURN_PROMISE)`.717. Register the Android plugin in `MainActivity` via `registerPlugin(MyPlugin::class.java)`.728. Declare all required permissions in `Info.plist` (iOS) and `AndroidManifest.xml` (Android).739. Implement runtime permission requests using `checkPermissions` and `requestPermissions` bridge methods.7410. Validate bridge-safe serialization: all data crossing the bridge must be JSON-serializable primitives or `JSObject`.7511. Run `npx cap sync` and smoke-test all methods on a physical or emulated device.7677## Bridge Safety Rules7879- Never pass non-serializable native objects (UIImage, Bitmap, etc.) directly across the bridge; convert to base64 strings or file URIs.80- Always call `call.resolve()` or `call.reject()` exactly once per method invocation; never both.81- Never block the main thread in a plugin method; dispatch to a background thread for blocking work.82- Use `notifyListeners()` only for events that may fire multiple times; do not use it for single-call responses.83- Validate all input from `call.getString()` / `call.getObject()` before use; treat bridge input as untrusted.8485## Permission Patterns8687- Declare usage descriptions in `Info.plist` for every permission type used on iOS.88- Request runtime permissions before accessing restricted APIs on both platforms.89- Implement the `checkPermissions` and `requestPermissions` Capacitor standard methods when the plugin accesses protected resources.90- Return explicit permission state strings (`granted`, `denied`, `prompt`) from `checkPermissions`.9192## Error Propagation Pattern9394```typescript95// TypeScript96export interface MyPlugin {97 doWork(options: { input: string }): Promise<{ result: string }>;98}99100// Native — always reject with a code and message101call.reject("INPUT_INVALID", "Input must not be empty", null);102// Or resolve103call.resolve(JSObject().apply { put("result", value) });104```105106## Quality Gates107108- All bridge methods return `Promise`; no synchronous bridge calls exist.109- No main-thread blocking in native implementations.110- All native-to-web data is JSON-serializable.111- Permissions are declared before first use and runtime requests are handled gracefully.112- `call.resolve()` and `call.reject()` are mutually exclusive in all code paths.113114## Done Criteria115116- TypeScript interface, iOS, and Android implementations are complete.117- All methods are smoke-tested on physical or emulated iOS and Android targets.118- Permissions are declared and runtime requests are verified.119- Error paths are tested and produce correct `reject` behavior.120- Bridge safety checklist passes without exceptions.121122---123> Source: [sextondjc/agentic-marketplace](https://github.com/sextondjc/agentic-marketplace) — distributed by [TomeVault](https://tomevault.io).124<!-- tomevault:4.0:skill_md:2026-06-16 -->