πŸš€ 8.9 Released! β†’ ⚑️ New Node-API Engine Preview, πŸ“² ns widget ios, πŸ’… Tailwind v4 and more...
Read Announcement

Gain critical insights into app issues by integrating Sentry, an advanced error tracking and performance monitoring service. Follow this guide to set up Sentry in your NativeScript application using the @nativescript-community/sentry plugin.


Step 1: Create a Sentry Account and Project ​

First, sign up for a Sentry account and create a new project specifically for your NativeScript app.

Step 2: Install the Plugin ​

Install the Sentry SDK into your NativeScript project:

bash
npminstall@nativescript-community/sentry

Step 3: Set Up Environment Variables ​

Create a .env file at the root of your project with these Sentry-related variables:

  • SENTRY_ORG_SLUG: Found under your project's Organization Settings.
  • SENTRY_PROJECT_SLUG_IOS & SENTRY_PROJECT_SLUG_ANDROID: Identifiers for your iOS and Android projects.
  • SENTRY_DSN_IOS & SENTRY_DSN_ANDROID: Obtain from [Project] > Settings > Client Keys (DSN).
  • SENTRY_AUTH_TOKEN: Generate via your Sentry account under User Auth Tokens.

Example .env:

bash
SENTRY_ORG_SLUG=nativescript
SENTRY_PROJECT_SLUG_IOS=myapp-ios
SENTRY_PROJECT_SLUG_ANDROID=myapp-android
SENTRY_DSN_IOS=your-ios-dsn
SENTRY_DSN_ANDROID=your-android-dsn
SENTRY_AUTH_TOKEN=your-auth-token

Replace the above placeholders with your actual Sentry details.

TIP

If you are also doing XR development with Vision Pro or Meta Quest, you may want to additionally setup SENTRY_PROJECT_SLUG_VISIONOS, SENTRY_DSN_VISIONOS, SENTRY_PROJECT_SLUG_QUEST, and SENTRY_DSN_QUEST. The bundling config below demonstrates using a visionOS version string as an example. Meta Quest would use the standard Android versioning location of app.gradle.

Step 4: Configure Webpack ​

We will use Webpack to manage environment variables and source maps with plugins:

bash
npminstallplist@sentry/webpack-plugindotenv-D

Update your webpack.config.js to configure Sentry integration:

js
constwebpack=require('@nativescript/webpack')
const { resolve, join, relative } =require('path')
const { readFileSync } =require('fs')
const { parse } =require('plist')
// load .env without having to specify cli env flags
require('dotenv').config()

constSentryCliPlugin=require('@sentry/webpack-plugin').sentryWebpackPlugin
constSourceMapDevToolPlugin=require('webpack').SourceMapDevToolPlugin

constSENTRY_PREFIX= process.env.SENTRY_PREFIX||'app:///'
constSENTRY_SOURCE_MAP_PATH=join(__dirname, 'dist', 'sourcemaps')

module.exports= (env) => {
 webpack.init(env)

 webpack.chainWebpack((config) => {
constisStoreBuild=!!env.production
constsentryDev=!isStoreBuild &&!!env['sentryDev']

constplatform= webpack.Utils.platform.getPlatformName()
constprojectSlug=
 platform ==='android'
? process.env.SENTRY_PROJECT_SLUG_ANDROID
: process.env.SENTRY_PROJECT_SLUG_IOS
constversionString=
 platform ==='android'
?readFileSync(
resolve(__dirname, 'App_Resources/Android/app.gradle'),
'utf8',
 ).match(/versionName\s+"([^"]+)"/)[1]
:parse(
readFileSync(
resolve(
 __dirname,
`App_Resources/${platform==='visionos'?'visionOS':'iOS'}/Info.plist`,
 ),
'utf8',
 ),
 )['CFBundleShortVersionString']

constSENTRY_DIST= sentryDev ?`dev-${Date.now()}`:`${Date.now()}`
constSENTRY_RELEASE= sentryDev ?SENTRY_DIST: versionString

 config.plugin('DefinePlugin').tap((args) => {
 Object.assign(args[0], {
 __SENTRY_DIST__: `'${SENTRY_DIST}'`,
 __SENTRY_RELEASE__: `'${SENTRY_RELEASE}'`,
 __SENTRY_ENVIRONMENT__: `'${
isStoreBuild?'production':'development'
}'`,
 __ENABLE_SENTRY__: isStoreBuild || sentryDev,
 __SENTRY_PREFIX__: `'${SENTRY_PREFIX}'`,
 __SENTRY_DSN_IOS__: JSON.stringify(process.env.SENTRY_DSN_IOS),
 __SENTRY_DSN_ANDROID__: JSON.stringify(process.env.SENTRY_DSN_ANDROID),
 })
return args
 })

if (isStoreBuild || sentryDev) {
 config.devtool(false)

 config
 .plugin('SourceMapDevToolPlugin|sentry')
 .use(SourceMapDevToolPlugin, [
 {
 append: `\n//# sourceMappingURL=${SENTRY_PREFIX}[name].js.map`,
 filename: relative(
 webpack.Utils.platform.getAbsoluteDistPath(),
join(SENTRY_SOURCE_MAP_PATH, '[name].js.map'),
 ),
 },
 ])

 config
 .plugin('SentryCliPlugin')
 .init(() =>
SentryCliPlugin({
 org: process.env.SENTRY_ORG_SLUG,
 project: projectSlug,
// force ignore non-legacy sourcemaps
 sourcemaps: {
 assets: '/dev/null',
 },
 release: {
 uploadLegacySourcemaps: {
 paths: [
join(__dirname, 'dist', 'sourcemaps'),
 webpack.Utils.platform.getAbsoluteDistPath(),
 ],
 urlPrefix: SENTRY_PREFIX,
 },
 dist: SENTRY_DIST,
 cleanArtifacts: true,
 deploy: {
 env: sentryDev ?'development':'production',
 },
 setCommits: {
 auto: true,
 ignoreMissing: true,
 },
...(SENTRY_RELEASE? { name: SENTRY_RELEASE } : {}),
 },
 authToken: process.env.SENTRY_AUTH_TOKEN,
 }),
 )
 .use(SentryCliPlugin)

 config.optimization.minimizer('TerserPlugin').tap((args) => {
// we format here otherwise the sourcemaps will be broken
 args[0].terserOptions.format = {
...args[0].terserOptions.format,
 max_line_len: 1000,
 indent_level: 1,
 }
return args
 })
 }
 })

return webpack.resolveConfig()
}

Step 5: Initialize Sentry in Your App ​

Create sentry.ts to initialize Sentry:

ts
import { Application, Trace, TraceErrorHandler } from'@nativescript/core'
import*as Sentry from'@nativescript-community/sentry'

declareconst__SENTRY_DIST__:string
declareconst__SENTRY_RELEASE__:string
declareconst__SENTRY_ENVIRONMENT__:string
declareconst__ENABLE_SENTRY__:boolean
declareconst__SENTRY_PREFIX__:string
declareconst__SENTRY_DSN_IOS__:string
declareconst__SENTRY_DSN_ANDROID__:string

let initialized =false
exportfunctioninitSentry() {
if (initialized ||!__ENABLE_SENTRY__) return
 initialized =true

 Sentry.init({
 dsn: __APPLE__ ? __SENTRY_DSN_IOS__ : __SENTRY_DSN_ANDROID__,
 debug: __DEV__,
 enableAppHangTracking: false,
 enableNativeCrashHandling: true,
 enableAutoPerformanceTracking: true,
 enableAutoSessionTracking: true,
 attachScreenshot: false,
 dist: __SENTRY_DIST__,
 release: __SENTRY_RELEASE__,
 environment: __SENTRY_ENVIRONMENT__,
 appPrefix: __SENTRY_PREFIX__,
 appHangsTimeoutInterval: 5,
 })

 Application.on('uncaughtError', (event) =>
 Sentry.captureException(event.error),
 )
 Application.on('discardedError', (event) =>
 Sentry.captureException(event.error),
 )
 Trace.setErrorHandler(errorHandler)
}

consterrorHandler:TraceErrorHandler= {
handlerError(error:Error) {
if (__DEV__) {
// (development) - log it
 console.error(error)
// (development) - or use Trace writing (categorical logging)
 Trace.write(error, Trace.categories.Error)
// (development) - throw it
throw error
 }

// (production) - send it to sentry
 Sentry.captureException(error)
 },
}

In your main bootstrap file (app.ts or main.ts), initialize on launch:

ts
import { initSentry } from'./sentry'

Application.on('launch', () => {
initSentry()
})

Step 6: Test Your Setup ​

Trigger a test crash to verify setup:

ts
thrownewError('Sentry test crash')

For native crashes:

  • iOS:
ts
NSString.stringWithString(null)
  • Android:
ts
new java.lang.String(null)

Your crashes should appear in your Sentry dashboard shortly after triggering.

Sentry Crash Reporting with iOSSentry Crash Reporting with iOS for Native StacktracesSentry Crash Reporting with Android

You're now successfully integrated with Sentry, gaining powerful insights into your app's performance and stability.

Example Repo ​

You can compare your setup against this example repo which follows this documentation.

https://github.com/NativeScript/example-sentry

Config Considerations ​

Within nativescript.config.ts, be mindful of the following.

ts
exportdefault {
 discardUncaughtJsExceptions: true,
 android: {
 v8Flags: '--expose_gc',
 markingMode: 'none',
 codeCache: true,
 suppressCallJSMethodExceptions: true,
 },
}

The options to discardUncaughtJsExceptions and suppressCallJSMethodExceptions will suppress JavaScript exceptions like those using throw which can prevent them from showing up in Sentry so just keep that in mind.

Flavor Notes ​

Various flavors may need some extra considerations.

Angular ​

You can setup a custom error handler.

Define a SentryErrorHandler within sentry.ts.

ts
import { ErrorHandler } from'@angular/core'

exportclassSentryErrorHandlerimplementsErrorHandler {
handleError(error:unknown) {
if (__ENABLE_SENTRY__ && initialized) {
 Sentry.captureException(error)
 }
 }
}

You can use it within your custom Angular error handler.

ts
import { ErrorHandler, Injectable } from'@angular/core'
import { SentryErrorHandler } from'./sentry'

@Injectable()
exportclassGlobalErrorHandlerextendsErrorHandler {
sentryErrorHandler=newSentryErrorHandler()

handleError(error:Error) {
 console.error('GlobalErrorHandler', error)
 console.error(error.stack)
this.sentryErrorHandler.handleError(error)
 }
}

Then in your app.component.ts or app.module.ts, depending on if using standalone, use the provider.

ts
providers: [
 {
 provide: ErrorHandler,
 useClass: GlobalErrorHandler,
 },
]
Previous
Styling

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /