Recommended resources
The freely available resources this lesson will direct you to.
Hello! Last time, we built up your diagnostic toolkit for Android, covering how to troubleshoot common build failures in Gradle, the Android SDK, and the AndroidManifest.xml. This knowledge is crucial for maintaining the native layer of your application.
Now, we're going to shift our focus from the build process to the runtime architecture that makes Capacitor work. This lesson addresses the core of Capacitor's functionality: the plugin model. We will dissect how your React code, running in a simple WebView, can reach out and command native device hardware like the camera or biometrics sensor. Our goal is to explain how JavaScript calls are dispatched to native Swift and Kotlin code, a process that might feel conceptually similar to the bridge mechanisms in React Native or the IPC in Electron, both of which you've encountered before.
Understanding this bridge is fundamental to achieving your goal of integrating features like push notifications and filesystem access.
The Three Pillars of a Capacitor App
At its heart, a Capacitor application consists of three main components that work in concert: the native shell, the WebView, and the Capacitor bridge. You've already worked with the first two: the native shell is the Xcode or Android Studio project itself, and the WebView is the component inside that shell where your React SPA is rendered.
The missing piece is the bridge that connects them. The Capacitor team provides an excellent high-level diagram that illustrates this relationship.

Let's break down what's happening here:
- Web App in WebView: Your React application (HTML, CSS, JS) runs inside a native WebView control. Think of a WebView as a lightweight, chromeless browser window embedded directly into the native app screen. This is a very similar concept to how Electron hosts web content.
- Capacitor Runtime & Bridge: This is the engine of Capacitor. It's a layer of native code that is injected into your app. Its primary jobs are to load plugins and facilitate communication between the WebView and the native code.
- Plugins: These are bundles of native code (Swift/Kotlin) that expose specific device features (like Camera, Filesystem, or Push Notifications) through a consistent JavaScript API.
The "magic" happens in how a call from your JavaScript code travels through the bridge to execute a function in a native plugin and get a result back.
The Journey of a Plugin Call
The process of calling a native feature from JavaScript is a well-defined, asynchronous message-passing flow. Let's trace this journey from start to finish. For a detailed overview of the bridge and runtime, the official "How Capacitor Works" blog post is an excellent resource.
Capacitor Blog - How Capacitor Works
This article provides a concise explanation of Capacitor's core architectural components.
Please read the sections Native Bridge and Runtime. As you read, focus on how the window.Capacitor.Plugins object is created and how the system manages asynchronous calls between the WebView and the native layer.
As you just read, the entire process is asynchronous. Let's formalize the step-by-step flow of a call like Camera.getPhoto():
- JavaScript Call: Your React component calls
Camera.getPhoto(). TheCameraobject you import from a Capacitor package (e.g.,@capacitor/camera) is not the native implementation itself. It's a JavaScript proxy object. - Proxy & Serialization: This proxy object, created via a function called
registerPluginfrom@capacitor/core, intercepts the call. It packages the plugin name ("Camera"), the method name ("getPhoto"), and any options into a JSON object. - Dispatch to Native: The proxy sends this serialized message across the JavaScript-to-native bridge. At this point, it returns a
Promiseto your web app, which willawaita response. - Native Runtime Routing: The Capacitor Runtime on the native side receives the message. It looks up the native plugin registered under the name "Camera" and finds the method corresponding to "getPhoto".
- Native Execution: The runtime invokes the actual native Swift or Kotlin function. This function then interacts with the underlying OS APIs to perform the requested action, such as opening the camera UI.
- Native Response: Once the native task is complete (e.g., the user takes a picture), the native function calls back to the Capacitor Runtime with the result (e.g., the image file path). This is typically done using a
resolvemethod on a call object. - Return to JavaScript: The Capacitor Runtime sends a response message back across the bridge to the WebView.
- Promise Resolution: The JavaScript-side of the bridge receives the response and uses it to resolve or reject the original
Promisefrom step 3. Yourawaitcompletes, and your React code now has the result from the native operation.
The Native Implementation: Swift and Kotlin/Java
To make this concrete, let's look at what the native side of a plugin actually looks like. You don't need to be a native developer to use plugins, but seeing the structure demystifies the process. Your extensive development background means you'll recognize the patterns of class extension and method decoration immediately.
The video "How to create project specific local plugin" provides a clear, practical demonstration of creating the native and web portions of a plugin.
How to create project specific local plugin | Ionic | Capacitor
This video walks through building a simple plugin from scratch. We will focus on the key parts that define the native interface.
Android (Java): Watch the segment from Android setup. Notice how the Java class extends Plugin, is annotated with @CapacitorPlugin, and the exposed method getHeight is annotated with @PluginMethod. The PluginCall object is the key to receiving data and sending a response with call.resolve(). iOS (Swift): Now watch the iOS portion from iOS setup. The pattern is analogous. The Swift class extends CAPPlugin, the class and method are marked with @objc, and the method receives a CAPPluginCall object used to resolve() the call. JavaScript Interface: Finally, see how the web interface is created from JS interface. The registerPlugin('MyStatusBar') call is what creates the JavaScript proxy object. The name "MyStatusBar" is the string key that links the JS object to the native classes you just saw.
The core components are:
-
For Android (Kotlin/Java):
- A class that extends
com.getcapacitor.Plugin. - An
@CapacitorPlugin(name = "...")annotation on the class to register it with the runtime. @PluginMethodannotations on public methods to expose them to the bridge.- Each method receives a
PluginCallobject, which is used to get parameters and return data viacall.resolve()orcall.reject().
- A class that extends
-
For iOS (Swift):
- A class that extends
CAPPlugin. - An
@objcdecorator on the class. - An
@objcdecorator on each function exposed to the bridge. - Each function receives a
CAPPluginCallobject for parameters and returning data. - An additional Objective-C file (
.m) is needed to bridge the Swift code, using macros likeCAP_PLUGINandCAP_PLUGIN_METHODto register the plugin and its methods.
- A class that extends
This consistent, symmetrical structure across both platforms is what allows a single JavaScript API call to work seamlessly on iOS and Android.
Contrasting with Your Experience: Electron and React Native
Given your background, it's useful to place Capacitor's model in context with other frameworks you've used.
-
Capacitor vs. Electron: Electron uses Inter-Process Communication (IPC) to send messages between the web-based renderer process (
ipcRenderer) and the Node.js-based main process (ipcMain). This is architecturally very similar to Capacitor's bridge—both are asynchronous message-passing systems connecting a web environment to a more privileged backend. The key difference is the target: Electron's backend is a Node.js server environment for desktop apps, while Capacitor's is the native iOS/Android runtime for mobile apps. Capacitor's plugin model provides a more formalized contract (@PluginMethod,CAPPluginCall) compared to the more free-form event listeners of IPC. -
Capacitor vs. React Native: React Native also uses a bridge to communicate between a JavaScript thread and the native platform. However, the purpose is fundamentally different. In React Native, your JavaScript code dictates the rendering of native UI components. When you write
<View>, it becomes a nativeUIVieworandroid.view.View. The bridge is constantly busy communicating layout and UI updates. In Capacitor, the UI is 100% web-based, living entirely within the WebView. The bridge is used only when you need to escape the web sandbox to access a native device service (like biometrics). This makes the Capacitor bridge much "quieter" and results in a different development experience: you build your UI with standard web tools (DOM, CSS), and only "call out" to native for specific services.
Conclusion
In this lesson, we've unpacked the mechanism that gives your web app its native superpowers. You now understand the complete lifecycle of a plugin call and the architecture that enables it.
Here are the key takeaways:
- The Architecture: A Capacitor app is a native shell hosting a WebView. A Bridge and Runtime layer connects the two.
- The Call Flow: A JavaScript call on a plugin proxy is serialized into a JSON message, sent across the bridge, and handled by the native runtime, which invokes the corresponding native Swift/Kotlin method. The result is sent back to resolve the original JavaScript Promise.
- The Native Side: Native plugin classes on iOS and Android follow a clear, annotation-based pattern (
@PluginMethod,@objc) and use aPluginCallobject to manage the asynchronous communication back to the web view. - The Web Side: The
@capacitor/corepackage'sregisterPluginfunction is the key to creating the JavaScript proxy that initiates this entire process.
In our next lesson, we will put this knowledge into immediate practice. We'll install the @capacitor/push-notifications plugin, which will require us to both use its JavaScript API in our React code and perform the necessary native configuration in Xcode and Android Studio, perfectly illustrating the two sides of the bridge we've explored today.