Skip to content
Learni
View all tutorials
Développement Mobile

How to Create a Hybrid Mobile App with Ionic and Capacitor in 2026

Lire en français

Introduction

Hybrid mobile apps are revolutionizing development in 2026: a single JavaScript/TypeScript codebase to target web, iOS, and Android simultaneously. Ionic, with its UI framework built on standard web components (StencilJS), and Capacitor, Ionic's modern native engine, let you access native APIs like the camera or notifications through bridge plugins.

Why choose this stack? Unlike React Native (heavier), Ionic + Capacitor delivers near-native performance via an optimized WebView, ultra-fast hot reload, and simplified maintenance. Imagine: your PWA becomes native with just a few commands. This intermediate tutorial guides you step-by-step through a complete project with an interactive home page and camera plugin. At the end, you'll build for Android/iOS. Estimated time: 30 min. Ideal for React devs conquering mobile without rewriting everything.

Prerequisites

  • Node.js 20+ and npm 10+
  • React and TypeScript knowledge
  • Ionic CLI (installed below)
  • Android Studio (for Android) or Xcode 15+ (for iOS, macOS required)
  • Capacitor CLI
  • Configured Android emulator or iOS simulator

Install Ionic CLI and Create the Project

terminal
npm install -g @ionic/cli@latest
ionic start photoGallery tabs --type react --capacitor
cd photoGallery
ionic serve

This command installs the Ionic CLI globally, creates a React tabs project with integrated Capacitor, and starts the dev server. The 'tabs' template provides a ready-to-navigate structure. Skip 'blank' for a quick start; always test with 'ionic serve' to verify hot reload.

Generated Project Structure

Your project includes:

  • src/ : React/Ionic components (pages, hooks).
  • capacitor.config.ts : Capacitor configuration.
  • android/ and ios/ : Generated after sync.

Open http://localhost:8100: tabs for Explore, Tab1, Tab2 with Material Design-like UI. Think of it like Next.js but for mobile, with Ionic routing.

Configure Capacitor for Native Plugins

capacitor.config.ts
import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.learni.photogallery',
  appName: 'Photo Gallery',
  webDir: 'build',
  bundledWebRuntime: false,
  plugins: {
    Camera: {
      permissions: {
        camera: true,
        photos: true
      }
    }
  }
};

export default config;

This file sets the unique app ID (for stores), web build folder, and enables the Camera plugin with iOS/Android permissions. 'bundledWebRuntime: false' uses the native WKWebView for maximum performance. Pitfall: forget 'webDir' and the web build will fail.

Add the Camera Plugin and Sync

terminal
npm install @capacitor/camera @capacitor/core
npm install @capacitor/android @capacitor/ios
npx cap sync
npx cap add android
npx cap add ios

Installs the official Camera plugin, native platforms, syncs web to native, and adds Android/iOS projects. 'cap sync' copies assets and regenerates. Tip: run after any dependency changes to avoid native crashes.

Integrate the Camera into a Page

Now update src/pages/Explore.tsx to capture and display photos. Use Ionic's useCamera hook.

Explore Page with Photo Capture

src/pages/Explore.tsx
import { useState } from 'react';
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonFab, IonFabButton, IonIcon, IonImg, useCamera } from '@ionic/react';
import { camera } from 'ionicons/icons';

const Explore: React.FC = () => {
  const [photo, setPhoto] = useState<string>();
  const { photo: cameraPhoto, takePhoto } = useCamera();

  const takePicture = async () => {
    const photoTaken = await takePhoto({
      quality: 90,
      allowEditing: true,
      resultType: 'dataUrl'
    });
    setPhoto(photoTaken.dataUrl);
  };

  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Explore</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        {photo && <IonImg src={photo} alt="Photo capturée" />}
        <IonFab vertical="bottom" horizontal="center" slot="fixed">
          <IonFabButton onClick={takePicture}>
            <IonIcon icon={camera}></IonIcon>
          </IonFabButton>
        </IonFab>
      </IonContent>
    </IonPage>
  );
};

export default Explore;

This complete component uses the useCamera() hook to take an editable photo (90% quality), displays it with , and adds a floating FAB. The hook handles permissions automatically. Pitfall: without resultType: 'dataUrl', it won't display in web; test on device for permissions.

Build Web and Sync Capacitor

terminal
npm run build
npx cap sync
npx cap open android
# Ou pour iOS : npx cap open ios

Builds the web app for production (build), syncs to native, and opens Android Studio/Xcode. Launch the emulator from the IDE. Don't skip 'sync': natives won't see updated assets.

Test on Device

Web : ionic serve.
Android : Studio > Run.
iOS : Xcode > Run.
Camera permissions prompted automatically. Debug: Chrome DevTools via chrome://inspect for Android.

Custom Android Permissions

android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <uses-permission android:name="android.permission.CAMERA" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  <uses-feature android:name="android.hardware.camera" android:required="true" />
  <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name">
    <activity android:exported="true" android:launchMode="singleTask" android:name="com.getcapacitorcommunity.camera.MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>
</manifest>

Key excerpt from the Android Manifest: adds camera/storage permissions. 'required=true' enforces camera presence. Edit after 'cap sync'; rebuild APK. iOS equivalent: Info.plist in Xcode.

Best Practices

  • Always sync after build : npx cap sync before opening any IDE.
  • Use Ionic hooks for plugins: abstracts permissions/state.
  • PWA first : test web before native to catch 80% of bugs.
  • Handle offline errors : Capacitor plugins wrap try/catch.
  • Version capacitor.config.ts in Git: key for CI/CD.

Common Errors to Avoid

  • Forget npm run build : natives load broken dev version.
  • Undeclared permissions : silent crashes on device (add to Manifest/Info.plist).
  • resultType: 'uri' without storage : lost photos (prefer 'dataUrl' for simplicity).
  • No 'bundledWebRuntime: false' : degraded WebView perf on iOS.

Next Steps

  • Advanced plugins: Geolocation, Push Notifications (Capacitor docs).
  • State management: Zustand or Jotai for complex apps.
  • CI/CD: GitHub Actions for auto APK/IPA builds.
Check out our Learni Dev mobile hybrid training and master Flutter or React Native.
How to Build Ionic Capacitor Hybrid App 2026 | Learni