| title | Capacitor |
|---|---|
| description | The platform, plugins, plugin calls and listeners of a Capacitor app, as its WebView reports them. |
The Capacitor tab shows a Capacitor app from the inside: its platform and environment, the plugins it can use, each plugin call with its arguments and outcome, and its plugin listeners. The data comes from the running app, through the browser overlay. To connect an app, follow Set up Capacitor.
The tab appears in the panel while a Capacitor app reports. In the hub, the Capacitor dock shows it, and shows the setup steps while no app reports. While no app reports, the dock's tooltip in the rail reads Capacitor: no app connected.
A strip at the top of the tab sums up the page:
| Item | What it says |
|---|---|
| Platform | The platform, and whether it is native or web. |
| Calls | Every call logged. Select it to show all calls again. |
| Failed | Calls that rejected. Select it to keep only those. |
| Slow, 500 ms+ | Calls that took 500 ms or more, or have waited that long for an answer. Select it to keep only those. |
| Pending | Calls without an answer yet. Select it to keep only those. |
| CapacitorHttp | On, Off or Unknown, as in Environment. |
| Plugin hints | How many rows of the Plugins table have a note. |
The selected count is marked as pressed, and selecting it again shows all calls. The call counts are left out on a page that logs no calls. A screen reader hears the strip change at most every two seconds, and hears nothing when a call arrives in the list.
| Field | What it is |
|---|---|
| Platform | Capacitor.getPlatform(), and whether it is native or web. |
| Server URL | Capacitor.getServerUrl(): where the WebView loads the app from. |
| CapacitorHttp | On when CapacitorHttp sends fetch through the native layer, as the native layer reports it. Unknown when it can't be read. |
| Calls logged | Native and web, Native only, Web only, or None on this platform. See Web calls. |
| User agent | The WebView's user agent. |
The Capacitor version is not available at runtime, so the tab doesn't show it. When several Capacitor apps report, pick one with Page.
Each call, newest first: Plugin.method(), its status, its duration, whether it went to the native bridge, and its time. The status chips have an icon and a label:
| Chip | Meaning |
|---|---|
| OK | The call resolved. |
| Error | The call rejected. |
| Not implemented | The platform has no implementation for it: the error code is UNIMPLEMENTED. |
| Pending | No answer yet. The row shows how long it has waited, such as waiting 3 s. |
| native, web | Whether the call went to the native bridge, or a web implementation answered it. |
| stream | A method that calls back more than once, such as Geolocation.watchPosition. |
A bar next to each duration compares it with the slowest call in the list.
Search by plugin or method, and pick All calls, Failed, Slow (500 ms or more) or Pending in the menu next to the search, or select a count in the summary. When nothing matches, the tab says which filters hide the calls, with Clear filters.
The list is one stop for the Tab key. Use the arrow keys, Home and End to move between calls, and Enter to select one. A selected call stays selected while more calls arrive.
A selected call shows three tabs:
| Tab | What it shows |
|---|---|
| Arguments | The arguments, masked. |
| Result or Error | The result, or the error with its code. A stream shows its latest result and how many times it called back. |
| Timing | When the call started and finished, and how long it took or has waited. |
Use the arrow keys, Home and End to move between the tabs.
Copy as JSON copies the call as the tab holds it. Copy as code copies the call as code to paste into the app:
await Camera.getPhoto({
quality: 90,
});Both copy the masked values the tab holds, so a masked value stays masked.
Every plugin the app imported or the native project has:
| Column | What it says |
|---|---|
| Available | Capacitor.isPluginAvailable() on this platform. |
| Imported | The app registered it with registerPlugin(), usually by importing its package. On iOS and Android this is Unknown unless the Capacitor hook loads before @capacitor/core, because the native layer lists every installed plugin. |
| Native | The native project has it. |
| Note | A plugin the app imports that is not available, with the command to run, such as npx cap sync android, and a Copy button. Or a native plugin the app does not import. Plugins @capacitor/core registers itself, such as WebView and CapacitorHttp, get no note. |
Each addListener registration: plugin, event, how many events it received, the last event, and whether it is still active. Last event shows the payload of the last event, masked like a call result, and its time. Only the last event is kept, not a history.
When the same plugin and event have more than one active listener, each of their rows says how many, such as 2 active listeners: possible leak. This often means a component adds a listener and never removes it.
Some plugins get a card with their current state, read from what the app already received: the results of its successful calls and the last event of its listeners. The panel never calls the plugin. Each field says where it came from and when, such as from foldStateChange, 3 s ago, or Not reported yet when the app never read it. The newest value wins.
The card shows when the page has the plugin. One plugin has a card: Foldable, from @erkamyaman/capacitor-foldable.
| Field | Read from |
|---|---|
| Fold state | state of getFoldState() and foldStateChange |
| Posture | posture of getFoldState() and foldStateChange |
| Hinge orientation | hingeOrientation of getFoldState() and foldStateChange |
| Hinge angle | angle of getHingeAngle() and hingeAngleChange |
| Active display | activeDisplay of getFoldState() and foldStateChange |
| Size class | widthClass and heightClass of getSizeClass() and sizeClassChange |
A plugin's native calls go through Capacitor.nativePromise and Capacitor.nativeCallback. The overlay wraps both on the page to watch the calls, and passes every argument and result through unchanged. It never adds plugin listeners of its own. This works with plugins the app registered before the overlay loaded, in Capacitor 6, 7 and 8.
The log starts when the overlay starts, unless the app loads the Capacitor hook earlier. See Startup calls.
A call that a plugin's web implementation answers never reaches the native bridge, and neither does the Not implemented error that @capacitor/core raises for a method no platform implements. The tab sees these calls only when @pangular-inspector/devtools/capacitor-hook runs before @capacitor/core. On the web, without the hook, the tab says that calls aren't shown.
Add the hook to the polyfills of your development configuration, so it loads before your app and stays out of production builds. A configuration's polyfills replace the list in the build's options, so copy that list and add the hook to it. In angular.json (excerpt):
{
"projects": {
"my-app": {
"architect": {
"build": {
"configurations": {
"development": {
"polyfills": ["zone.js", "@pangular-inspector/devtools/capacitor-hook"]
}
}
}
}
}
}
}This sample assumes the base list is ["zone.js"]. Leave zone.js out if your app doesn't use it. The hook starts on import, and does nothing when ngDevMode is off.
If the hook runs after @capacitor/core, it logs native calls only and changes nothing else. In that case, the tab still records a Not implemented error that your app leaves unhandled.
The overlay usually starts after bootstrap, so calls your app makes at startup, such as SplashScreen.hide() or StatusBar.setStyle(), happen before it. The Capacitor hook from Web calls starts logging at once and keeps the calls in a capped buffer. The overlay sends them when it connects.
Arguments, results, errors and the last event of each listener are masked like other live values, on the page and again on the server with its own redaction config:
- Fields with a secret-looking name, JWTs and bearer tokens are masked.
- Strings are masked like HTTP bodies before they are cut: a JSON string or a form body (
password=...) has its secret fields masked. - A
valuestored under a secret-lookingkeyis masked in the arguments and in the result, as inPreferences.set({key: 'authToken', value}). Only this shape is recognised: the name has to be in a field calledkey, next to a field calledvalue. - CapacitorHttp request URLs are masked like the URLs in SSR & HTTP, and secret headers are masked by name.
- Long strings, such as base64 photos, are cut. A listener's last event is cut shorter, at 200 characters a string.
Values that no rule recognizes as secret are shown as they are.
The overlay leaves its own requests to the devtools server out of the log.
| Tool | What it answers |
|---|---|
capacitor-list-calls |
Recent plugin calls, with filters for a plugin, failed calls and slow calls, then the active listeners with their last event. |
capacitor-list-plugins |
The environment and every plugin, with notes like the Plugins table. |
See Tools for their arguments.
The page and the server keep the latest limits.capacitorCalls calls, 200 by default. When older calls are dropped, the tab says how many.
To turn the inspector off, set inspectors.capacitor to false. The tab, the dock, the page collector and both agent tools go away. See Inspectors and agent tools.
@capacitor/core to log the calls their web implementations answer. See Web calls.
No. The app gets a promise that settles the same way, with the same arguments and results, and a rejection the app leaves unhandled still reaches unhandledrejection. The hook passes everything through once it stops.
No. It reads the results and events the app already received. A field the app never read says Not reported yet. Call the method in your app, or add the listener, to fill it.
They ran before the overlay started. Load the Capacitor hook from your polyfills. See Startup calls.