--- url: 'https://capacitor.nativescript.org/introduction.md' --- ## A Brief Introduction [Just take me to the Installation Guide!](installation.md) :::tip Note **8.x** targets **Capacitor 8** on **iOS and Android**. Installation is fully additive, with zero modifications to your Xcode or Android projects. For CocoaPods-based apps, use `@nativescript/capacitor@5`. ::: ## What is NativeScript? NativeScript enables direct native platform API usage from JavaScript. It achieves this through the provided iOS and Android runtimes which create a map of each platform API to your TypeScript codebase. With the NativeScript bundle located in `src/nativescript` within your project, you allow rich platform API development to flourish right from your Ionic web codebase. The result is a liberating development experience. ## It is uniquely delightful NativeScript is a **celebration** ๐ŸŽ‰ of the platform. In other words: * what the platform provides, NativeScript provides. * what the platform does well, NativeScript does well. * what the platform says is an error, NativeScript says is an error. * what the platform wants you to do, NativeScript wants you to do. The API's you interact with via NativeScript **are** the native platform api's. It's not "layers of abstraction" as often talked about amongst "cross platform frameworks". NativeScript simply provides a map of your platform to your TypeScript codebase and you just use everything that's made available. This means you can refer to "source" documentation like [Apple Developer Docs](https://developer.apple.com/documentation/technologies) and [Android Developer Docs](https://developer.android.com/reference) This is unique and **one of a kind**. ## Performance and Stability Performance is lightning โšก fast due to the "real time marshalling" of platform API calls directly from JavaScript. NativeScript is extremely stable and has been developed and deployed to enterprise production critical applications for well over 6+ years now. --- --- url: 'https://capacitor.nativescript.org/installation.md' --- # Installing @nativescript/capacitor :::tip Version note **8.x** works with **Capacitor 8** apps on **iOS and Android** (on iOS, Capacitor 6/7 apps must use [SPM mode](https://capacitorjs.com/docs/ios/spm)). Using CocoaPods? Use `@nativescript/capacitor@5` and the v5 docs. ::: * **iOS**: no CocoaPods, no Podfile edits, no AppDelegate changes, no Xcode build phases, no linker flags. The NativeScript runtime arrives as a Swift Package ([NativeScript/ios-spm](https://github.com/NativeScript/ios-spm)) when Capacitor syncs the plugin, and platform API metadata ships inside the runtime's framework with zero configuration. * **Android**: no `Application` replacement, no manifest edits, no `build.gradle` grafting, nothing copied into your repo. The plugin is a self-contained Gradle module that Capacitor wires up on sync; `nscap build` fetches the runtime once (cached) and generates platform metadata automatically. ## 1. Start from a Capacitor app Follow the [Ionic](https://ionicframework.com/getting-started) or [Capacitor](https://capacitorjs.com/docs/getting-started) getting started guide, and make sure your platforms are added: ```bash npx cap add ios npx cap add android ``` ## 2. Install and initialize ```bash npm install @nativescript/capacitor npx nscap init ``` `nscap init` is **additive and idempotent** โ€” it only creates files and npm scripts, never touching your Xcode project: * `src/nativescript/index.ts` โ€” your native TypeScript entry, with a working native modal example * `src/native-custom.d.ts` โ€” strongly type your own `native.*` helpers * npm scripts that wire `nscap build` into Capacitor's `capacitor:copy:before` hook, so your native code builds automatically on every `npx cap copy` / `npx cap sync` ## 3. Build and run ```bash npm run build # build your web assets as usual (any bundler) npx cap sync npx cap run ios # and/or: npx cap run android ``` That's the entire setup. At app launch you'll see the example's greeting in the native console, and from your web code you can immediately do: ```ts import { native } from '@nativescript/capacitor'; const version = await native.UIDevice.currentDevice.systemVersion.get; native.openNativeModalView(); // the scaffolded example helper ``` ## Requirements * Capacitor 8 app (on iOS, SPM is the Capacitor 8 default โ€” or Capacitor 6/7 with [SPM mode](https://capacitorjs.com/docs/ios/spm)) * iOS 15+, Xcode with the iOS SDK * Android minSdk 23+, Android SDK, JDK 17+ * Node 18+ ## Troubleshooting ### Blank webview after adding NativeScript If the app launches to a blank screen, your web assets were likely never built โ€” run your web build (e.g. `npm run build`) before `npx cap sync`. The NativeScript build hook creates the web assets folder, which can mask Capacitor's usual "web assets directory not found" error on brand-new apps. ### Where do my NativeScript `console.log`s go? To the native system log, not the webview console. On iOS, see them in the Xcode console, or: ```bash # iOS simulator xcrun simctl spawn booted log stream --predicate 'process == "App"' ``` On Android they appear in logcat under the `JS` tag: ```bash adb logcat -s JS ``` ### First build is slow The very first iOS build downloads the NativeScript runtime binaries through Swift Package Manager, and the first `nscap build` with an Android platform fetches the Android runtime once. Both are cached; subsequent builds are fast. --- --- url: 'https://capacitor.nativescript.org/getting-started.md' --- # @nativescript/capacitor is now ready Let's take a look at what you have at your fingertips now. ## `src/nativescript` Your project now contains a powerful isolated environment. This is where you can write as much NativeScript as your project needs for various platform features and capabilities. It is structured as follows: ``` . โ”œโ”€ src/native-custom.d.ts // strongly type your own native.* helpers โ””โ”€ src/nativescript โ”œโ”€ examples โ”‚ โ””โ”€ modal.ts โ””โ”€ index.ts ``` Unlike v5, there is **no nested npm project**, no separate `package.json`, `tsconfig.json`, or second `npm install` inside `src/nativescript`. It's just TypeScript, bundled in milliseconds by `nscap build`. The `index.ts` alongside the `examples` folder is a great example of how you can scale out native platform features by creating your own organization of additional native helper methods to import and bundle together for your app's usage. You can make direct native platform API calls here as well as attach additional methods to the `native` object which your web application can use. ### `index.ts`: The very first line is important, it initializes the Capacitor communication: ```typescript // keep this import first: it boots the NativeScript bridge import '@nativescript/capacitor/bridge'; ``` This file allows you to organize various native helpers in your own folder structure and bring together into the single bundle which is delivered with your Capacitor app. [Learn more about the bridge api here](bridge-api) ## Isolated bundle The `src/nativescript` area of your codebase is completely isolated from the rest of your web codebase. This means you are free to go wild with all sorts of native platform behavior throughout your web application with confidence that the `native` object is only **active** when running on the native platform. It is bundled on its own completely separate from your web build, allowing it to operate purely as an additive power helper to your web app when running via Capacitor. The bundle is rebuilt automatically on every `npx cap copy` / `npx cap sync` via the `capacitor:copy:before` hook that `nscap init` added โ€” or on demand with `npm run build:nativescript`. ## SDK typings and extra metadata Two optional power-ups: ```bash npx nscap typings # full iOS SDK TypeScript declarations โ†’ src/nativescript/typings/ npx nscap metadata # custom iOS metadata, e.g. to expose extra frameworks ``` By default you don't need `nscap metadata` at all โ€” the standard iOS SDK surface ships inside the runtime's framework, and **Android metadata is generated automatically** by `nscap build` (standard SDK + runtime, cached). Generate custom iOS metadata only when you want `native.*` access to API surface beyond the standard SDK; declare extra search paths in `src/nativescript/metadata.json` (`frameworkPaths`, `headerPaths`). ## Debugging Verbose bridge marshalling logs are off by default: ```ts import { nativeDebug } from '@nativescript/capacitor'; nativeDebug(true); ``` --- --- url: 'https://capacitor.nativescript.org/explaining-the-examples.md' --- ## Explaining the examples The installation includes 1 simple example to give you ideas of possibilities. ### `examples/modal.ts` An example showing how to do some native ui blending to open a purely native platform modal on **both platforms**. This is exactly what `nscap init` scaffolds: ```typescript import { iosRootViewController } from '@nativescript/capacitor/bridge'; native.openNativeModalView = () => { if (native.isAndroid) { const activity = (global).androidCapacitorActivity; const builder = new android.app.AlertDialog.Builder(activity); builder.setTitle('Hello from NativeScript ๐Ÿš€'); builder.setMessage('A fully native Android dialog, built from TypeScript.'); builder.setPositiveButton('Close', null); builder.show(); console.log('NSCAP_MODAL presented'); return; } const vc = UIViewController.alloc().init(); vc.view.backgroundColor = UIColor.systemIndigoColor; const label = UILabel.alloc().initWithFrame( CGRectMake(0, 80, UIScreen.mainScreen.bounds.size.width, 50), ); label.text = 'Hello from NativeScript ๐Ÿš€'; label.textColor = UIColor.whiteColor; label.textAlignment = NSTextAlignment.Center; label.font = UIFont.boldSystemFontOfSize(28); vc.view.addSubview(label); iosRootViewController().presentViewControllerAnimatedCompletion(vc, true, () => { console.log('modal presented'); }); }; ``` Everything is a direct platform API call โ€” `UIViewController`, `UILabel`, `CGRectMake` on iOS exactly as in the [Apple Developer Docs](https://developer.apple.com/documentation/uikit); `android.app.AlertDialog.Builder` on Android exactly as in the [Android Developer Docs](https://developer.android.com/reference/android/app/AlertDialog.Builder). No subclassing, no bindings โ€” and the iOS completion callback demonstrates callbacks marshalling naturally across the bridge. Usage in your web codebase: ```typescript import { native } from '@nativescript/capacitor'; native.openNativeModalView(); // Open native modal ``` Declare it in `src/native-custom.d.ts` for full type support โ€” see [What is native-custom.d.ts?](native-custom) --- --- url: 'https://capacitor.nativescript.org/using-dev-nativescript.md' --- # Development Workflow The NativeScript bundle rebuilds automatically as part of your normal Capacitor loop โ€” the `capacitor:copy:before` hook runs `nscap build` (milliseconds) on every copy/sync: ```bash # after editing anything in src/nativescript npx cap run ios # or: npx cap run android ``` That's it: `cap run` syncs (rebuilding your NativeScript bundle via the hook), builds, and deploys. For web-side changes, use your framework's dev server as usual โ€” the `native` object is simply inactive in a plain browser, so your web dev loop is unaffected. :::tip v5 note The v5 `dev:nativescript` watcher CLI has been retired. On-device live reload of NativeScript code (without reinstalling the app) is on the 8.x roadmap via TKLiveSync, which already ships with the runtime. ::: --- --- url: 'https://capacitor.nativescript.org/using-build-mobile.md' --- # Building your project `nscap init` adds these npm scripts to your project: ```json "build:nativescript": "nscap build", "capacitor:copy:before": "nscap build" ``` ### `build:nativescript` Bundles just your NativeScript changes from `src/nativescript`. It is built into a `nativescript` folder inside your `webDir` as specified in your `capacitor.config.json` or `capacitor.config.ts`. Bundling uses esbuild and completes in milliseconds. When an Android platform is present, the same command also keeps the Android side ready: it fetches the NativeScript Android runtime once (cached) into the plugin module and generates platform metadata automatically โ€” you'll see it in the output: ``` โšก nscap: android: plugin module ready (runtime + metadata) ``` ### `capacitor:copy:before` This is a [Capacitor CLI hook](https://capacitorjs.com/docs/cli/hooks): it runs automatically **before every** `npx cap copy` and `npx cap sync`. In practice you never have to think about the NativeScript build at all โ€” your regular Capacitor workflow keeps everything in sync: ```bash npm run build # your web build, as usual npx cap sync ios # nscap build runs automatically, then Capacitor copies everything ``` :::tip Coming from v5? The v5 `build:mobile` / `npm-run-all` scripts are no longer needed โ€” the hook replaces them. Keep using your plain web `build` script. ::: ## Custom metadata rides along If you've generated custom platform metadata with `npx nscap metadata`, `nscap build` automatically bundles it alongside your NativeScript code. Without it, the runtime uses the SDK metadata bundled inside the NativeScript framework โ€” the build output tells you which is in effect: ``` โšก nscap: built dist/nativescript/index.js ยท framework-bundled SDK metadata (zero-config) ``` --- --- url: 'https://capacitor.nativescript.org/production-tips.md' --- # Production Tips The 8.x pipeline bundles your `src/nativescript` code with esbuild โ€” output is already compact and tree-shaken, and there is no separate "production mode" to configure. A few notes for release builds: * **Debug logging is already off by default.** Verbose bridge marshalling logs only appear if you call `nativeDebug(true)` โ€” make sure you're not shipping that call. * **Metadata footprint.** The platform API metadata lives in your app package โ€” ~8โ€“12 MB on iOS (from the NativeScript framework by default, or `nscap metadata`), ~2โ€“3 MB on Android (generated by `nscap build`). This matches the footprint every NativeScript app has always had โ€” it *is* the map of the platform APIs. * **NativeScript `console.log` output** goes to the system log in release builds too; strip or gate any noisy logging in your native helpers before shipping. :::tip v5 note The v5 webpack flags (`--env=uglify=true`, `--env=production=true`) no longer exist โ€” esbuild replaced the webpack pipeline. Bundle minification lands as an `nscap build` flag in a follow-up 8.x release. ::: --- --- url: 'https://capacitor.nativescript.org/what-is-native.md' --- # What is 'native'? The `native` object which `@nativescript/capacitor` provides is a [JavaScript Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). This allows the api to be *active* only when run on the native platform via Capacitor. Otherwise it is completely *inactive* when used inside a standalone web browser. The Proxy also allows each "dot notation" from it to translate and marshall "in real time" to your native platform. ## Quick access Using the `native` object is meant to be a quick access utility to anything you need from your native platform. It's also intended to be expanded upon with your own helper methods as you see fit. It exposes platform API's as made public by each native platform. All native properties which handle "primitive" types (string, number, boolean) use a standard interface for fetching native values from your platform device: ```typescript export interface NativeProperty { get: Promise set: (value: T) => Promise } ``` This means if you want to get your current device screen brightness, you could use something like this on iOS for example: ```typescript const value = await native.UIScreen.mainScreen.brightness.get; console.log(value); // your screen brightness value ``` And if you want to set the brightness: ```typescript native.UIScreen.mainScreen.brightness.set(.8); ``` --- --- url: 'https://capacitor.nativescript.org/what-is-the-bridge.md' --- # What is '@nativescript/capacitor/bridge'? 1. This bridge initializes the Capacitor communication. 2. Can only be used from within `src/nativescript`. 3. Contains helper methods to aid in common behaviors. :::tip Note Additional low level helper methods will grow in time here. ::: --- --- url: 'https://capacitor.nativescript.org/bridge-api.md' --- # @nativescript/capacitor/bridge The bridge is fundamental to NativeScript for Capacitor and also exposes a few utility methods for quick usage. ## iosRootViewController The root view controller of your Capacitor app (or topmost presenting controller if using in context of an existing modal). * `iosRootViewController()` A helper that you, yourself, could write inside `src/nativescript` on your own. We provide it as a convenience because it is so handy and often used. #### Example usage: ```typescript import { iosRootViewController } from "@nativescript/capacitor/bridge"; const vc = UIViewController.alloc().init(); vc.view.backgroundColor = UIColor.blueColor; iosRootViewController().presentViewControllerAnimatedCompletion(vc, true, () => { console.log('presented'); }); ``` The source of the helper: ```typescript export const iosRootViewController = () => { if (native.isAndroid) { console.log("iosRootViewController is iOS only."); } else { const app = UIApplication.sharedApplication; const win = app.keyWindow || (app.windows && app.windows.count > 0 && app.windows.objectAtIndex(0)); let vc = win.rootViewController; while (vc && vc.presentedViewController) { vc = vc.presentedViewController; } return vc; } }; ``` ## iosAddNotificationObserver (v2+) Add an iOS Notification observer. This will internally track the observer references however you will also receive the observer reference as the return value in case you need it. See `iosRemoveNotificationObserver` below for how to remove later. * `iosAddNotificationObserver: (notificationName: string, onReceiveCallback: (notification: NSNotification) => void) => any;` Learn more in [iOS docs](https://developer.apple.com/documentation/foundation/nsnotificationcenter). #### Example usage: ```typescript import { iosAddNotificationObserver } from "@nativescript/capacitor/bridge"; iosAddNotificationObserver('AnyEventName', (notification: NSNotification) => { console.log('AnyEventName:', notification.object) }); ``` The source of the helper: ```typescript let iosNotificationObserverClass; let iosNotificationObservers: Array; function ensureNotificationObserverClass() { if (iosNotificationObserverClass) { return; } @NativeClass class NotificationObserver extends NSObject { private _onReceiveCallback: (notification: NSNotification) => void; public static initWithCallback(onReceiveCallback: (notification: NSNotification) => void): NotificationObserver { const observer = super.new(); observer._onReceiveCallback = onReceiveCallback; return observer; } public onReceive(notification: NSNotification): void { this._onReceiveCallback(notification); } public static ObjCExposedMethods = { onReceive: { returns: interop.types.void, params: [NSNotification] }, }; } iosNotificationObserverClass = NotificationObserver; } export const iosAddNotificationObserver = (notificationName: string, onReceiveCallback: (notification: NSNotification) => void) => { ensureNotificationObserverClass(); const observer = iosNotificationObserverClass.initWithCallback(onReceiveCallback); NSNotificationCenter.defaultCenter.addObserverSelectorNameObject(observer, 'onReceive', notificationName, null); if (!iosNotificationObservers) { iosNotificationObservers = []; } iosNotificationObservers.push(observer); return observer; } ``` ## iosRemoveNotificationObserver (v2+) Remove an iOS Notification observer. * `iosRemoveNotificationObserver: (observer: any, notificationName: string) => void;` Learn more in [iOS docs](https://developer.apple.com/documentation/foundation/nsnotificationcenter). #### Example usage: ```typescript import { iosAddNotificationObserver, iosRemoveNotificationObserver } from "@nativescript/capacitor/bridge"; const observer = iosAddNotificationObserver('AnyEventName', (notification: NSNotification) => { console.log('AnyEventName:', notification.object) }); iosRemoveNotificationObserver(observer, 'AnyEventName'); ``` The source of the helper: ```typescript export const iosRemoveNotificationObserver = (observer: any, notificationName: string) => { if (iosNotificationObservers) { const index = iosNotificationObservers.indexOf(observer); if (index >= 0) { iosNotificationObservers.splice(index, 1); NSNotificationCenter.defaultCenter.removeObserverNameObject(observer, notificationName, null); } } } ``` ## androidCreateDialog Create Android fragment dialogs on the fly. * `androidCreateDialog(view: () => android.view.View, id?: string)` This method is another one that you, yourself, could write inside `src/nativescript`. It's another handy and convenient utility which can be used often for various native ui blending. #### Example usage: ```typescript androidCreateDialog(() => { const activity = (global).androidCapacitorActivity; const layout = new android.widget.LinearLayout(activity); layout.setGravity(android.view.Gravity.CENTER); layout.setOrientation(android.widget.LinearLayout.VERTICAL); const btn = new android.widget.Button(activity); btn.setText("Ionic"); layout.addView(btn); return layout; }); ``` The source of the helper: ```typescript let DialogImpl; let DialogFragmentImpl; if (native.isAndroid) { @NativeClass() class DialogImplClass extends android.app.Dialog { constructor(fragment, context, themeResId) { super(context, themeResId); return global.__native(this); } } DialogImpl = DialogImplClass; @NativeClass() class DialogFragmentImplClass extends androidx.fragment.app.DialogFragment { view: () => android.view.View; id: string; constructor(view: () => android.view.View, id?: string) { super(); this.view = view; this.id = id; return global.__native(this); } onCreateDialog(savedInstanceState) { super.onCreateDialog(savedInstanceState); const activity = (global).androidCapacitorActivity; const theme = this.getTheme(); // In fullscreen mode, get the application's theme. // theme = activity.getApplicationInfo().theme; const dialog = new DialogImpl(this, activity, theme); dialog.setCanceledOnTouchOutside(true); return dialog; } onCreateView(inflater: any, container: any, savedInstanceState: any) { return this.view(); } } DialogFragmentImpl = DialogFragmentImplClass; } export const androidCreateDialog = ( view: () => android.view.View, id?: string ) => { const df = new DialogFragmentImpl(view, id); const fragmentManager = (( global )).androidCapacitorActivity.getSupportFragmentManager(); df.show(fragmentManager, id || uniqueId()); }; // general internal utility const uniqueId = () => { return "_" + Math.random().toString(36).substr(2, 9); }; ``` ## androidBroadcastReceiverRegister Register an Android BroadcastReceiver. * `androidBroadcastReceiverRegister(intentFilter: string, onReceiveCallback: (context: android.content.Context, intent: android.content.Intent) => void): void` Learn more [in Android developer docs](https://developer.android.com/guide/components/broadcasts) as well as [the BroadcastReceiver api docs](https://developer.android.com/reference/android/content/BroadcastReceiver). #### Example usage: ```typescript const intentFilter = "android.os.action.POWER_SAVE_MODE_CHANGED"; androidBroadcastReceiverRegister(intentFilter, (context, intent) => { const manager: android.os.PowerManager = native.androidCapacitorActivity.getSystemService( android.content.Context.POWER_SERVICE ); console.log( `Power Save Mode is ${manager.isPowerSaveMode() ? "enabled" : "disabled"}` ); }); ``` ## androidBroadcastReceiverUnRegister Stop receiving broadcasts for the provided intent filter. * `androidBroadcastReceiverUnRegister(intentFilter: string): void` #### Example usage: ```typescript const intentFilter = "android.os.action.POWER_SAVE_MODE_CHANGED"; androidBroadcastReceiverUnRegister(intentFilter); ``` Both BroadcastReceiver utilities are also things you could write yourself but we provide as a convenience. The source of these helpers: ```typescript let androidBroadcastReceiverClass; let androidRegisteredReceivers: { [key: string]: android.content.BroadcastReceiver; }; function ensureBroadCastReceiverClass() { if (androidBroadcastReceiverClass) { return; } @NativeClass class BroadcastReceiver extends android.content.BroadcastReceiver { private _onReceiveCallback: ( context: android.content.Context, intent: android.content.Intent ) => void; constructor( onReceiveCallback: ( context: android.content.Context, intent: android.content.Intent ) => void ) { super(); this._onReceiveCallback = onReceiveCallback; return global.__native(this); } public onReceive( context: android.content.Context, intent: android.content.Intent ) { if (this._onReceiveCallback) { this._onReceiveCallback(context, intent); } } } androidBroadcastReceiverClass = BroadcastReceiver; } export const androidBroadcastReceiverRegister = ( intentFilter: string, onReceiveCallback: ( context: android.content.Context, intent: android.content.Intent ) => void ): void => { ensureBroadCastReceiverClass(); const registerFunc = (context: android.content.Context) => { const receiver: android.content.BroadcastReceiver = new androidBroadcastReceiverClass( onReceiveCallback ); context.registerReceiver( receiver, new android.content.IntentFilter(intentFilter) ); if (!androidRegisteredReceivers) { androidRegisteredReceivers = {}; } androidRegisteredReceivers[intentFilter] = receiver; }; if (global.androidCapacitorActivity) { registerFunc(global.androidCapacitorActivity); } }; export const androidBroadcastReceiverUnRegister = ( intentFilter: string ): void => { if (!androidRegisteredReceivers) { androidRegisteredReceivers = {}; } const receiver = androidRegisteredReceivers[intentFilter]; if (receiver) { global.androidCapacitorActivity.unregisterReceiver(receiver); androidRegisteredReceivers[intentFilter] = undefined; delete androidRegisteredReceivers[intentFilter]; } }; ``` --- --- url: 'https://capacitor.nativescript.org/native-custom.md' --- # native-custom.d.ts You can attach as many custom utilities to the "native" object as your project demands. When adding your own native helpers, you can expand this interface to provide strong type checking for extra utilities you add to your project. ```typescript declare module '@nativescript/capacitor' { export interface customNativeAPI extends nativeCustom {} } /** * Define your own strongly typed native helpers here. */ export interface nativeCustom { openNativeModalView: () => void; } ``` The example provided, `openNativeModalView`, can be found in the `nativeCustom` interface to give you guidance on how to add your own. --- --- url: 'https://capacitor.nativescript.org/custom-v-direct.md' --- ## When to create a custom helper vs. using "native" API's directly? The decision is solely up to you in all cases. The "native" object is quick access to your native platform so it's suitable and conveneient for a lot of quick native setters and/or getters. For more complex behavior, creating a custom helper may be cleaner. However everything you can do in a helper, you could also do directly via the "native" object. There's a lot of flexibility here. The one technical guidance we would give is that each call from the "native" object is a communication point between your native platform and Capacitor. Thus to keep the communication to a minimum, creating a native helper utility can reduce potentially multiple calls to just one method communication. --- --- url: 'https://capacitor.nativescript.org/event-communication.md' --- # Notify and listen to events When needing to communicate events back and forth between Ionic/Capacitor and your NativeScript utilities you can use: * `notifyEvent: (name: string, data?: any) => void`: Notify event name with optional data. * `onEvent: (name: string, callback: Function) => void`: Listen to event name with callback. * `removeEvent: (name: string, callback?: Function) => void`: Remove event listeners. For example using the [zip example](solution-145) you can use `notifyEvent` in your NativeScript utilities to emit events: * `src/nativescript/zip.ts` ```ts // ... import { notifyEvent } from "@nativescript/capacitor/bridge"; native.fileZip = function (options: ZipOptions) { // ... Zip.zip({ directory, archive, onProgress: (progress) => { notifyEvent('zipProgress', progress); }, }).then((filePath) => { notifyEvent('zipComplete', filePath); }); } }; ``` You can then listen to those events in your Ionic components: * `src/app/explore-container.component.ts`: ```ts import { native } from '@nativescript/capacitor' // ... export class ExploreContainerComponent implements OnInit { ngOnInit() { native.onEvent('zipComplete', (filepath) => { console.log('ionic zip complete:', filepath) }) native.onEvent('zipProgress', (progress) => { console.log('ionic zip progress:', progress) }) } } ``` --- --- url: 'https://capacitor.nativescript.org/ai.md' --- # AI & agents These docs are built for AI tools as much as for people. Three machine-friendly surfaces are published alongside the site: ## llms.txt * [`/llms.txt`](https://capacitor.nativescript.org/llms.txt) โ€” an index of every documentation page with titles and links, following the [llms.txt convention](https://llmstxt.org). * [`/llms-full.txt`](https://capacitor.nativescript.org/llms-full.txt) โ€” the entire documentation in a single markdown file, ideal for dropping into a model's context. ## Markdown twins Every page is also published as raw markdown at the same path with a `.md` suffix โ€” for example [`/installation.md`](https://capacitor.nativescript.org/installation.md). Point an agent at any docs URL and swap `.html` (or no extension) for `.md` to get clean markdown. ## MCP server A [Model Context Protocol](https://modelcontextprotocol.io) endpoint is available at: ``` https://capacitor.nativescript.org/mcp ``` It speaks the MCP Streamable HTTP transport and exposes three tools: | Tool | What it does | |---|---| | `search_docs` | Full-text search across the documentation, returns matching pages with snippets | | `get_page` | Fetch any docs page as raw markdown | | `get_sitemap` | The `llms.txt` index, for orientation | ### Connect from Claude Code ```bash claude mcp add nativescript-capacitor --transport http https://capacitor.nativescript.org/mcp ``` ### Connect from other MCP clients Use the Streamable HTTP transport with the URL above โ€” the server is stateless (no sessions, no SSE), so any spec-compliant client works. --- --- url: 'https://capacitor.nativescript.org/migration-guide-v5-v8.md' --- # Migrating v5 to v8 v8 is a ground-up rework of both platform integrations, and **nothing touches your native projects anymore**: * **iOS**: the NativeScript runtime arrives as a Swift Package ([NativeScript/ios-spm](https://github.com/NativeScript/ios-spm)) through Capacitor's own SPM plugin flow (no CocoaPods, no Podfile, no AppDelegate edits, no build phases, no custom linker). Metadata ships inside the runtime framework. * **Android**: the plugin is a self-contained Gradle module meaning no `Application` replacement, no manifest edits, no `build.gradle` grafting, no binaries copied into your repo, and **no Static Binding Generator** (the runtime generates bindings dynamically). `nscap build` fetches the runtime once and generates metadata automatically. ## 1. Remove the v5 integration From your v5 app, run v5's uninstaller **before** upgrading โ€” it restores the Xcode project changes v5 made: ```bash npx uninstall-nativescript npm uninstall @nativescript/capacitor ``` Keep your `src/nativescript` code โ€” it carries over. ## 2. Move your app to Capacitor 8 (SPM) Follow the official [Capacitor upgrade guide](https://capacitorjs.com/docs/updating/8-0). If your app still uses CocoaPods, migrate the iOS project to SPM first: ```bash npx cap spm-migration-assistant ``` (or re-scaffold the `ios` platform: remove it, then `npx cap add ios` on Capacitor 8 โ€” SPM is the default template.) ## 3. Install v8 ```bash npm install @nativescript/capacitor npx nscap init ``` `nscap init` scaffolds fresh files but **keeps anything that already exists** โ€” your existing `src/nativescript/index.ts` and `src/native-custom.d.ts` are left untouched. ## 4. Adapt your NativeScript code Most native TypeScript carries over unchanged. Things to check: * **Nested project files are gone.** Delete `src/nativescript/package.json`, `tsconfig.json`, and `references.d.ts` โ€” v8 bundles with esbuild directly, and there is no second `npm install`. If you imported from `@nativescript/core`, note that core is not part of the 8.0.0 bundle (planned for a later 8.x release). * **`@NativeClass` decorated classes**: the v8 bundler (esbuild) does not run the webpack NativeClass transformer. On iOS, replace decorated `class X extends NSObject` with the runtime's `NSObject.extend()` API: ```ts const MyDelegate = NSObject.extend( { someMethod(arg) { /* ... */ }, }, { name: 'MyDelegate', protocols: [/* e.g. UITextFieldDelegate */], }, ); ``` On Android, `SomeClass.extend({...})` and `new SomeInterface({...})` work at runtime via the runtime's dynamic binding generation โ€” no build step, no SBG. * **Deprecated iOS APIs**: v5-era examples used `presentModalViewControllerAnimated`, which no longer exists on modern iOS. Use: ```ts iosRootViewController().presentViewControllerAnimatedCompletion(vc, true, () => { // presented }); ``` * **Bridge helpers**: `iosRootViewController`, `iosAddNotificationObserver`, `iosRemoveNotificationObserver`, `runOnUIThread`, the event API (`notifyEvent` / `onEvent` / `removeEvent`), and `global.androidCapacitorActivity` all carry over as-is. The v5 convenience wrappers `androidCreateDialog` and `androidBroadcastReceiverRegister` are not in 8.0.0 โ€” use direct platform APIs instead (e.g. `android.app.AlertDialog.Builder`, as the scaffolded modal example shows). ## 5. Update your scripts Your web `build` script goes back to being just the web build. The v5 extras are replaced by the hook `nscap init` added: | v5 | v8 | |---|---| | `npm run build:mobile` | `npm run build` then `npx cap sync` (hook builds NS automatically) | | `build:nativescript` โ†’ `build-nativescript` | `build:nativescript` โ†’ `nscap build` | | `dev:nativescript` watcher | retired; `npx cap run ios` after edits (live reload on the 8.x roadmap) | | production webpack flags | not needed (esbuild); minify flag coming in a later 8.x | ## 6. Build and run ```bash npm run build npx cap sync npx cap run ios # and/or: npx cap run android ``` The first iOS build downloads the runtime via SPM, and the first `nscap build` with an Android platform fetches the Android runtime; after that you're on the normal fast path. --- --- url: 'https://capacitor.nativescript.org/migration-guide-v4-v5.md' --- ## Migrating from v4 to v5 @nativescript/capacitor follows Capacitor major version releases. For existing v4 users, you can update by: 1. Update `package.json`: ```ts "@nativescript/capacitor": "^5.0.0" ``` 2. Update `ios/App/Podfile` to reference the latest `NativeScriptSDK ~8.4.2` Pod and adjust the post\_install setup: ```ts require_relative '../../node_modules/@nativescript/capacitor/ios/nativescript.rb' require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers' platform :ios, '13.0' use_frameworks! # workaround to avoid Xcode caching of Pods that requires # Product -> Clean Build Folder after new Cordova plugins installed # Requires CocoaPods 1.6 or newer install! 'cocoapods', :disable_input_output_paths => true def capacitor_pods pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app' pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics' pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard' pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar' pod 'NativescriptCapacitor', :path => '../../node_modules/@nativescript/capacitor' end target 'App' do capacitor_pods # Add your Pods here pod 'NativeScriptSDK', '~> 8.4.2' pod 'NativeScriptUI', '~> 0.1.2' end post_install do |installer| assertDeploymentTarget(installer) nativescript_capacitor_post_install(installer) end ``` 3. Ensure latest pod specs are refreshed locally You can run `pod repo update` to ensure the latest references are available on your system. 4. Clean project dependencies and ensure lock (if using) is updated to reflect latest @nativescript/capacitor 5.0.0 is installed. 5. Ensure native projects are cleaned before running with updates. --- --- url: 'https://capacitor.nativescript.org/migration-guide-v2-v4.md' --- ## Migrating from v2 to v4 @nativescript/capacitor follows Capacitor major version releases now. For existing v2 users, you can update by: 1. Update `package.json`: ```ts "@nativescript/capacitor": "^4.0.0" ``` 2. Update `AppDelegate.swift`: From this: ```ts self.nativescript?.runMainScript() ``` to this: ```ts self.nativescript?.runMainApplication() ``` 3. Update `ios/App/Podfile` to reference the latest `~8.3.3` Pod and adjust the post\_install setup: ```ts require_relative '../../node_modules/@nativescript/capacitor/ios/nativescript.rb' require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers' platform :ios, '13.0' use_frameworks! # workaround to avoid Xcode caching of Pods that requires # Product -> Clean Build Folder after new Cordova plugins installed # Requires CocoaPods 1.6 or newer install! 'cocoapods', :disable_input_output_paths => true def capacitor_pods pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app' pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics' pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard' pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar' pod 'NativescriptCapacitor', :path => '../../node_modules/@nativescript/capacitor' end target 'App' do capacitor_pods # Add your Pods here pod 'NativeScript', '~> 8.3.3' pod 'NativeScriptUI' end post_install do |installer| assertDeploymentTarget(installer) nativescript_capacitor_post_install(installer) end ``` 4. Ensure latest pod specs are refreshed locally You can run `pod repo update` to ensure the latest references are available on your system. 5. Clean project dependencies and ensure lock (if using) is updated to reflect latest @nativescript/capacitor 4.0.0 is installed. 6. Ensure native projects are cleaned before running with updates. --- --- url: 'https://capacitor.nativescript.org/capacitor-proposals.md' --- # Solutions for the Capacitor Community Common solutions to Capacitor Community Proposals found here: https://github.com/capacitor-community/proposals ## How to Use Most solutions can be copy/pasted into your own `src/nativescript` files and wired into your app right away. We will try to provide explanations where needed and further instruction if required to use any posted solution. Feel free to [post your own request on the appropriate proposals](https://github.com/capacitor-community/proposals/issues) for NativeScript solutions you'd like to see here. ## Community Proposal Solutions * [Brightness - Proposal 77](solution-77.md) * [Power Mode - Proposal 79](solution-79.md) * [Zip - Proposal 145](solution-145.md) --- --- url: 'https://capacitor.nativescript.org/solution-77.md' --- # Screen Brightness https://github.com/capacitor-community/proposals/issues/77 ### `src/nativescript/index.ts`: ```typescript // Screen Brightness import "./brightness"; ``` ### `src/nativescript/brightness.ts`: ```typescript native.setScreenBrightness = (value: number) => { if (native.isAndroid) { const context = native.androidCapacitorActivity; if (android.os.Build.VERSION.SDK_INT < 23) { const attr = context.getWindow().getAttributes(); attr.screenBrightness = value; context.getWindow().setAttributes(attr); } else { if (!android.provider.Settings.System.canWrite(context)) { const intent = new android.content.Intent( android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS ); intent.setData( android.net.Uri.parse("package:" + context.getPackageName()) ); intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } if (android.provider.Settings.System.canWrite(context)) { android.provider.Settings.System.putInt( context.getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS, value * 100 ); } } } else { UIScreen.mainScreen.brightness = value; } }; ``` ## Usage in your Ionic web codebase: Provide strong type checking for this new helper by modifying the following: ### `src/native-custom.d.ts` ```typescript /** * Define your own strongly typed native helpers here. */ export interface nativeCustom { setScreenBrightness: (value: number) => void; } ``` Now you can use it anywhere in your Ionic web codebase with the following: ```typescript import { native } from '@nativescript/capacitor'; native.setScreenBrightness(.8); ``` --- --- url: 'https://capacitor.nativescript.org/solution-79.md' --- # Power Mode https://github.com/capacitor-community/proposals/issues/79 ### `src/nativescript/index.ts`: ```typescript // Power Mode import "./power-mode"; ``` ### `src/nativescript/power-mode.ts`: On Android this extends `BroadcastReceiver` directly from TypeScript โ€” in 8.x the runtime generates the binding dynamically, no build step required. *Validated on Android API 35 (emulator): toggling battery saver fires the callback.* ```typescript let isListening = false; let clientCallback: (isEnabled: boolean) => void; let observer; let receiver; native.togglePowerModeListener = (callback?: (isEnabled: boolean) => void) => { clientCallback = callback; if (native.isAndroid) { const context = native.androidCapacitorActivity; if (!isListening) { isListening = true; const Receiver = android.content.BroadcastReceiver.extend({ onReceive(ctx, intent) { const manager: android.os.PowerManager = ctx.getSystemService( android.content.Context.POWER_SERVICE ); if (manager && clientCallback) { clientCallback(manager.isPowerSaveMode()); } }, }); receiver = new Receiver(); context.registerReceiver( receiver, new android.content.IntentFilter("android.os.action.POWER_SAVE_MODE_CHANGED") ); } else { isListening = false; context.unregisterReceiver(receiver); receiver = null; } } else { if (!isListening) { isListening = true; observer = NSNotificationCenter.defaultCenter.addObserverForNameObjectQueueUsingBlock( NSProcessInfoPowerStateDidChangeNotification, null, null, (n: NSNotification) => { if (clientCallback) { clientCallback(NSProcessInfo.processInfo.lowPowerModeEnabled); } } ); } else { isListening = false; NSNotificationCenter.defaultCenter.removeObserver(observer); } } }; native.isInLowPowerMode = () => { if (native.isAndroid) { const manager: android.os.PowerManager = native.androidCapacitorActivity.getSystemService( android.content.Context.POWER_SERVICE ); if (manager) { return manager.isPowerSaveMode(); } } else { return NSProcessInfo.processInfo.lowPowerModeEnabled; } return false; }; ``` ## Usage in your Ionic web codebase: Provide strong type checking for this new helper by modifying the following: ### `src/native-custom.d.ts` ```typescript /** * Define your own strongly typed native helpers here. */ export interface nativeCustom { togglePowerModeListener: (callback?: (isEnabled: boolean) => void) => void; } ``` Now you can use it anywhere in your Ionic web codebase with the following: ```typescript import { native } from '@nativescript/capacitor'; native.togglePowerModeListener((isEnabled: boolean) => { console.log("Power Mode changed:", isEnabled); }); ``` --- --- url: 'https://capacitor.nativescript.org/solution-145.md' --- # Zip https://github.com/capacitor-community/proposals/issues/145 In 8.x this needs **no plugins, no pods, no custom metadata** โ€” both platforms can create zip archives with nothing but their standard APIs, which are already in the default metadata: * **iOS**: `NSFileCoordinator` with the `ForUploading` reading option hands you a zip of any folder (the same mechanism Files/Mail use for folder uploads). * **Android**: `java.util.zip.ZipOutputStream`, straight from the JDK. *Validated on iOS 26.5 (simulator) and Android API 35 (emulator).* ### `src/nativescript/index.ts`: ```typescript // Zip import "./zip"; ``` ### `src/nativescript/zip.ts`: ```typescript import { notifyEvent } from "@nativescript/capacitor/bridge"; import { ZipOptions } from "../native-custom"; function zipFolderIOS(directory: string, archive: string) { const fileManager = NSFileManager.defaultManager; const coordinator = NSFileCoordinator.alloc().init(); const errorRef = new interop.Reference(); coordinator.coordinateReadingItemAtURLOptionsErrorByAccessor( NSURL.fileURLWithPath(directory), NSFileCoordinatorReadingOptions.ForUploading, errorRef, (zipUrl) => { if (fileManager.fileExistsAtPath(archive)) { fileManager.removeItemAtPathError(archive); } fileManager.copyItemAtPathToPathError(zipUrl.path, archive); notifyEvent("zipComplete", archive); } ); } function zipFolderAndroid(directory: string, archive: string) { const dir = new java.io.File(directory); const outFile = new java.io.File(archive); const zos = new java.util.zip.ZipOutputStream( new java.io.FileOutputStream(outFile) ); const files = dir.listFiles(); for (let i = 0; i < files.length; i++) { zos.putNextEntry(new java.util.zip.ZipEntry(files[i].getName())); const fis = new java.io.FileInputStream(files[i]); const buffer = Array.create("byte", 4096); let len: number; while ((len = fis.read(buffer)) > 0) { zos.write(buffer, 0, len); } fis.close(); zos.closeEntry(); } zos.close(); notifyEvent("zipComplete", archive); } native.fileZip = (options: ZipOptions) => { if (native.isAndroid) { const context = native.androidCapacitorActivity; const directory = new java.io.File(context.getFilesDir(), options.directory) .getAbsolutePath(); const archive = new java.io.File(context.getCacheDir(), options.archive) .getAbsolutePath(); zipFolderAndroid(directory, archive); } else { const documents = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true ).firstObject; zipFolderIOS( documents + "/" + options.directory, NSTemporaryDirectory() + options.archive ); } }; ``` :::tip Unzipping? Android can also *extract* with the same zero-dependency approach (`java.util.zip.ZipInputStream`). iOS has no public unzip API โ€” that use case lands with NativeScript plugin support in a later 8.x release. ::: ## Usage in your Ionic web codebase: Provide strong type checking for this new helper by modifying the following: ### `src/native-custom.d.ts` ```typescript /** * Define your own strongly typed native helpers here. */ export interface ZipOptions { directory: string; archive: string; } export interface nativeCustom { fileZip(options: ZipOptions): void; } ``` Now you can use it anywhere in your Ionic web codebase with the following: ```typescript import { native } from "@nativescript/capacitor"; native.onEvent("zipComplete", (filepath) => { console.log("zip complete:", filepath); }); native.fileZip({ directory: "assets", archive: "assets.zip" }); ``` --- --- url: 'https://capacitor.nativescript.org/uninstall.md' --- # Doesn't fit your needs? Removing NativeScript from a Capacitor 8.x app is just removing files โ€” nothing was ever changed in your Xcode project, so there is nothing to restore: 1. `npm uninstall @nativescript/capacitor` 2. Delete `src/nativescript/` and `src/native-custom.d.ts` (or archive them for later) 3. Remove the `build:nativescript` and `capacitor:copy:before` scripts from your `package.json` 4. `npx cap sync` :::tip v5 note The v5 `uninstall-nativescript.js` restore script is no longer needed โ€” it existed to undo v5's Xcode project modifications, and 8.x never makes any. ::: If anything doesn't clean up as expected, let us know [here](https://github.com/NativeScript/capacitor/issues). --- --- url: 'https://capacitor.nativescript.org/README.md' --- # @nativescript/capacitor Docs https://capacitor.nativescript.org/ To develop locally: ``` npm install npm start ``` To build locally: ``` npm run build ``` Preview the site: ``` http-server .vitepress/dist/ ``` --- --- url: 'https://capacitor.nativescript.org/swag-contest.md' --- # Did someone say "Free Swag"? **Yes!** For the first 10 developers whom create helpful solutions to any of the [listed Capacitor Community proposals here](https://github.com/capacitor-community/proposals/issues) with this plugin, we will send you: * 2 sweet bottle openers * 2 sweet socks *annnndddd...* * 2 stress balls! Once you create a solution, just post a comment on that proposal linking to your solution and send an email to support@nativescript.org with a link to your comment and mailing address and we will send you a sweet goodie bag! ![NativeScript Swag](/assets/images/ns-swag.png "NativeScript Swag") --- --- url: 'https://capacitor.nativescript.org/index.md' --- ```ts import { iosRootViewController } from '@nativescript/capacitor/bridge'; native.openNativeModalView = () => { if (native.isAndroid) { const builder = new android.app.AlertDialog.Builder( native.androidCapacitorActivity ); builder.setTitle('Hello from NativeScript ๐Ÿš€'); builder.setPositiveButton('Close', null); builder.show(); return; } const vc = UIViewController.alloc().init(); vc.view.backgroundColor = UIColor.systemIndigoColor; iosRootViewController().presentViewControllerAnimatedCompletion( vc, true, () => console.log('presented!') ); }; ``` ```ts import { native } from '@nativescript/capacitor'; export class ExploreContainerComponent { async showNative() { // any platform API, directly from your web code const version = native.isAndroid ? await native.android.os.Build.MODEL.get : await native.UIDevice.currentDevice.systemVersion.get; console.log('running on', version); // or your own native helpers, written in TypeScript native.openNativeModalView(); } } ```