---
title: How to Detect the Network Status in Capacitor
description: "Learn how to detect the network status in a Capacitor app: check the connection type, listen for changes, and handle offline and metered connections."
date:
  created: 2026-08-08
  updated: 2026-08-08
authors:
  - robingenz
categories:
  - Capacitor
  - Guides
  - SDKs
links:
  - Capacitor Network: sdks/capacitor/network.md
faq: true
---

# How to Detect the Network Status in Capacitor

To detect the network status in a Capacitor app, install the [Capacitor Network plugin](../../sdks/capacitor/network.md), call [`getStatus()`](../../sdks/capacitor/network.md#getstatus) to read the current connection, and attach a [`networkStatusChange`](../../sdks/capacitor/network.md#addlistenernetworkstatuschange-) listener to get notified whenever it changes. The plugin tells you whether the device is connected, how it is connected (Wi-Fi, cellular, ethernet, or VPN), and things the browser's `navigator.onLine` can't: whether the connection has verified internet access, whether it is metered, and whether the user has enabled a data-saving mode.

In this guide, we build network detection up step by step: reading the current status, reacting to changes, telling "connected" apart from "actually online", handling metered and data-saving connections, checking airplane mode, and putting it all together in an offline banner.

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="https://capawesome.io/" target="_blank">
    <img alt="Build and deploy your Capacitor app with Capawesome Cloud" src="https://capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
  </a>
</div>

## Key Takeaways

- The [Capacitor Network plugin](../../sdks/capacitor/network.md) (`@capawesome/capacitor-network`) reads the network status on Android, iOS, and the web through a single TypeScript API, with no permissions or configuration required.
- [`getStatus()`](../../sdks/capacitor/network.md#getstatus) returns the connection state, the connection type (`WIFI`, `CELLULAR`, `ETHERNET`, `VPN`, `SATELLITE`, `NONE`, or `UNKNOWN`), and flags for constrained, expensive, and ultra-constrained connections.
- On Android, `internetReachable` reflects the system's [`NET_CAPABILITY_VALIDATED`](https://developer.android.com/reference/android/net/NetworkCapabilities#NET_CAPABILITY_VALIDATED){:target="_blank"} check, so captive portals and dead VPN tunnels don't count as "online".
- `constrained` detects Data Saver on Android and Low Data Mode on iOS; `expensive` detects metered Wi-Fi and cellular connections.
- The device is only observed while at least one listener is attached, so listening for changes doesn't cost battery when you don't need it.

## Getting the Current Network Status

Reading the network status takes a single call to [`getStatus()`](../../sdks/capacitor/network.md#getstatus). To install the Capacitor Network plugin first, please refer to the [Installation](../../sdks/capacitor/network.md#installation) section in the plugin documentation. Once installed, no configuration is needed; on Android, the plugin already declares the required `ACCESS_NETWORK_STATE` permission in its own manifest.

```typescript
import { Network } from '@capawesome/capacitor-network';

const logNetworkStatus = async () => {
  const status = await Network.getStatus();
  console.log('Connected:', status.connected);
  console.log('Connection type:', status.connectionType);
};
```

The returned [`GetStatusResult`](../../sdks/capacitor/network.md#getstatusresult) contains more than the two properties above:

- **`connected`**: whether the device is connected to any network.
- **`connectionType`**: how it is connected, as a typed [`ConnectionType`](../../sdks/capacitor/network.md#connectiontype) enum (`WIFI`, `CELLULAR`, `ETHERNET`, `VPN`, `SATELLITE`, `NONE`, or `UNKNOWN`).
- **`internetReachable`**: whether the connection has verified access to the internet (Android only, `null` elsewhere).
- **`constrained`**: whether a data-saving mode restricts the connection.
- **`expensive`**: whether the connection is metered.
- **`ultraConstrained`**: whether bandwidth is severely limited, for example on a carrier-provided satellite network.

A word on the `null` values you'll see in some of these properties: the plugin returns `null` wherever a platform can't determine the answer, instead of guessing. That makes the API honest about platform limits, and it's why the examples below compare against `false` explicitly rather than relying on truthiness.

## Listening for Network Changes

Polling `getStatus()` is the wrong tool for reacting to connectivity drops; instead, register a listener for the [`networkStatusChange`](../../sdks/capacitor/network.md#addlistenernetworkstatuschange-) event and let the plugin push updates to you:

```typescript
import { Network } from '@capawesome/capacitor-network';

const watchNetwork = async () => {
  await Network.addListener('networkStatusChange', status => {
    console.log('Network changed:', status.connectionType);
  });
};
```

The listener receives the same [`GetStatusResult`](../../sdks/capacitor/network.md#getstatusresult) shape as `getStatus()`, so switching from Wi-Fi to cellular, losing the connection entirely, or entering Low Data Mode all arrive through the same event. The plugin only observes the device while at least one listener is attached, so there is no background cost once you clean up.

When your feature no longer needs updates, remove the listeners with [`removeAllListeners()`](../../sdks/capacitor/network.md#removealllisteners):

```typescript
import { Network } from '@capawesome/capacitor-network';

const stopWatching = async () => {
  await Network.removeAllListeners();
};
```

## Why "Connected" Doesn't Mean Online

A device can be connected to a network without reaching the internet. The classic case is a captive portal: hotel or airport Wi-Fi reports a healthy connection, but every request is redirected to a login page until the user signs in. A VPN whose tunnel has silently died behaves the same way. If your app starts a sync the moment `connected` turns `true`, both cases produce failed requests and confused users.

This is what the `internetReachable` property is for. On Android, it reflects the [`NET_CAPABILITY_VALIDATED`](https://developer.android.com/reference/android/net/NetworkCapabilities#NET_CAPABILITY_VALIDATED){:target="_blank"} capability, meaning the operating system has actually verified that the connection reaches the internet:

```typescript
import { Network } from '@capawesome/capacitor-network';

const canSync = async () => {
  const { connected, internetReachable } = await Network.getStatus();
  return internetReachable ?? connected;
};
```

On iOS and the web, `internetReachable` is always `null`, because those platforms can't distinguish validated internet access from mere connectivity. The `?? connected` fallback above handles that cleanly: use the verified answer where the platform provides one, and fall back to the connection state everywhere else.

## Detecting Metered and Data-Saving Connections

Not every connection should be treated equally, even when it works perfectly. Users on metered hotspots or limited data plans don't want your app to pull hundreds of megabytes in the background, and both Android (Data Saver) and iOS (Low Data Mode) let them say so system-wide. The `expensive` and `constrained` properties expose exactly these signals, so a download queue can respect them:

```typescript
import { Network } from '@capawesome/capacitor-network';

const shouldDownloadLargeFiles = async () => {
  const { connected, expensive, constrained } = await Network.getStatus();
  return connected && expensive === false && constrained === false;
};
```

The strict `=== false` comparisons matter here. Both properties are `null` on platforms that can't determine them (for example, most browsers), and treating "unknown" the same as "cheap and unrestricted" would defeat the purpose of the check.

## Checking Airplane Mode on Android

When the connection type is `NONE`, it helps to tell the user why. On Android, [`isAirplaneModeEnabled()`](../../sdks/capacitor/network.md#isairplanemodeenabled) answers one common cause directly:

```typescript
import { Network } from '@capawesome/capacitor-network';

const explainOffline = async () => {
  const { enabled } = await Network.isAirplaneModeEnabled();
  return enabled
    ? 'Airplane mode is on. Disable it to reconnect.'
    : 'You are offline. Check your connection.';
};
```

This method is Android-only, since iOS offers no public API for reading the airplane mode state and browsers don't expose it either.

## Building an Offline Banner

The most common use of network detection is also the simplest: an offline banner that appears when the connection drops and disappears when it comes back. Combining the initial status read with the change listener covers both the app launch and every change afterwards:

```typescript
import { Network } from '@capawesome/capacitor-network';

const toggleOfflineBanner = (offline: boolean) => {
  document.getElementById('offline-banner')?.classList.toggle('hidden', !offline);
};

const setupOfflineBanner = async () => {
  const status = await Network.getStatus();
  toggleOfflineBanner(!status.connected);

  await Network.addListener('networkStatusChange', status => {
    toggleOfflineBanner(!(status.internetReachable ?? status.connected));
  });
};
```

The same pattern maps directly to a state variable in Angular, React, or Vue: read once on startup, subscribe for changes, and drive the banner from a single boolean. Note the reachability fallback from earlier reappearing in the listener, so Android users behind a captive portal see the banner even though they are technically connected.

## FAQ

### What is the difference between `connected` and `internetReachable`?

`connected` tells you whether the device is on any network at all, while `internetReachable` tells you whether that network has verified access to the internet. The two disagree behind captive portals and broken VPN tunnels, where the device is connected but nothing gets through. `internetReachable` is only available on Android and is `null` on iOS and the web.

### How is this plugin different from the official Capacitor Network plugin?

The official `@capacitor/network` plugin reports the connection state and a basic connection type. The [Capacitor Network plugin](../../sdks/capacitor/network.md) from Capawesome additionally reports verified internet reachability on Android, data-saving and metered connection flags, satellite and ultra-constrained network detection, an airplane mode check, and distinguishes ethernet and VPN connections as their own connection types.

### Do I need any permissions to detect the network status?

No. The plugin works without configuration on all platforms. The `ACCESS_NETWORK_STATE` permission it needs on Android is declared in the plugin's own manifest, so there is nothing to add to your app.

### Does network detection work in the browser?

Yes. On the web, the plugin reads [`navigator.onLine`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine){:target="_blank"} and the [Network Information API](https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API){:target="_blank"} where the browser supports it. Properties that browsers can't provide, such as `internetReachable`, are `null` there.

### Why is a VPN connection reported as `UNKNOWN` on iOS?

On iOS, the plugin reads the network status from the [`NWPathMonitor`](https://developer.apple.com/documentation/network/nwpathmonitor){:target="_blank"} of the Network framework, which does not identify VPN tunnels as a distinct interface type. The `VPN` connection type is therefore only reported on platforms that can detect it, such as Android.

## Try Capawesome

The quickest way to make this stick is to add the offline banner from this guide to your own app. Subscribe below to get new Capacitor guides like this one as they are published.

[Subscribe to the Capawesome Newsletter](https://capawesome.io/newsletter/){ .md-button .md-button--primary }

## Conclusion

Detecting the network status in a Capacitor app comes down to two calls: [`getStatus()`](../../sdks/capacitor/network.md#getstatus) for the current state and a [`networkStatusChange`](../../sdks/capacitor/network.md#addlistenernetworkstatuschange-) listener for everything after. The properties beyond `connected` are where the real quality wins live: `internetReachable` keeps captive portals from looking like working connections, and `expensive` and `constrained` keep large downloads off networks where they hurt.

There is one connection type we deliberately skipped here: satellite. Detecting it, and adapting your app to its extreme bandwidth limits on Android 15+ and iOS 26, is covered in [How to Detect Satellite Networks in Capacitor](./how-to-detect-satellite-networks-in-a-capacitor-app.md). If you have questions, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW){:target="_blank"}, and subscribe to the [Capawesome newsletter](https://capawesome.io/newsletter/){:target="_blank"} to stay up to date with new plugins and guides.
