Skip to content
Back to skills

Capacitor

ASecurity

Capacitor is a runtime that packages a web app as a native iOS and Android app and exposes native device APIs to JavaScript. Use when someone asks to "convert my website to a mobile app", "Capacitor", "web to native app", "access camera from JavaScript", "deploy web app to App Store", "hybrid mobile app", or "Ionic Capacitor". Covers native API access, plugin system, web-to-native bridge, and app store deployment.

  • 142 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added May 27, 2026
developmentjavascripttypescriptgojavaswiftkotlinbashreactvueangular

Works with

  • terminal
  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill capacitor --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Capacitor?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Capacitor
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-capacitor/badge)](https://www.skillsdirectory.com/skills/terminalskills-capacitor)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: capacitor
description: >-
  Capacitor is a runtime that packages a web app as a native iOS and Android
  app and exposes native device APIs to JavaScript. Use when someone asks to
  "convert my website to a mobile app", "Capacitor", "web to native app",
  "access camera from JavaScript", "deploy web app to App Store", "hybrid
  mobile app", or "Ionic Capacitor".
  Covers native API access, plugin system, web-to-native bridge, and app
  store deployment.
license: Apache-2.0
compatibility: "Capacitor 8: Node.js 22+, any web framework (React, Vue, Angular, Svelte). iOS builds need macOS with Xcode 26+; Android builds need Android Studio 2025.2.1+."
metadata:
  author: terminal-skills
  version: "1.1.0"
  category: development
  repository: https://github.com/ionic-team/capacitor
  tags: ["mobile", "capacitor", "hybrid", "native", "ios"]
---

# Capacitor

## Overview

Capacitor wraps your web app in a native container and gives it access to native device APIs — camera, file system, push notifications, biometrics, geolocation, and more. Your existing React/Vue/Angular/Svelte app becomes an iOS and Android app without rewriting in Swift or Kotlin. Built by the Ionic team, it's the modern replacement for Cordova/PhoneGap. This skill targets Capacitor 8 (iOS 15+, Android 7 / API 24+).

## When to Use

- Have a web app and want native iOS/Android versions
- Need native device features (camera, push notifications, biometrics)
- Want one codebase for web + iOS + Android
- Converting a PWA to a native app for App Store distribution
- Team knows web tech but not Swift/Kotlin

## Instructions

### Setup

The web project needs a `package.json` and a build output directory (`dist`, `build`, `www`) whose `index.html` has a `<head>` tag.

```bash
npm install @capacitor/core
npm install -D @capacitor/cli
npx cap init "Trailhead" dev.fernhill.trailhead --web-dir dist

# Add platforms
npm install @capacitor/ios @capacitor/android
npx cap add ios        # Swift Package Manager project (default since Capacitor 8)
npx cap add android
```

`cap init` writes `capacitor.config.ts` (or `.json` when TypeScript is not installed) with the first three keys; plugin settings are added by hand. With TypeScript 7 the CLI loads the `.ts` config through Node's ES-module loader, so `package.json` needs `"type": "module"` (Vite templates have it); without it every `cap` command stops with `Parsing capacitor.config.ts failed`:

```typescript
// capacitor.config.ts
import type { CapacitorConfig } from "@capacitor/cli";

const config: CapacitorConfig = {
  appId: "dev.fernhill.trailhead",
  appName: "Trailhead",
  webDir: "dist",
  plugins: {
    PushNotifications: { presentationOptions: ["badge", "sound", "alert"] },
  },
};

export default config;
```

### Build and Run

```bash
# Build your web app first
npm run build

# Copy web assets to native projects and update native dependencies
npx cap sync

# Open in native IDE
npx cap open ios      # Opens Xcode
npx cap open android  # Opens Android Studio

# Or run directly (run = sync + build + deploy)
npx cap run ios --list                      # available simulators and devices
npx cap run ios --target-name "iPhone 17"   # or --target with an id from --list
npx cap run android

# Live reload from a running dev server (e.g. Vite on port 5173)
npx cap run android --live-reload --port 5173

# Signed release build from the terminal
npx cap build android --androidreleasetype AAB \
  --keystorepath release.keystore --keystorepass "$ANDROID_KEYSTORE_PASSWORD" \
  --keystorealias upload --keystorealiaspass "$ANDROID_KEY_PASSWORD"
```

### Native Plugins

```bash
# Install plugins
npm install @capacitor/camera @capacitor/filesystem @capacitor/push-notifications
npm install @capacitor/haptics @capacitor/share @capacitor/browser
npx cap sync
```

```typescript
// src/native/camera.ts — Access the device camera (@capacitor/camera 8.1+)
import { Camera, CameraDirection, MediaTypeSelection } from "@capacitor/camera";

export async function takePhoto(): Promise<string | undefined> {
  const photo = await Camera.takePhoto({
    quality: 80,
    cameraDirection: CameraDirection.Rear,
    saveToGallery: false,
  });
  return photo.webPath;  // Path to display in an <img> element
}

export async function pickFromGallery(): Promise<string[]> {
  const { results } = await Camera.chooseFromGallery({
    mediaType: MediaTypeSelection.Photo,
    allowMultipleSelection: true,
    limit: 5,
  });
  return results.flatMap((item) => (item.webPath ? [item.webPath] : []));
}
```

`Camera.getPhoto()` and `Camera.pickImages()` still work but are deprecated since plugin version 8.1; new code should use `takePhoto` and `chooseFromGallery`.

```typescript
// src/native/notifications.ts — Push notifications (iOS and Android only)
import { PushNotifications } from "@capacitor/push-notifications";

export async function registerPush(sendTokenToServer: (token: string) => Promise<void>) {
  // Attach listeners before register() so the first event is not missed
  await PushNotifications.addListener("registration", (token) => {
    void sendTokenToServer(token.value);  // FCM token on Android, APNs token on iOS
  });
  await PushNotifications.addListener("registrationError", (err) => {
    console.error("Push registration failed:", err.error);
  });
  await PushNotifications.addListener("pushNotificationReceived", (notification) => {
    console.log("Received:", notification.title, notification.body);
  });

  let status = await PushNotifications.checkPermissions();
  if (status.receive === "prompt") status = await PushNotifications.requestPermissions();
  if (status.receive !== "granted") throw new Error("Push permission denied");
  await PushNotifications.register();
}
```

```typescript
// src/native/filesystem.ts — File system access
import { Directory, Encoding, Filesystem } from "@capacitor/filesystem";

export async function saveFile(data: string, filename: string) {
  await Filesystem.writeFile({
    path: filename,
    data: data,
    directory: Directory.Documents,
    encoding: Encoding.UTF8,  // Without an encoding, data must be base64
  });
}

export async function readFile(filename: string): Promise<string> {
  const result = await Filesystem.readFile({
    path: filename,
    directory: Directory.Documents,
    encoding: Encoding.UTF8,
  });
  return result.data as string;
}
```

### Platform Detection

```typescript
// src/utils/platform.ts — Adapt behavior per platform
import { Capacitor } from "@capacitor/core";
import { Share } from "@capacitor/share";

export const isNative = Capacitor.isNativePlatform();
export const platform = Capacitor.getPlatform(); // 'ios' | 'android' | 'web'
export const hasPush = Capacitor.isPluginAvailable("PushNotifications");

// Use the native share sheet when available, fall back to the clipboard
export async function shareLink(title: string, url: string) {
  const { value: canShare } = await Share.canShare();
  if (canShare) await Share.share({ title, url });
  else await navigator.clipboard.writeText(url);
}
```

## Examples

### Example 1: Convert a React app to mobile

**User prompt:** "I have a React web app built with Vite. Make it available on iOS and Android."

```bash
npm install @capacitor/core @capacitor/ios @capacitor/android
npm install -D @capacitor/cli
npx cap init "Trailhead" dev.fernhill.trailhead --web-dir dist
npm run build
npx cap add android && npx cap add ios
npx cap sync
npx cap run android
```

`cap add` creates the `android/` and `ios/` folders (commit them), and `cap sync` reports each step (Android part shown; iOS prints the same):

```text
✔ Copying web assets from dist to android/app/src/main/assets/public in 1.36ms
✔ Creating capacitor.config.json in android/app/src/main/assets in 229.50μs
✔ copy android in 3.65ms
✔ Updating Android plugins in 232.46μs
✔ update android in 8.14ms
[info] Sync finished in 0.021s
```

The app then installs and opens on the connected emulator or device. `npx cap doctor` lists installed versus latest Capacitor packages when something looks off.

### Example 2: Add camera and file upload

**User prompt:** "Add photo capture and file upload to my mobile web app."

```bash
npm install @capacitor/camera
npx cap sync
```

Add `NSCameraUsageDescription`, `NSPhotoLibraryUsageDescription` and `NSPhotoLibraryAddUsageDescription` strings to `ios/App/App/Info.plist`, then upload the captured file:

```typescript
import { takePhoto } from "./native/camera";

export async function uploadReceipt(): Promise<void> {
  const webPath = await takePhoto();
  if (!webPath) return;
  const blob = await (await fetch(webPath)).blob();
  const form = new FormData();
  form.append("receipt", blob, "receipt.jpg");
  await fetch(`${import.meta.env.VITE_API_URL}/receipts`, { method: "POST", body: form });
}
```

On a device the native camera opens and the photo is posted as multipart form data; in a browser the same code falls back to a file input (or the PWA Elements camera UI if installed).

## Guidelines

- **`npx cap sync` after every web build and every plugin install** — copies assets and updates native dependencies
- **`npx cap run` for testing** — faster than opening IDE each time
- **Platform detection** — `Capacitor.isNativePlatform()` for conditional native features
- **Not every plugin runs on the web** — Camera, Filesystem and Share have web implementations; Push Notifications does not. Guard with `Capacitor.isPluginAvailable()`
- **Live reload for development** — `npx cap run ios --live-reload --host 192.168.1.68 --port 5173`; the dev server must listen on `0.0.0.0` and the device must be on the same network. Never commit a `server.url` entry in the config
- **Custom native code** — write Swift/Kotlin plugins when needed
- **App Store deployment** — archive in Xcode for iOS; Android Studio or `npx cap build android` for Android. iOS builds require a Mac
- **`capacitor.config.ts`** — configure server URL, plugins, app info
- **Permissions in native config** — camera, location, etc. need `Info.plist` usage descriptions; on Android the Camera plugin needs no permission unless `saveToGallery` is used
- **Push setup is native** — Android needs the Firebase `google-services.json` in `android/app/`; iOS needs the Push Notifications capability and the two `AppDelegate.swift` forwarding methods from the plugin README
- **Upgrading** — keep `@capacitor/core`, `cli`, `ios` and `android` on the same version; `npx cap migrate` moves a Capacitor 7 project to 8 (Node 22+, Xcode 26+, iOS 15 deployment target, `minSdkVersion` 24). New iOS projects use Swift Package Manager; pass `--packagemanager CocoaPods` to `cap add ios` to keep CocoaPods
- **Not for gaming** — great for content apps, tools, dashboards; use Unity/Flutter for games

Files in this skill

  • SKILL.md5.6 KB
  • _scores.json1.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.