
Description
Generates new Capacitor plugin scaffolds and first-pass implementations from conversational requirements or a structured YAML input contract. Use when a user says "generate a Capacitor plugin", "create a Capacitor plugin scaffold", "build a native plugin for iOS and Android", "turn this plugin plan into Capacitor code", or "use this YAML contract to generate a plugin". Do not use for analyzing Cordova source, migrating whole apps, installing existing plugins, upgrading Capacitor versions, or publishing production-ready code without human review.
SKILL.md
Capacitor Plugin Generator
Generate a reviewable Capacitor plugin candidate from either human intent or a structured YAML contract. The output should follow the official Capacitor plugin architecture, but it is a first pass that requires human review before release.
When to Use This Skill
✅ Use this skill when:
- Creating a new Capacitor plugin from scratch.
- Adding native functionality (camera, sensors, storage, etc.) to a Capacitor app.
- Designing plugin architecture and API contracts.
- Implementing native code for iOS (Swift) or Android (Kotlin/Java).
- Bridging native APIs to JavaScript/TypeScript.
- Setting up plugin configuration and build systems.
- Generating a plugin from a structured YAML contract handed off by the
cordova-capacitor-plugin-migrationskill.
❌ Do NOT use this skill for:
- Building standard Capacitor apps (use Capacitor documentation instead).
- Web-only features that don't require native bridges.
- Modifying existing Capacitor core plugins.
- Analyzing Cordova plugin structure (use
cordova-capacitor-plugin-migrationfirst; that skill produces the input contract this skill consumes). - Upgrading existing plugins to newer Capacitor versions.
- Publishing production-ready code without human review.
Prerequisites
| Requirement | Use |
|---|---|
| Node.js LTS and npm | Run the Capacitor plugin generator and package scripts. |
| Xcode | Build and verify iOS output when iOS is targeted. |
| Android Studio and Android SDK | Build and verify Android output when Android is targeted. |
| CocoaPods and Gradle | Resolve native dependencies when required by generated code. |
| Capacitor plugin knowledge | Review generated native bridge code before publishing. |
Agent Behavior
- Detect whether the user provided conversational intent or structured YAML.
- In conversational mode, ask only for missing plugin identity, methods, platforms, events, permissions, configuration, and native dependencies.
- In structured mode, parse
references/input-contract.md, skip elicitation, and halt if the optional migration block contains blockers or tier 3 hooks. - Treat migration metadata as implementation context only. Do not inspect or analyze Cordova source; that belongs to the migration skill.
- Prefer the official Capacitor plugin generator, then edit the generated scaffold to implement the requested API.
- Keep generated code contract-first: TypeScript definitions drive web, iOS, Android, docs, and sample app behavior.
- Use only Capacitor classes that exist in the installed
@capacitor/core,@capacitor/android, and@capacitor/iospackages. Do not invent helper classes, utilities, or import paths. When uncertain whether an API exists, read the package source rather than infer from its name. - When the generated TypeScript contract mirrors an existing public API (Capacitor core/community, Capawesome, internal libraries, or a documented JavaScript API the user is replacing), look up the actual string literal values used on the wire. Do not derive them from human-friendly names. Structured-mode YAML pins these values explicitly; conversational-mode generation must consult the source.
- Keep bridge files thin. Split native logic into implementation, manager, permission, config, and mapper helpers when a method would otherwise become a large mixed-responsibility block.
- Choose native APIs by intended product behavior, not by demo convenience. If
the requested behavior requires permissions, special app settings, or manual
app configuration, generate the proper
checkPermissions()/requestPermissions()flow and document the manual setup. - Clearly report which verification commands were run and which need local human/device validation.
- Never publish for real from this skill. Run publish checks and
npm publish --access public --dry-runonly.
Procedures
Phase 1: Determine the Task and Entry Mode
Read references/input-contract.md. If the input is YAML with plugin,
platforms, and api, parse it as structured mode and skip questions. If the
input is conversational, elicit the minimum missing fields needed to create the
same contract shape internally.
Phase 2: Scaffold
Read references/scaffolding.md. Run the official Capacitor plugin generator
with non-interactive flags when possible. Enforce name parity:
registerPlugin() JavaScript name equals iOS jsName equals Android
@CapacitorPlugin(name).
Phase 3: Design the TypeScript API
Read references/api-design.md. Define src/definitions.ts before native
implementation. Use options/result interfaces per method, string unions instead
of enums, listener signatures for events, and JSDoc with @since everywhere.
Phase 4: Implement the Web Layer
Read references/web-guide.md. Extend WebPlugin, feature-detect browser APIs
before use, throw unavailable() when an API exists but is unavailable in the
current browser, and throw unimplemented() when no web equivalent exists.
Register the web layer through a dynamic import.
Phase 5: Define Method Signatures
Read references/api-design.md. For every method, choose one bridge return
type: value, void, or callback. Use callback return types only for streams or
long-lived watchers. Keep event names identical across TypeScript, web, iOS, and
Android.
Phase 6: Implement iOS
Read references/architecture-patterns.md and references/ios-implementation.md. Use the
Bridge pattern by default: a thin Capacitor plugin class delegates to an
implementation class. Use a Facade only for complex plugins with multiple native
subsystems, permission flows, or lifecycle concerns.
Phase 7: Implement Android
Read references/architecture-patterns.md and references/android-implementation.md. Use the
Bridge pattern by default: a thin Plugin class delegates to an implementation
class. Use a Facade only for complex plugins with multiple managers, permission
flows, services, activities, or lifecycle hooks.
Phase 8: Generate a Sample App
Read references/sample-app.md. Create or update a sample app that exercises
the entire public plugin API, including success paths, expected errors,
permissions, configuration, and listeners.
Phase 9: Docgen and Verify
Read references/testing-strategies.md and references/publishing.md.
Generate API docs from JSDoc with npm run docgen; do not hand-write API docs.
Run the relevant verify commands for targeted platforms and record any
environment-limited checks.
Phase 10: Publish Checks
Read references/publishing.md. Run the pre-publish checklist and dry run:
npm publish --access public --dry-run. Do not publish the generated plugin
without explicit human review outside this skill.
Best Practices
DO
- ✅ Use command-line flags with
npm init @capacitor/pluginso the scaffolder runs non-interactively. - ✅ Detect entry mode (conversational vs structured YAML) before asking questions. Skip elicitation entirely in structured mode.
- ✅ Design the TypeScript API first (contract-first), then implement web, iOS, and Android against that contract.
- ✅ Implement the web layer for testing without devices, even when
most methods throw
unimplemented(). - ✅ Inspect the official plugin's native dependencies when mirroring an existing API; declare the same SDKs and write a thin adapter rather than reimplementing.
- ✅ Document every public symbol with JSDoc and
@since. - ✅ Run
npm run fmtbefore committing andnpm run verifybefore reporting completion. - ✅ Use the two-class pattern (bridge + implementation) on iOS and Android for testability.
- ✅ Match wire-format string and numeric values exactly when mirroring
an existing API. Look up the official
definitions.tsrather than guessing from human-friendly names. - ✅ Keep event names identical across TypeScript / web / iOS / Android.
DON'T
- ❌ Mix concerns — keep the plugin focused on one capability.
- ❌ Skip error handling. Reject with codes from the canonical 4-code
taxonomy (
UNAVAILABLE,PERMISSION_DENIED,INVALID_PARAMETER,OPERATION_FAILED). - ❌ Use callbacks instead of promises in the TypeScript surface.
- ❌ Forget the web implementation, even for iOS/Android-only features.
- ❌ Hard-code values that should be configurable. Use
references/configuration.mdruntime plugin configuration. - ❌ Invent Capacitor classes, helpers, or import paths. Verify against
the installed
@capacitor/core,@capacitor/android, and@capacitor/iospackages. - ❌ Call
notifyListeners(...)from outside thePluginsubclass — seereferences/architecture-patterns.md"Event Dispatch Locality". - ❌ Publish from this skill. Run dry-run only with
npm publish --access public --dry-run. - ❌ Inspect or analyze Cordova source — that belongs to the sibling migration skill.
Error Handling
| Symptom | Fix |
|---|---|
npm init @capacitor/plugin fails with Refusing to prompt in non-TTY environment | Pass all required flags non-interactively: npm init @capacitor/plugin <folder> -- --name "<npm-name>" --package-id "<reverse-dns>" --class-name "<PascalCase>" --description "<one-line>" --author "<name <email>>" --license "<SPDX>" --repo "<url>" --android-lang "<kotlin|java>". See references/scaffolding.md. |
npm init @capacitor/plugin fails with invalid option: --android-lang undefined: Must be either 'kotlin' or 'java' | The --android-lang flag is required when running non-interactively. Add --android-lang "kotlin" (recommended for new plugins) or --android-lang "java" to the command. |
| Plugin silently fails to load | Make registerPlugin() name match iOS jsName and Android @CapacitorPlugin(name). |
| Event not received in JS | Make the event name string identical across TypeScript, web, iOS, and Android. |
| iOS method not callable from JS | Ensure the method is marked @objc and listed in pluginMethods. |
| Android method not callable from JS | Ensure the method is public and annotated with @PluginMethod(). |
npm run verify:ios fails | Run pod install --repo-update; then rerun the iOS verify command. |
npm run verify:android fails | Sync Gradle and check Android SDK, compile SDK, and dependency versions. |
Android compile error: class X is public, should be declared in a file named X.java | Java requires a public class to live in a file matching its name. When generating multiple Java classes per plugin, place each public class in its own file. Kotlin does not impose this rule. |
Android compile error: notifyListeners(...) has protected access in Plugin | notifyListeners() is protected on Plugin. Call it only from inside a class that extends Plugin. If another class needs to emit events, return the data to the plugin and dispatch there, or expose a public wrapper on the plugin that calls notifyListeners() internally. |
Android compile error: cannot find symbol: class … for a com.getcapacitor.* import | The import does not exist on the installed @capacitor/android surface. Verify imports against the package source before generating; do not infer Capacitor classes from their names. |
Android compile error: <method> in <Subclass> cannot override <method> in Plugin; attempting to assign weaker access privileges; was public | A helper on the Plugin subclass collides with a public method that com.getcapacitor.Plugin already defines (e.g., hasPermission, getPermissionState, notifyListeners). Either rename the helper or match the parent's public visibility. |
TypeScript build error: Cannot find type definition file for '<name>' or Invalid module name in augmentation, module '<name>' cannot be found | The augmented module is not installed. Add it to devDependencies (and to tsconfig.json compilerOptions.types if a triple-slash reference is used). Applies to any module augmentation, not just @capacitor/cli. |
TypeScript build error: Interface 'X' incorrectly extends interface 'Y'. Property 'Z' is optional in type 'X' but required in type 'Y' (or the symmetric error) | Do not redeclare members the built-in lib type already provides. Use the lib type directly, or augment via declare global { interface Y { newMember?: ... } } for genuinely new members only. Use 'name' in target runtime guards for capability checks. |
npm run docgen produces empty output | Add JSDoc to src/definitions.ts; docgen reads the TypeScript contract. |
| Web API absent in target browser | Use unavailable() when the API exists but is missing here; use unimplemented() when no web equivalent exists. |
| Structured YAML is rejected | Validate against references/input-contract.md; ensure required base fields are present and blockers are empty. |
| Generated output looks too broad | Split unrelated capabilities into separate plugins and regenerate with a smaller API surface. |
| Generated native code reimplements logic the official plugin delegates to a native SDK | Inspect the official plugin's .podspec / Package.swift / android/build.gradle for native SDK dependencies. If present, declare the same SDK and write a thin adapter — see references/api-design.md "Native Dependency Detection". |
Related Skills
cordova-capacitor-plugin-migration: Analyze Cordova plugins and produce a structured migration plan for this generator.
References
references/input-contract.md: Structured YAML contract for generator input.references/scaffolding.md: Generator invocation, name parity, and scaffold verification.references/api-design.md: TypeScript API design, JSDoc, event signatures, and method return types.references/web-guide.md: WebPlugin patterns, dynamic import, feature detection, and errors.references/architecture-patterns.md: Bridge and Facade patterns plus event parity.references/ios-implementation.md: iOS bridge, implementation class, permissions, dependencies, and Podspec.references/android-implementation.md: Android bridge, implementation class, permissions, dependencies, and Gradle.references/configuration.md: Capacitor config keys underplugins.<PluginJSName>.references/testing-strategies.md: Local linking, verify commands, hooks, and review workflow.references/publishing.md: Package fields, docgen, checklist, and dry-run publishing.references/sample-app.md: Sample app requirements that exercise the full API.references/permission-patterns.md: Deep-dive on permission flows — multi-permission DispatchGroup on iOS, location delegate, check-before-use / just-in-time / deferred consumer patterns, opening system settings.references/typescript-implementation.md: Deep-dive on TypeScript layer — singleton plugin pattern, typed error classes, full event listener handle bookkeeping, helper utilities, Jest scaffolding.