Live presentation: Top 10 Ways to Launch a Successful Mobile Project


Updating Capacitor to 3.0 in your plugin

There are several required and recommended changes for plugins that are being updated to Capacitor 3.

🚧 This guide is a work-in-progress. Thanks for your patience!

Planning for a Core API

It is currently difficult for the core team to make changes to the internals of Capacitor without potentially affecting plugins. Because most classes and methods in Capacitor 2 are public for both iOS and Android, we have observed undesired usage of Capacitor APIs that we considered internal.

During Capacitor 3 development, we will be evaluating this problem and creating an official public API for plugins, which will be documented here.


Use the new @CapacitorPlugin annotation

The @NativePlugin annotation is deprecated. We now recommend using the new @CapacitorPlugin annotation, which will allow for the new permissions API.

The name attribute is the same. The requestCodes and permissionRequestCode attributes are removed. The permissions attribute will need to be replaced with list of @Permission annotations, each containing a list of manifest strings and their corresponding alias, which you can omit for now until the new permissions API is implemented in your plugin.

     name = "FooBar",
-    requestCodes = {
-        FooBarPlugin.REQUEST_SOME_METHOD,
-    },
-    permissionRequestCode = FooBarPlugin.REQUEST_ALL_PERMISSIONS,
-    permissions = { Manifest.permission.FOO, Manifest.permission.BAR }
+    permissions = {
+        @Permission(strings = { Manifest.permission.FOO }, alias = "foo"),
+        @Permission(strings = { Manifest.permission.BAR }, alias = "bar")
+    })
 public class FooBarPlugin extends Plugin {
     static final int REQUEST_SOME_METHOD = 10051;
     static final int REQUEST_SOME_OTHER_METHOD = 10052;

Android request codes

Capacitor 3.0 implements the AndroidX Activity Result API and removes manually defined request codes. Instead of providing a request code and overriding handleOnActivityResult or handleRequestPermissionsResult, plugins should provide callback methods using the @ActivityCallback or @PermissionCallback annotations. These callbacks can then be referenced when launching a new Activity or Permission request.

-static final int IMAGE_REQUEST = 10052;

 public void chooseImage(PluginCall call) {
     Intent intent = new Intent(Intent.ACTION_PICK);
-    startActivityForResult(call, intent, IMAGE_REQUEST);
+    startActivityForResult(call, intent, "chooseImageResult");

+private void chooseImageResult(PluginCall call, ActivityResult result) {
+    if (result.getResultCode() == Activity.RESULT_CANCELED) {
+        call.reject("Activity canceled");
+    } else {
+        Intent data = result.getData();
+        // do something with the result data
+        call.resolve("Success!");
+    }


Weak References

The relationship between objects was updated in Capacitor 3 to fix memory leaks. The consequence is that a plugin’s references to objects higher in the hierarchy are now weak, which in Swift means they are optional. You are most likely to encounter this change when accessing bridge but it also applies to other properties such as webView. Calling a method on the bridge is relatively unchanged except that it now requires optional chaining:

-bridge.presentVC(myViewController, animated: true, completion: nil)
+bridge?.presentVC(myViewController, animated: true, completion: nil)

The biggest impact of this change is that all return values from the bridge will be optional as well. Safely handling and unwrapping optionals can require extra steps.

if bridge?.isSimEnvironment {
     // BAD: Will not compile. The boolean is optional (because `bridge` is optional)
     // and must be unwrapped before it can be evaluated.
if bridge?.isSimEnvironment == true {
     // NEUTRAL: An explicit comparison with an optional will work for a boolean but
     // may not be suitable for all data types.
if let isSim = bridge?.isSimEnvironment, isSim {
     // GOOD: Using optional binding to unwrap the optional before examining its value.

Bridge Changes

In addition to the reference changing from strong to weak, the API of the bridge itself has been updated (it is now exposed via a more formal protocol). Many of the properties and methods have been renamed but backwards support exists where possible by preserving and deprecating the old interfaces. Xcode will be able to automatically suggest the newer replacement in most cases. You should migrate any existing code so your plugin can build without compiler warnings.

Xcode autocorrect suggestion

CAPPluginCall Parameters

Capacitor includes a collection of convenience methods (getString, getDate, etc.) on CAPPluginCall to access the data passed from JavaScript to a plugin method. These have been updated for Capacitor 3.

  • get() has been removed. If you want to access the arguments directly, read the options dictionary.
  • hasOption has been deprecated. Use one of the typed accessors to check for a value.
  • Any accessor that takes a default value now requires a non-optional default but returns a non-optional result. This can change the optionality of your local variables but should reduce the usage of force unwrapping, which is an anti-pattern in Swift.
  • The behavior around dates and null values has been slightly changed and better documented. Find more information here.
  • The Obj-C convenience accessors have been split out to avoid conflicts with the Swift implementations. If you are working in Obj-C, you will need to import them separately by adding #import <Capacitor/CAPBridgedJSTypes.h> to your .m file.

Changes to PluginCall & CAPPluginCall

Use resolve() and reject()

We believe resolve() and reject() better reflect the Promise-like flow intended for plugin methods. They should be preferred over success() and error() (now deprecated), even in callback-style plugin methods.

resolve() without arguments now resolves with undefined

Previously, calling resolve() with no arguments resulted in an empty object being sent to the JavaScript layer. Because this is unlike the behavior of JavaScript’s Promise.resolve(), as of Capacitor 3, undefined is sent instead.

Saving calls

The save() method has deprecated and a replacement keepAlive property has been added. The recommended patterns for saving a call have been documented to clarify the behavior. Read more about that here.



Evaluate error handling

We are now recommending that plugin authors make use of the new error codes in Capacitor 3:

  • Unavailable: indicates the functionality can’t be used right now
  • Unimplemented: indicates the functionality can’t or won’t be implemented, or may be implemented in the future

Read more about Error Handling for Web, iOS, and Android.

Adopting the new Permissions API

Prior to 3.0, it was expected that permissions were automatically requested by a plugin before feature use. For example, a Geolocation plugin would automatically request permission when the user location was requested for the first time and then continue appropriately if the permission was granted or denied.

One goal of Capacitor 3 is to give app developers the ability to request or check permissions at any time and control how and when the user prompts are presented. This provides more flexibility in the user experience by allowing the app to respond to the user’s choice in a variety of ways.

It is perfectly fine to continue automatically requesting permissions, but you are encouraged to adopt the new permissions pattern as well to give app developers control over permissions.

Learn how to implement the Permissions API in your plugin ›

Contribute ->