Capacitor Apps skill for Codex

Ship the web app natively without forking it. Codex treats the ios and android folders as committed source, keeps every @capacitor package on one major, and adds native features through a typed plugin interface with a web fallback so the app still runs in a browser. It writes app-local Swift and Kotlin plugins that always resolve or reject, handles back button, safe areas and CORS origins, and strips live-reload URLs before release.

  • Mobile & Desktop
  • $capacitor-apps
  • 182 lines
  • 2 files
  • ~1,900 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- capacitor-apps

Installs to ~/.agents/skills/capacitor-apps/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $capacitor-apps, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Builds and maintains Capacitor hybrid apps (with Ionic, React, Vue, Angular or plain web) - capacitor.config, cap sync and the native iOS and Android projects, official and community plugins, writing custom Swift and Kotlin plugins, live reload, deep links, back button and safe areas, native HTTP, and Capacitor major upgrades. Use when wrapping a web app as an iOS and Android app, adding a native capability through a plugin, debugging a plugin that works on one platform only, or upgrading Capacitor.

Use it when

  • Turning an existing web app into store-ready iOS and Android apps
  • Adding a native capability that no official plugin covers
  • A plugin works on one platform and fails on the other
  • Upgrading Capacitor or its plugins

Use something else when

How to check the result

npm run build plus npx cap sync succeeds cleanly, release builds run on physical devices with no dev server, and each plugin method's success, reject and permission-denied paths are exercised on both platforms.

The mistake it prevents

Leaving a live-reload server.url in the native config, so the release build tries to load a laptop's dev server.

~/.agents/skills/capacitor-apps/SKILL.md

--- name: capacitor-apps description: Builds and maintains Capacitor hybrid apps (with Ionic, React, Vue, Angular or plain web) - capacitor.config, cap sync and the native iOS and Android… license: MIT ---

Done well means: the web app runs unchanged in the browser and in the native shells, every native capability goes through a typed plugin with a web fallback, the native projects are committed and buildable from a clean checkout after npx cap sync, and nothing dev-only (live reload URLs, cleartext) ships in a release build.

Before changing anything

  1. Read AGENTS.md, package.json and capacitor.config.ts (or .json): appId, webDir, server, plugin config.
  2. Check versions: npx cap doctor prints core, CLI and platform versions. All @capacitor/* packages must share the same major.
  3. Inspect ios/ and android/: they are source code, committed, and may contain manual edits (Info.plist keys, manifest permissions, Gradle config). Never delete and regenerate them without asking.
  4. Note the iOS dependency manager: Swift Package Manager (default for new projects on recent Capacitor) or CocoaPods (ios/App/Podfile). Plugin compatibility differs.
  5. npm install, Pods and Gradle downloads need network; ask for approval when sandboxed.

Daily commands

npm run build && npx cap sync           # copy web build + update native deps; run after any plugin change
npx cap copy                             # web assets only (faster, no native dep changes)
npx cap open ios                         # Xcode
npx cap open android                     # Android Studio
npx cap run android --target <id>        # build, install, launch
npx cap run ios --live-reload --host 192.168.1.20 --port 5173   # dev only

Live reload writes the dev server URL into the native config for that run. Before a release build, confirm server.url is absent from capacitor.config.ts and ios/App/App/capacitor.config.json / android/app/src/main/assets/capacitor.config.json.

Config essentials

import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.example.field',
  appName: 'Field',
  webDir: 'dist',
  plugins: {
    SplashScreen: { launchAutoHide: false },
    CapacitorHttp: { enabled: false },   // enable only if you want fetch patched globally
  },
};
export default config;

Do not set server.cleartext: true or android.allowMixedContent: true for production.

Choosing a plugin

NeedFirst choice
Camera, filesystem, preferences, share, haptics, status bar, keyboard, app lifecycleOfficial @capacitor/* plugins
Push@capacitor/push-notifications (FCM on Android, APNs on iOS)
Secure storage, biometrics, SQLite, in-app purchaseMaintained community plugin (check last release, open issues, SPM support)
Anything else or company-specific SDKCustom plugin (below)

Before adding a community plugin, check it supports your Capacitor major and your iOS dependency manager. An unmaintained plugin blocks every future upgrade.

Writing a custom plugin

For reuse across apps, scaffold a package with npm init @capacitor/plugin@latest. For an app-only plugin, add the classes directly in the native projects:

// src/plugins/echo.ts
import { registerPlugin } from '@capacitor/core';
export interface EchoPlugin { echo(options: { value: string }): Promise<{ value: string }>; }
export const Echo = registerPlugin<EchoPlugin>('Echo', {
  web: () => import('./echo-web').then((m) => new m.EchoWeb()),
});
// ios/App/App/EchoPlugin.swift
import Capacitor

@objc(EchoPlugin)
public class EchoPlugin: CAPPlugin, CAPBridgedPlugin {
    public let identifier = "EchoPlugin"
    public let jsName = "Echo"
    public let pluginMethods: [CAPPluginMethod] = [
        CAPPluginMethod(name: "echo", returnType: CAPPluginReturnPromise)
    ]
    @objc func echo(_ call: CAPPluginCall) {
        guard let value = call.getString("value") else { return call.reject("value is required") }
        call.resolve(["value": value])
    }
}

Register an app-local iOS plugin from a CAPBridgeViewController subclass (set it as the storyboard’s view controller class):

class MainViewController: CAPBridgeViewController {
    override open func capacitorDidLoad() { bridge?.registerPluginInstance(EchoPlugin()) }
}
// android/app/src/main/java/com/example/field/EchoPlugin.kt
@CapacitorPlugin(name = "Echo")
class EchoPlugin : Plugin() {
    @PluginMethod
    fun echo(call: PluginCall) {
        val value = call.getString("value") ?: return call.reject("value is required")
        call.resolve(JSObject().put("value", value))
    }
}
// MainActivity.onCreate: registerPlugin(EchoPlugin::class.java) before super.onCreate(savedInstanceState)
  • Do slow work off the main thread (Kotlin coroutines / execute {}; Swift Task or a background queue), and touch UI only on the main thread.
  • Every method resolves or rejects exactly once; a forgotten path hangs the JS promise.
  • Request runtime permissions through Capacitor’s permission API (@Permission aliases on Android, checkPermissions/requestPermissions on both), and add Info.plist usage strings.
  • Keep the TypeScript interface the single contract; write the web implementation (even if it throws unavailable) so the app runs in a browser.

Native behaviours to handle

  • Android back button: App.addListener('backButton', ({ canGoBack }) => canGoBack ? history.back() : App.exitApp()), or let Ionic’s router handle it.
  • Deep links: App.addListener('appUrlOpen', ({ url }) => router.navigate(parse(url))); hosting and verification are covered in mobile-deep-links.
  • Safe areas: viewport-fit=cover in the viewport meta and env(safe-area-inset-*) paddings.
  • Keyboard: the Keyboard plugin’s resize mode decides whether the webview shrinks; test forms near the bottom of the screen on both platforms.
  • CORS: requests from the webview originate from capacitor://localhost (iOS) or https://localhost (Android). Allow those origins on your API, or route through CapacitorHttp.

Upgrading Capacitor

  1. Read the official upgrade guide for the target major; it lists required Xcode, Android Gradle Plugin, minimum iOS and Android SDK levels.
  2. Run npx cap migrate (or the Capacitor VS Code extension’s migration), then review the diff in ios/ and android/ line by line.
  3. Upgrade every @capacitor/* plugin and check community plugins for compatible releases before merging.
  4. Build both platforms from clean and run the smoke test below.

Gotchas

  • Forgetting npx cap sync after adding a plugin gives “plugin is not implemented on ios”.
  • Web storage (localStorage, IndexedDB) can be cleared by the OS under storage pressure; use Preferences or SQLite for data that must persist.
  • Over-the-air live updates (Appflow, Capgo) may change web code only; changing native behaviour or the app’s primary purpose through OTA breaks store rules.
  • Large JS bundles slow cold start more in a webview than in a browser; lazy-load routes.

Verification

  • npm run build && npx cap sync succeeds with no warnings about mismatched versions
  • Release builds open on a physical iPhone and Android device with no dev server
  • Each custom plugin method: success, reject path and permission denial tested on both
  • Back button, deep link, rotate, background and resume behave correctly
  • App still runs in a desktop browser with web fallbacks

Output

**Capacitor:** <version> iOS deps: SPM | CocoaPods
**Plugins:** <name -> official/community/custom, platforms>
**Native edits:** <files changed in ios/ and android/>
**Tested:** <device, OS, build type> -> result

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Capacitor Apps"
  short_description: "Ship the web app natively without forking it"
  default_prompt: "$capacitor-apps Wrap our Vite React app with Capacitor for iOS and Android"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem