If you’ve ever shipped a NativeScript app and realized your console is still flooded with debug logs in production — this one’s for you.
NativeScript provides a handy global magic variable called __DEV__ that lets you conditionally run code only during development. In this article, I’ll show you how I used it to add verbose API logging that automatically disappears in production builds.
The Problem
While debugging an API integration in my NativeScript app, I needed to see the full request URL, parameters, and response body. So I added console.log everywhere:
const callApi = (endpoint: string, params: any) => {
console.log(`POST ${endpoint}`)
 console.log(`params: ${JSON.stringify(params)}`)

 return fetch(endpoint, {
 method: 'POST',
 headers: {
 'Content-Type': 'application/json',
 'Accept': 'application/json',
 },
 body: JSON.stringify(params),
 }).then(async (response) => {
 const text = await response.text()
 console.log(`Response: ${response.status} ${text}`)
 return JSON.parse(text)
 })
}This works great for debugging. But I definitely don’t want all of this logging in a production build — it’s noisy, can leak sensitive data, and adds unnecessary overhead.
I could manually comment/uncomment these lines every time I switch between debug and release… but there’s a much better way.
The Solution: __DEV__
NativeScript (via its Vite configuration) exposes several global magic variables that are resolved at build time:
| Variable | Description |
|---|---|
__DEV__ | true in development mode, false in production |
__ANDROID__ | true when building for Android |
__IOS__ | true when building for iOS |
__VISIONOS__ | true when building for visionOS |
The key insight is that __DEV__ is a compile-time constant, not a runtime check. This means:
ns debug android→__DEV__istruens run android→__DEV__isfalse- Release/production builds →
__DEV__isfalse
Even better, because it’s resolved at build time, Vite’s tree-shaking will completely remove the dead code branches from your production bundle.
Using __DEV__ in Practice
Here’s how I refactored my API utility:
const callApi = (endpoint: string, params: any) => {
if (__DEV__) {
 console.log(`[API] >>> POST ${endpoint}`)
 console.log(`[API] >>> params: ${JSON.stringify(params)}`)
 }

 return fetch(endpoint, {
 method: 'POST',
 headers: {
 'Content-Type': 'application/json',
 'Accept': 'application/json',
 },
 body: JSON.stringify(params),
 }).then(async (response) => {
 // In production, parse JSON directly (faster, no extra memory allocation)
 if (!__DEV__) return response.json()

 // In development, read raw text first for debugging
 const text = await response.text()
 console.log(`[API] <<< ${response.status} ${text.substring(0, 500)}`)
 return JSON.parse(text)
 })
}Now when I run ns debug android, I see detailed logs:
[API] >>> POST https://api.example.com/health_check
[API] >>> params: {"device_id":"abc123","secret":"xyz789"}
[API] <<< 200 {"status":"ok"}And when I run ns run android or build for release — zero noise. The logging code doesn’t even exist in the bundle.
TypeScript Setup
If you’re using TypeScript (and you should be), you might see a squiggly line under __DEV__ because the compiler doesn’t know about it. Fix this by adding a type declaration.
Create or update your references.d.ts (or any .d.ts file included in your project):
declare const __DEV__: boolean
declare const __ANDROID__: boolean
declare const __IOS__: boolean
declare const __VISIONOS__: booleanMore Use Cases
The __DEV__ flag isn’t just for logging. Here are some other patterns I’ve found useful:
Mock Data in Development
export function getConfig() {
if (__DEV__) {
 return {
 apiUrl: 'https://staging.example.com/api',
 timeout: 30000, // longer timeout for debugging
 }
 }
 return {
 apiUrl: 'https://api.example.com',
 timeout: 10000,
 }
}Developer Tools / Debug UI
// In your main app component
if (__DEV__) {
 // Show a debug overlay with device info, network status, etc.
 registerDebugOverlay()
}Performance Timing
export async function fetchData() {
let start: number
 if (__DEV__) {
 start = Date.now()
 }

 const result = await api.getData()

 if (__DEV__) {
 console.log(`fetchData took ${Date.now() - start!}ms`)
 }

 return result
}Skipping Expensive Operations During Development
if (__DEV__) {
// Skip actual SMS sending during development
 console.log(`[DEV] Would send SMS to ${phoneNumber}: ${message}`)
 return { success: true }
}

return sendRealSms(phoneNumber, message)How It Works Under the Hood
NativeScript uses Vite as its build tool. In the NativeScript Vite plugin, __DEV__ is defined using Vite’s define option, which performs a global string replacement at build time.
When you run ns debug, the build essentially does:
// Before (source code)
if (__DEV__) {
 console.log('debug info')
}

// After build (development)
if (true) {
 console.log('debug info')
}And for production:
// After build (production) — before minification
if (false) {
 console.log('debug info')
}

// After tree-shaking/minification — completely removed!The dead code is eliminated entirely, so there’s zero runtime cost in production.
Key Takeaways
- Use
__DEV__instead of manual flags or environment variables for debug/release branching - It’s a compile-time constant — dead code gets tree-shaken from production builds
- Add TypeScript declarations to avoid type errors
- Great for: debug logging, mock data, dev tools, performance timing
ns debug=__DEV__istrue,ns run/ release =__DEV__isfalse
Stop littering your codebase with // TODO: remove before release comments. Let the build tool do the work for you.

