Skip to content

Instantly share code, notes, and snippets.

@wcastand
Created August 27, 2025 13:06
Show Gist options
  • Select an option

  • Save wcastand/71623f28a185d1b1066380047b3973ac to your computer and use it in GitHub Desktop.

Select an option

Save wcastand/71623f28a185d1b1066380047b3973ac to your computer and use it in GitHub Desktop.

add the plugin to your app config

// app.config.ts
[...]
plugins: {
		"./plugins/build/datadog-native-initialization.js",
		["expo-datadog"],
}
[...]

I do also run "eas-build-post-install": "bun run generate:datadog-config"

Main reason is we need the datadog config also in the expo-updates and to bundle the JS so we can't do that in the expo plugin at prebuild since the JS bundle is done before that in build steps on EAS.

In theory with this, datadog init before React-native. Hope it helps

import { join, relative } from "node:path"
import {
type ConfigPlugin,
createRunOncePlugin,
IOSConfig,
withAppBuildGradle,
withAppDelegate,
withMainApplication,
withXcodeProject,
} from "@expo/config-plugins"
import { addImports, findNewInstanceCodeBlock } from "@expo/config-plugins/build/android/codeMod"
import { insertContentsInsideSwiftFunctionBlock } from "@expo/config-plugins/build/ios/codeMod"
import { insertContentsAtOffset } from "@expo/config-plugins/build/utils/commonCodeMod"
import { mergeContents } from "@expo/config-plugins/build/utils/generateCode"
type DatadogConfig = {
clientToken: string
env: string
applicationId: string
nativeCrashReportEnabled: boolean
nativeLongTaskThresholdMs?: number
longTaskThresholdMs?: number
sessionSamplingRate?: number
site: "US1" | "US1_FED" | "US3" | "US5" | "EU1" | "AP1"
trackingConsent?: "PENDING" | "GRANTED" | "NOT_GRANTED"
telemetrySampleRate?: number
vitalsUpdateFrequency?: "NEVER" | "RARE" | "AVERAGE" | "FREQUENT"
uploadFrequency?: "RARE" | "AVERAGE" | "FREQUENT"
batchSize?: "SMALL" | "MEDIUM" | "LARGE"
trackFrustrations?: boolean
trackBackgroundEvents?: boolean
customEndpoints?: {
rum?: string
logs?: string
trace?: string
}
nativeViewTracking?: boolean
nativeInteractionTracking?: boolean
verbosity?: "DEBUG" | "INFO" | "WARN" | "ERROR"
proxy?: {
type: "HTTP" | "HTTPS" | "SOCKS"
address: string
port: number
username?: string
password?: string
}
serviceName?: string
version?: string
firstPartyHosts?: Array<{
match: string
propagatorTypes?: Array<"DATADOG" | "B3" | "B3MULTI" | "TRACECONTEXT">
}>
trackInteractions: boolean
trackResources: boolean
trackErrors: boolean
actionNameAttribute?: string
useAccessibilityLabel?: boolean
resourceTracingSamplingRate?: number
bundleLogsWithRum?: boolean
bundleLogsWithTraces?: boolean
appHangThreshold?: number
trackNonFatalAnrs?: boolean
initialResourceThreshold?: number
}
/**
* Adds Datadog native initialization to Android platform
* This ensures Datadog SDK is initialized before React Native starts
*/
const withAndroidDatadogInit: ConfigPlugin = (config) => {
return withMainApplication(config, async (config) => {
// Add the import for Datadog native initialization
config.modResults.contents = addImports(
config.modResults.contents,
["com.datadog.reactnative.DdSdkNativeInitialization"],
false,
)
// Add the initialization call in onCreate() after super.onCreate()
const initCall = "DdSdkNativeInitialization.initFromNative(this.applicationContext)\n"
const res = findNewInstanceCodeBlock(config.modResults.contents, "onCreate", "kt")
if (!config.modResults.contents.includes(initCall) && res) {
const superOnCreateCall = "super.onCreate()"
const index = res.code.indexOf(superOnCreateCall)
if (index !== -1) {
config.modResults.contents = insertContentsAtOffset(
config.modResults.contents,
initCall,
res.start + index + superOnCreateCall.length + 1,
)
}
}
return config
})
}
/**
* Adds Datadog native initialization to iOS platform
* This ensures Datadog SDK is initialized before React Native starts
*/
const withIOSDatadogInit: ConfigPlugin = (config) => {
return withAppDelegate(config, (config) => {
let contents = config.modResults.contents
// Add the import for DatadogSDKReactNative
contents = mergeContents({
anchor: /import Expo/,
comment: "//",
newSrc: "import DatadogSDKReactNative",
offset: 1,
src: contents,
tag: "ios-datadog-import",
}).contents
// Add the initialization call at the beginning of didFinishLaunchingWithOptions
contents = insertContentsInsideSwiftFunctionBlock(
contents,
"application(_:didFinishLaunchingWithOptions:)",
"DdSdk.initFromNative()",
{ position: "head" },
)
config.modResults.contents = contents
return config
})
}
/**
* Configures Android gradle to include Datadog configuration
*/
const withAndroidDatadogGradle: ConfigPlugin = (config) => {
return withAppBuildGradle(config, (config) => {
let contents = config.modResults.contents
// Add the datadog configuration gradle script
contents = mergeContents({
anchor: /apply plugin: "com\.facebook\.react"/,
comment: "//",
newSrc: `apply from: "${config.modRequest.projectRoot}/node_modules/@datadog/mobile-react-native/datadog-configuration.gradle"`,
offset: 1,
src: contents,
tag: "datadog-configuration-gradle",
}).contents
config.modResults.contents = contents
return config
})
}
/**
* Configures iOS to include the Datadog configuration file as a resource
*/
const withIOSDatadogConfig: ConfigPlugin = (config) => {
return withXcodeProject(config, (config) => {
const projectRoot = config.modRequest.projectRoot
const configFileName = "datadog-configuration.json"
const configFilePath = join(projectRoot, configFileName)
// Add the configuration file as a resource to the iOS project
try {
const relativePath = relative(config.modRequest.platformProjectRoot, configFilePath)
IOSConfig.XcodeUtils.addResourceFileToGroup({
filepath: relativePath,
groupName: "Resources",
isBuildFile: true,
project: config.modResults,
targetUuid: config.modResults.getFirstTarget().uuid,
verbose: true,
})
} catch (error) {
console.warn("Warning: Could not add datadog-configuration.json to iOS resources:", error)
}
return config
})
}
/**
* Main plugin that combines all Datadog native initialization features
*/
const withDatadogNativeInit: ConfigPlugin<DatadogConfig> = (c) => {
let config = c
// Configure Android
config = withAndroidDatadogGradle(config)
config = withAndroidDatadogInit(config)
// Configure iOS
config = withIOSDatadogConfig(config)
config = withIOSDatadogInit(config)
return config
}
export default createRunOncePlugin(withDatadogNativeInit, "DatadogNativeInitialization", "2.0.0")
import { writeFileSync } from "node:fs"
import { join } from "node:path"
import { Env } from "src/config/env"
const configFileName = "datadog-configuration.json"
const configFilePath = join(process.cwd(), configFileName)
if (!Env.DATADOG_CLIENT_TOKEN || !Env.DATADOG_RUM_APPLICATION_ID) {
console.error("Datadog client token or RUM application ID is not set in environment variables.")
process.exit(1)
}
const configContent: { configuration: DatadogConfig; $schema: string } = {
$schema: "./node_modules/@datadog/mobile-react-native/datadog-configuration.schema.json",
configuration: {
applicationId: Env.DATADOG_RUM_APPLICATION_ID,
batchSize: Env.IS_DEV ? "SMALL" : "MEDIUM",
clientToken: Env.DATADOG_CLIENT_TOKEN,
env: process.env.EXPO_PUBLIC_APP_VARIANT ?? "development",
longTaskThresholdMs: 1000,
nativeCrashReportEnabled: true,
sessionSamplingRate: 100,
site: "EU1",
telemetrySampleRate: 20,
trackBackgroundEvents: false,
trackErrors: true,
trackInteractions: false,
// Ensure required fields are present with sensible defaults
trackingConsent: "GRANTED",
trackResources: false,
uploadFrequency: Env.IS_DEV ? "FREQUENT" : "AVERAGE",
verbosity: "WARN",
},
}
console.log("Creating Datadog configuration file at:", configFilePath)
writeFileSync(configFilePath, JSON.stringify(configContent, null, 2))
console.log("Datadog configuration file created successfully.")
type DatadogConfig = {
clientToken: string
env: string
applicationId: string
nativeCrashReportEnabled: boolean
nativeLongTaskThresholdMs?: number
longTaskThresholdMs?: number
sessionSamplingRate?: number
site: "US1" | "US1_FED" | "US3" | "US5" | "EU1" | "AP1"
trackingConsent?: "PENDING" | "GRANTED" | "NOT_GRANTED"
telemetrySampleRate?: number
vitalsUpdateFrequency?: "NEVER" | "RARE" | "AVERAGE" | "FREQUENT"
uploadFrequency?: "RARE" | "AVERAGE" | "FREQUENT"
batchSize?: "SMALL" | "MEDIUM" | "LARGE"
trackFrustrations?: boolean
trackBackgroundEvents?: boolean
customEndpoints?: {
rum?: string
logs?: string
trace?: string
}
nativeViewTracking?: boolean
nativeInteractionTracking?: boolean
verbosity?: "DEBUG" | "INFO" | "WARN" | "ERROR"
proxy?: {
type: "HTTP" | "HTTPS" | "SOCKS"
address: string
port: number
username?: string
password?: string
}
serviceName?: string
version?: string
firstPartyHosts?: Array<{
match: string
propagatorTypes?: Array<"DATADOG" | "B3" | "B3MULTI" | "TRACECONTEXT">
}>
trackInteractions: boolean
trackResources: boolean
trackErrors: boolean
actionNameAttribute?: string
useAccessibilityLabel?: boolean
resourceTracingSamplingRate?: number
bundleLogsWithRum?: boolean
bundleLogsWithTraces?: boolean
appHangThreshold?: number
trackNonFatalAnrs?: boolean
initialResourceThreshold?: number
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment