diff --git a/.eslintignore b/.eslintignore
index 26ecb1ae7cc7..aa10a3073f4e 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -14,3 +14,4 @@ web/gtm.js
 src/libs/SearchParser/searchParser.js
 src/libs/SearchParser/autocompleteParser.js
 help/_scripts/**
+modules/**
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
index 6d6406551cdd..bef985265d7f 100644
--- a/android/app/proguard-rules.pro
+++ b/android/app/proguard-rules.pro
@@ -10,6 +10,7 @@
 # Add any project specific keep options here:
 -keep class com.expensify.chat.BuildConfig { *; }
 -keep class com.facebook.** { *; }
+-keep class com.margelo.nitro.** { *; }
 -keep, allowoptimization, allowobfuscation class expo.modules.** { *; }
 
 # Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 142d919a7a18..a859703ae719 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -5,6 +5,7 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 27de0846d9d3..ab1973f510bc 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -50,6 +50,8 @@
 	</dict>
 	<key>ITSAppUsesNonExemptEncryption</key>
 	<false/>
+	<key>NSContactsUsageDescription</key>
+	<string>Import contacts from your phone so your favorite people are always a tap away.</string>
 	<key>LSApplicationQueriesSchemes</key>
 	<array>
 		<string>venmo</string>
diff --git a/ios/Podfile b/ios/Podfile
index 41dc5179752d..bdad8a0ec396 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -24,6 +24,7 @@ prepare_react_native_project!
 
 setup_permissions([
   'Camera',
+  'Contacts',
   'LocationAccuracy',
   'LocationAlways',
   'LocationWhenInUse'
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 31ff58598c82..dcb6a4ece1d0 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -28,6 +28,28 @@ PODS:
     - AppAuth/Core
   - AppLogs (0.1.0)
   - boost (1.84.0)
+  - ContactsModule (0.0.1):
+    - DoubleConversion
+    - glog
+    - hermes-engine
+    - NitroModules
+    - RCT-Folly (= 2024.01.01.00)
+    - RCTRequired
+    - RCTTypeSafety
+    - React-Core
+    - React-debug
+    - React-Fabric
+    - React-featureflags
+    - React-graphics
+    - React-ImageManager
+    - React-NativeModulesApple
+    - React-RCTFabric
+    - React-rendererdebug
+    - React-utils
+    - ReactCodegen
+    - ReactCommon/turbomodule/bridging
+    - ReactCommon/turbomodule/core
+    - Yoga
   - DoubleConversion (1.1.6)
   - EXAV (14.0.7):
     - ExpoModulesCore
@@ -287,6 +309,29 @@ PODS:
     - nanopb/encode (= 2.30908.0)
   - nanopb/decode (2.30908.0)
   - nanopb/encode (2.30908.0)
+  - NitroModules (0.18.1):
+    - DoubleConversion
+    - glog
+    - hermes-engine
+    - RCT-Folly (= 2024.01.01.00)
+    - RCTRequired
+    - RCTTypeSafety
+    - React-callinvoker
+    - React-Core
+    - React-debug
+    - React-Fabric
+    - React-featureflags
+    - React-graphics
+    - React-ImageManager
+    - React-jsi
+    - React-NativeModulesApple
+    - React-RCTFabric
+    - React-rendererdebug
+    - React-utils
+    - ReactCodegen
+    - ReactCommon/turbomodule/bridging
+    - ReactCommon/turbomodule/core
+    - Yoga
   - Onfido (29.7.2)
   - onfido-react-native-sdk (10.6.0):
     - DoubleConversion
@@ -2750,6 +2795,7 @@ DEPENDENCIES:
   - AirshipServiceExtension
   - AppLogs (from `../node_modules/react-native-app-logs/AppLogsPod`)
   - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
+  - ContactsModule (from `../modules/ContactsNitroModule`)
   - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
   - EXAV (from `../node_modules/expo-av/ios`)
   - EXImageLoader (from `../node_modules/expo-image-loader/ios`)
@@ -2765,6 +2811,7 @@ DEPENDENCIES:
   - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
   - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
   - lottie-react-native (from `../node_modules/lottie-react-native`)
+  - NitroModules (from `../node_modules/react-native-nitro-modules`)
   - "onfido-react-native-sdk (from `../node_modules/@onfido/react-native-sdk`)"
   - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
   - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
@@ -2915,6 +2962,8 @@ EXTERNAL SOURCES:
     :path: "../node_modules/react-native-app-logs/AppLogsPod"
   boost:
     :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec"
+  ContactsModule:
+    :path: "../modules/ContactsNitroModule"
   DoubleConversion:
     :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
   EXAV:
@@ -2946,6 +2995,8 @@ EXTERNAL SOURCES:
     :tag: hermes-2024-08-15-RNv0.75.1-4b3bf912cc0f705b51b71ce1a5b8bd79b93a451b
   lottie-react-native:
     :path: "../node_modules/lottie-react-native"
+  NitroModules:
+    :path: "../node_modules/react-native-nitro-modules"
   onfido-react-native-sdk:
     :path: "../node_modules/@onfido/react-native-sdk"
   RCT-Folly:
@@ -3160,6 +3211,7 @@ SPEC CHECKSUMS:
   AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa
   AppLogs: 3bc4e9b141dbf265b9464409caaa40416a9ee0e0
   boost: 26992d1adf73c1c7676360643e687aee6dda994b
+  ContactsModule: 21671b28654413dc28795d1afc3b12eaffa28ed1
   DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5
   EXAV: afa491e598334bbbb92a92a2f4dd33d7149ad37f
   EXImageLoader: ab589d67d6c5f2c33572afea9917304418566334
@@ -3199,6 +3251,7 @@ SPEC CHECKSUMS:
   MapboxMaps: e76b14f52c54c40b76ddecd04f40448e6f35a864
   MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6
   nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
+  NitroModules: ebe2ba2d01dc03c1f82441561fe6062b8c3c4366
   Onfido: f3af62ea1c9a419589c133e3e511e5d2c4f3f8af
   onfido-react-native-sdk: 4ccfdeb10f9ccb4a5799d2555cdbc2a068a42c0d
   Plaid: c32f22ffce5ec67c9e6147eaf6c4d7d5f8086d89
@@ -3295,7 +3348,7 @@ SPEC CHECKSUMS:
   RNLiveMarkdown: 8f9d9b32a25969ddb5f59eb92136b73823bbd141
   RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81
   rnmapbox-maps: 460d6ff97ae49c7d5708c3212c6521697c36a0c4
-  RNPermissions: 0b1429b55af59d1d08b75a8be2459f65a8ac3f28
+  RNPermissions: 9e5c26aaa982fe00743281f6f47fbdc050ebc58f
   RNReactNativeHapticFeedback: 73756a3477a5a622fa16862a3ab0d0fc5e5edff5
   RNReanimated: d95f865e1e42c34ca56b987e0719a8c72fc02dbc
   RNScreens: de6e57426ba0e6cbc3fb5b4f496e7f08cb2773c2
@@ -3311,6 +3364,6 @@ SPEC CHECKSUMS:
   VisionCamera: c95a8ad535f527562be1fb05fb2fd324578e769c
   Yoga: a1d7895431387402a674fd0d1c04ec85e87909b8
 
-PODFILE CHECKSUM: 615266329434ea4a994dccf622008a2197313c88
+PODFILE CHECKSUM: e744fa802b4bee097ff8d1977dd8f79d16b21547
 
 COCOAPODS: 1.15.2
diff --git a/modules/ContactsNitroModule/.gitignore b/modules/ContactsNitroModule/.gitignore
new file mode 100644
index 000000000000..d3b53dfce541
--- /dev/null
+++ b/modules/ContactsNitroModule/.gitignore
@@ -0,0 +1,78 @@
+# OSX
+#
+.DS_Store
+
+# XDE
+.expo/
+
+# VSCode
+.vscode/
+jsconfig.json
+
+# Xcode
+#
+build/
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata
+*.xccheckout
+*.moved-aside
+DerivedData
+*.hmap
+*.ipa
+*.xcuserstate
+project.xcworkspace
+
+# Android/IJ
+#
+.classpath
+.cxx
+.gradle
+.idea
+.project
+.settings
+local.properties
+android.iml
+
+# Cocoapods
+#
+example/ios/Pods
+
+# Ruby
+example/vendor/
+
+# node.js
+#
+node_modules/
+npm-debug.log
+yarn-debug.log
+yarn-error.log
+
+# BUCK
+buck-out/
+\.buckd/
+android/app/libs
+android/keystores/debug.keystore
+
+# Yarn
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/sdks
+!.yarn/versions
+
+# Expo
+.expo/
+
+# Turborepo
+.turbo/
+
+# generated by bob
+lib/
diff --git a/modules/ContactsNitroModule/.watchmanconfig b/modules/ContactsNitroModule/.watchmanconfig
new file mode 100644
index 000000000000..0967ef424bce
--- /dev/null
+++ b/modules/ContactsNitroModule/.watchmanconfig
@@ -0,0 +1 @@
+{}
diff --git a/modules/ContactsNitroModule/ContactsModule.podspec b/modules/ContactsNitroModule/ContactsModule.podspec
new file mode 100644
index 000000000000..5f0b012c7b52
--- /dev/null
+++ b/modules/ContactsNitroModule/ContactsModule.podspec
@@ -0,0 +1,29 @@
+require "json"
+
+package = JSON.parse(File.read(File.join(__dir__, "package.json")))
+
+Pod::Spec.new do |s|
+  s.name         = "ContactsModule"
+  s.version      = package["version"]
+  s.summary      = package["description"]
+  s.homepage     = package["homepage"]
+  s.license      = package["license"]
+  s.authors      = package["author"]
+
+  s.platforms    = { :ios => min_ios_version_supported }
+  s.source       = { :git => "https://github.com/mrousavy/nitro.git", :tag => "#{s.version}" }
+
+  s.source_files = [
+    # Implementation (Swift)
+    "ios/**/*.{swift}",
+    # Autolinking/Registration (Objective-C++)
+    "ios/**/*.{m,mm}",
+    # Implementation (C++ objects)
+    "cpp/**/*.{hpp,cpp}",
+  ]
+
+  load 'nitrogen/generated/ios/ContactsModule+autolinking.rb'
+  add_nitrogen_files(s)
+
+  install_modules_dependencies(s)
+end
diff --git a/modules/ContactsNitroModule/android/CMakeLists.txt b/modules/ContactsNitroModule/android/CMakeLists.txt
new file mode 100644
index 000000000000..beb0c308df07
--- /dev/null
+++ b/modules/ContactsNitroModule/android/CMakeLists.txt
@@ -0,0 +1,29 @@
+project(ContactsModule)
+cmake_minimum_required(VERSION 3.9.0)
+
+set (PACKAGE_NAME ContactsModule)
+set (CMAKE_VERBOSE_MAKEFILE ON)
+set (CMAKE_CXX_STANDARD 20)
+
+# Define C++ library and add all sources
+add_library(${PACKAGE_NAME} SHARED
+        src/main/cpp/cpp-adapter.cpp
+)
+
+# Add Nitrogen specs :)
+include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/ContactsModule+autolinking.cmake)
+
+# Set up local includes
+include_directories(
+        "src/main/cpp"
+        "../cpp"
+)
+
+find_library(LOG_LIB log)
+
+# Link all libraries together
+target_link_libraries(
+        ${PACKAGE_NAME}
+        ${LOG_LIB}
+        android                                   # <-- Android core
+)
diff --git a/modules/ContactsNitroModule/android/build.gradle b/modules/ContactsNitroModule/android/build.gradle
new file mode 100644
index 000000000000..0b414c88dea3
--- /dev/null
+++ b/modules/ContactsNitroModule/android/build.gradle
@@ -0,0 +1,130 @@
+buildscript {
+  repositories {
+    google()
+    mavenCentral()
+  }
+
+  dependencies {
+    classpath "com.android.tools.build:gradle:7.2.1"
+  }
+}
+
+def reactNativeArchitectures() {
+  def value = rootProject.getProperties().get("reactNativeArchitectures")
+  return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
+}
+
+def isNewArchitectureEnabled() {
+  return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
+}
+
+apply plugin: "com.android.library"
+apply plugin: 'org.jetbrains.kotlin.android'
+apply from: '../nitrogen/generated/android/ContactsModule+autolinking.gradle'
+
+if (isNewArchitectureEnabled()) {
+  apply plugin: "com.facebook.react"
+}
+
+def getExtOrDefault(name) {
+  return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["ContactsModule_" + name]
+}
+
+def getExtOrIntegerDefault(name) {
+  return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ContactsModule_" + name]).toInteger()
+}
+
+android {
+  namespace "com.margelo.nitro.contacts"
+
+  ndkVersion getExtOrDefault("ndkVersion")
+  compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
+
+  defaultConfig {
+    minSdkVersion getExtOrIntegerDefault("minSdkVersion")
+    targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
+    buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
+
+    externalNativeBuild {
+      cmake {
+        cppFlags "-O2 -frtti -fexceptions -Wall -fstack-protector-all"
+        arguments "-DANDROID_STL=c++_shared"
+        abiFilters (*reactNativeArchitectures())
+      }
+    }
+  }
+
+  externalNativeBuild {
+    cmake {
+      path "CMakeLists.txt"
+    }
+  }
+
+  packagingOptions {
+    excludes = [
+            "META-INF",
+            "META-INF/**",
+            "**/libc++_shared.so",
+            "**/libfbjni.so",
+            "**/libjsi.so",
+            "**/libfolly_json.so",
+            "**/libfolly_runtime.so",
+            "**/libglog.so",
+            "**/libhermes.so",
+            "**/libhermes-executor-debug.so",
+            "**/libhermes_executor.so",
+            "**/libreactnativejni.so",
+            "**/libturbomodulejsijni.so",
+            "**/libreact_nativemodule_core.so",
+            "**/libjscexecutor.so"
+    ]
+  }
+
+  buildFeatures {
+    buildConfig true
+    prefab true
+  }
+
+  buildTypes {
+    release {
+      minifyEnabled false
+    }
+  }
+
+  lintOptions {
+    disable "GradleCompatible"
+  }
+
+  compileOptions {
+    sourceCompatibility JavaVersion.VERSION_1_8
+    targetCompatibility JavaVersion.VERSION_1_8
+  }
+
+  sourceSets {
+    main {
+      if (isNewArchitectureEnabled()) {
+        java.srcDirs += [
+          // React Codegen files
+          "${project.buildDir}/generated/source/codegen/java"
+        ]
+      }
+    }
+  }
+}
+
+repositories {
+  mavenCentral()
+  google()
+}
+
+
+dependencies {
+  // For < 0.71, this will be from the local maven repo
+  // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
+  //noinspection GradleDynamicVersion
+  implementation "com.facebook.react:react-native:+"
+
+  // Add a dependency on NitroModules
+  implementation project(":react-native-nitro-modules")
+}
+
diff --git a/modules/ContactsNitroModule/android/gradle.properties b/modules/ContactsNitroModule/android/gradle.properties
new file mode 100644
index 000000000000..59d3858d1bb9
--- /dev/null
+++ b/modules/ContactsNitroModule/android/gradle.properties
@@ -0,0 +1,5 @@
+ContactsModule_kotlinVersion=1.9.24
+ContactsModule_minSdkVersion=23
+ContactsModule_targetSdkVersion=34
+ContactsModule_compileSdkVersion=34
+ContactsModule_ndkVersion=26.1.10909125
diff --git a/modules/ContactsNitroModule/android/src/main/AndroidManifest.xml b/modules/ContactsNitroModule/android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000000..a2f47b6057db
--- /dev/null
+++ b/modules/ContactsNitroModule/android/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+</manifest>
diff --git a/modules/ContactsNitroModule/android/src/main/cpp/cpp-adapter.cpp b/modules/ContactsNitroModule/android/src/main/cpp/cpp-adapter.cpp
new file mode 100644
index 000000000000..7a88410f3e4d
--- /dev/null
+++ b/modules/ContactsNitroModule/android/src/main/cpp/cpp-adapter.cpp
@@ -0,0 +1,6 @@
+#include <jni.h>
+#include "ContactsModuleOnLoad.hpp"
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
+  return margelo::nitro::contacts::initialize(vm);
+}
diff --git a/modules/ContactsNitroModule/android/src/main/java/com/margelo/nitro/contacts/ContactsModulePackage.java b/modules/ContactsNitroModule/android/src/main/java/com/margelo/nitro/contacts/ContactsModulePackage.java
new file mode 100644
index 000000000000..e8c26844ce86
--- /dev/null
+++ b/modules/ContactsNitroModule/android/src/main/java/com/margelo/nitro/contacts/ContactsModulePackage.java
@@ -0,0 +1,34 @@
+package com.margelo.nitro.contacts;
+
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.module.model.ReactModuleInfoProvider;
+import com.facebook.react.TurboReactPackage;
+import com.margelo.nitro.core.HybridObject;
+import com.margelo.nitro.core.HybridObjectRegistry;
+
+import java.util.HashMap;
+import java.util.function.Supplier;
+
+public class ContactsModulePackage extends TurboReactPackage {
+  @Nullable
+  @Override
+  public NativeModule getModule(String name, ReactApplicationContext reactContext) {
+    return null;
+  }
+
+  @Override
+  public ReactModuleInfoProvider getReactModuleInfoProvider() {
+    return () -> {
+        return new HashMap<>();
+    };
+  }
+
+  static {
+    System.loadLibrary("ContactsModule");
+  }
+}
diff --git a/modules/ContactsNitroModule/android/src/main/java/com/margelo/nitro/contacts/HybridContactsModule.kt b/modules/ContactsNitroModule/android/src/main/java/com/margelo/nitro/contacts/HybridContactsModule.kt
new file mode 100644
index 000000000000..00feaa7660c2
--- /dev/null
+++ b/modules/ContactsNitroModule/android/src/main/java/com/margelo/nitro/contacts/HybridContactsModule.kt
@@ -0,0 +1,166 @@
+package com.margelo.nitro.contacts
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.provider.ContactsContract
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import com.facebook.react.bridge.ReactApplicationContext
+import com.margelo.nitro.NitroModules
+import com.margelo.nitro.core.Promise
+
+class HybridContactsModule : HybridContactsModuleSpec() {
+    @Volatile
+    private var estimatedMemorySize: Long = 0
+
+    override val memorySize: Long
+        get() = estimatedMemorySize
+
+    private val context: ReactApplicationContext? = NitroModules.applicationContext
+
+    private fun requestContactPermission(): Boolean {
+        val currentActivity = context?.currentActivity
+        return if (currentActivity != null) {
+            ActivityCompat.requestPermissions(
+                currentActivity, arrayOf(REQUIRED_PERMISSION), PERMISSION_REQUEST_CODE
+            )
+            true
+        } else {
+            false
+        }
+    }
+
+    private fun hasPhoneContactsPermission(): Boolean {
+        return context?.let {
+            ContextCompat.checkSelfPermission(it, Manifest.permission.READ_CONTACTS)
+        } == PackageManager.PERMISSION_GRANTED
+    }
+
+    override fun getAll(keys: Array<ContactFields>): Promise<Array<Contact>> {
+        return Promise.parallel {
+            val contacts = mutableListOf<Contact>()
+            if (!hasPhoneContactsPermission()) {
+                requestContactPermission()
+                return@parallel emptyArray()
+            }
+
+            context?.contentResolver?.let { resolver ->
+                val projection = arrayOf(
+                    ContactsContract.Data.MIMETYPE,
+                    ContactsContract.Data.CONTACT_ID,
+                    ContactsContract.Data.DISPLAY_NAME,
+                    ContactsContract.Contacts.PHOTO_URI,
+                    ContactsContract.Contacts.PHOTO_THUMBNAIL_URI,
+                    ContactsContract.Data.DATA1,
+                    ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
+                    ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
+                    ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME
+                )
+
+                val selection = "${ContactsContract.Data.MIMETYPE} IN (?, ?, ?)"
+                val selectionArgs = arrayOf(
+                    ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE,
+                    ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
+                    ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
+                )
+
+                val sortOrder = "${ContactsContract.Data.CONTACT_ID} ASC"
+
+                resolver.query(
+                    ContactsContract.Data.CONTENT_URI,
+                    projection,
+                    selection,
+                    selectionArgs,
+                    sortOrder
+                )?.use { cursor ->
+                    val mimeTypeIndex = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE)
+                    val contactIdIndex = cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID)
+                    val photoUriIndex = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI)
+                    val thumbnailUriIndex =
+                        cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI)
+                    val data1Index = cursor.getColumnIndex(ContactsContract.Data.DATA1)
+                    val givenNameIndex =
+                        cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)
+                    val familyNameIndex =
+                        cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)
+                    val middleNameIndex =
+                        cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME)
+
+                    var currentContact: Contact? = null
+                    var currentContactId: String? = null
+                    val currentPhoneNumbers = mutableListOf<StringHolder>()
+                    val currentEmailAddresses = mutableListOf<StringHolder>()
+
+                    while (cursor.moveToNext()) {
+                        val contactId = cursor.getString(contactIdIndex)
+                        val mimeType = cursor.getString(mimeTypeIndex)
+
+                        if (contactId != currentContactId) {
+                            currentContact?.let { contact ->
+                                contacts.add(
+                                    contact.copy(
+                                        phoneNumbers = currentPhoneNumbers.toTypedArray(),
+                                        emailAddresses = currentEmailAddresses.toTypedArray()
+                                    )
+                                )
+                            }
+                            currentPhoneNumbers.clear()
+                            currentEmailAddresses.clear()
+                            currentContact = Contact(
+                                firstName = "",
+                                lastName = "",
+                                middleName = null,
+                                phoneNumbers = emptyArray(),
+                                emailAddresses = emptyArray(),
+                                imageData = cursor.getString(photoUriIndex) ?: "",
+                                thumbnailImageData = cursor.getString(thumbnailUriIndex) ?: ""
+                            )
+                            currentContactId = contactId
+                        }
+
+                        when (mimeType) {
+                            ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> {
+                                currentContact = currentContact?.copy(
+                                    firstName = cursor.getString(givenNameIndex) ?: "",
+                                    lastName = cursor.getString(familyNameIndex) ?: "",
+                                    middleName = cursor.getString(middleNameIndex)
+                                )
+                            }
+
+                            ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
+                                cursor.getString(data1Index)?.let { phone ->
+                                    currentPhoneNumbers.add(StringHolder(phone))
+                                }
+                            }
+
+                            ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE -> {
+                                cursor.getString(data1Index)?.let { email ->
+                                    currentEmailAddresses.add(StringHolder(email))
+                                }
+                            }
+                        }
+                    }
+
+                    // Add the last contact
+                    currentContact?.let { contact ->
+                        contacts.add(
+                            contact.copy(
+                                phoneNumbers = currentPhoneNumbers.toTypedArray(),
+                                emailAddresses = currentEmailAddresses.toTypedArray()
+                            )
+                        )
+                    }
+                }
+            }
+
+            // Update memory size based on contact count
+            estimatedMemorySize = contacts.size.toLong() * 1024 // Assume ~1KB per contact
+            contacts.toTypedArray()
+        }
+    }
+
+    companion object {
+        const val PERMISSION_REQUEST_CODE = 1
+        const val REQUIRED_PERMISSION = Manifest.permission.READ_CONTACTS
+    }
+}
diff --git a/modules/ContactsNitroModule/babel.config.js b/modules/ContactsNitroModule/babel.config.js
new file mode 100644
index 000000000000..3e0218e68fc3
--- /dev/null
+++ b/modules/ContactsNitroModule/babel.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+  presets: ['module:@react-native/babel-preset'],
+}
diff --git a/modules/ContactsNitroModule/ios/HybridContactsModule.swift b/modules/ContactsNitroModule/ios/HybridContactsModule.swift
new file mode 100644
index 000000000000..59cc3ea31702
--- /dev/null
+++ b/modules/ContactsNitroModule/ios/HybridContactsModule.swift
@@ -0,0 +1,72 @@
+import NitroModules
+import Contacts
+import Foundation
+
+final class HybridContactsModule: HybridContactsModuleSpec {
+    public var hybridContext = margelo.nitro.HybridContext()
+    public var memorySize: Int { MemoryLayout<HybridContactsModule>.size }
+    
+    private let contactStore = CNContactStore()
+    private let imageDirectory: URL
+    private let fieldToKeyDescriptor: [ContactFields: CNKeyDescriptor] = [
+        .firstName: CNContactGivenNameKey as CNKeyDescriptor,
+        .lastName: CNContactFamilyNameKey as CNKeyDescriptor,
+        .phoneNumbers: CNContactPhoneNumbersKey as CNKeyDescriptor,
+        .emailAddresses: CNContactEmailAddressesKey as CNKeyDescriptor,
+        .middleName: CNContactMiddleNameKey as CNKeyDescriptor,
+        .imageData: CNContactImageDataKey as CNKeyDescriptor,
+        .thumbnailImageData: CNContactThumbnailImageDataKey as CNKeyDescriptor,
+        .givenNameKey: CNContactGivenNameKey as CNKeyDescriptor
+    ]
+    
+    init() {
+        imageDirectory = FileManager.default.temporaryDirectory.appendingPathComponent("ContactImages")
+        try? FileManager.default.createDirectory(at: imageDirectory, withIntermediateDirectories: true)
+    }
+    
+    func getAll(keys: [ContactFields]) throws -> Promise<[Contact]> {
+        Promise.async { [unowned self] in
+            let keysSet = Set(keys)
+            let keysToFetch = keys.compactMap { self.fieldToKeyDescriptor[$0] }
+            guard !keysToFetch.isEmpty else { return [] }
+            
+            let request = CNContactFetchRequest(keysToFetch: keysToFetch)
+            var contacts = [Contact]()
+            contacts.reserveCapacity(1000)
+            
+            try self.contactStore.enumerateContacts(with: request) { contact, _ in
+                contacts.append(self.processContact(contact, keysSet: keysSet))
+            }
+            
+            return contacts
+        }
+    }
+    
+    @inline(__always)
+    private func processContact(_ contact: CNContact, keysSet: Set<ContactFields>) -> Contact {
+        Contact(
+            firstName: keysSet.contains(.firstName) ? contact.givenName : nil,
+            lastName: keysSet.contains(.lastName) ? contact.familyName : nil,
+            middleName: keysSet.contains(.middleName) ? contact.middleName : nil,
+            phoneNumbers: keysSet.contains(.phoneNumbers) ? contact.phoneNumbers.map { StringHolder(value: $0.value.stringValue) } : nil,
+            emailAddresses: keysSet.contains(.emailAddresses) ? contact.emailAddresses.map { StringHolder(value: $0.value as String) } : nil,
+            imageData: keysSet.contains(.imageData) ? getImagePath(for: contact, isThumbnail: false) : nil,
+            thumbnailImageData: keysSet.contains(.thumbnailImageData) ? getImagePath(for: contact, isThumbnail: true) : nil
+        )
+    }
+    
+    @inline(__always)
+    private func getImagePath(for contact: CNContact, isThumbnail: Bool) -> String? {
+        let imageData = isThumbnail ? contact.thumbnailImageData : contact.imageData
+        guard let data = imageData else { return nil }
+        
+        let fileName = "\(contact.identifier)_\(isThumbnail ? "thumb" : "full").jpg"
+        let fileURL = imageDirectory.appendingPathComponent(fileName)
+        
+        if !FileManager.default.fileExists(atPath: fileURL.path) {
+            try? data.write(to: fileURL, options: .atomic)
+        }
+        
+        return fileURL.path
+    }
+}
diff --git a/modules/ContactsNitroModule/nitro.json b/modules/ContactsNitroModule/nitro.json
new file mode 100644
index 000000000000..426f8486118a
--- /dev/null
+++ b/modules/ContactsNitroModule/nitro.json
@@ -0,0 +1,17 @@
+{
+  "cxxNamespace": ["contacts"],
+  "ios": {
+    "iosModuleName": "ContactsModule"
+  },
+  "android": {
+    "androidNamespace": ["contacts"],
+    "androidCxxLibName": "ContactsModule"
+  },
+  "autolinking": {
+    "ContactsModule": {
+      "swift": "HybridContactsModule",
+      "kotlin": "HybridContactsModule"
+    }
+  },
+  "ignorePaths": ["node_modules"]
+}
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModule+autolinking.cmake b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModule+autolinking.cmake
new file mode 100644
index 000000000000..5478bc224b05
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModule+autolinking.cmake
@@ -0,0 +1,59 @@
+#
+# ContactsModule+autolinking.cmake
+# This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+# https://github.com/mrousavy/nitro
+# Copyright © 2024 Marc Rousavy @ Margelo
+#
+
+# This is a CMake file that adds all files generated by Nitrogen
+# to the current CMake project.
+#
+# To use it, add this to your CMakeLists.txt:
+# ```cmake
+# include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/ContactsModule+autolinking.cmake)
+# ```
+
+# Add all headers that were generated by Nitrogen
+include_directories(
+  "../nitrogen/generated/shared/c++"
+  "../nitrogen/generated/android/c++"
+  "../nitrogen/generated/android/"
+)
+
+# Add all .cpp sources that were generated by Nitrogen
+target_sources(
+  # CMake project name (Android C++ library name)
+  ContactsModule PRIVATE
+  # Autolinking Setup
+  ../nitrogen/generated/android/ContactsModuleOnLoad.cpp
+  # Shared Nitrogen C++ sources
+  ../nitrogen/generated/shared/c++/HybridContactsModuleSpec.cpp
+  # Android-specific Nitrogen C++ sources
+  ../nitrogen/generated/android/c++/JHybridContactsModuleSpec.cpp
+)
+
+# Add all libraries required by the generated specs
+find_package(fbjni REQUIRED) # <-- Used for communication between Java <-> C++
+find_package(ReactAndroid REQUIRED) # <-- Used to set up React Native bindings (e.g. CallInvoker/TurboModule)
+find_package(react-native-nitro-modules REQUIRED) # <-- Used to create all HybridObjects and use the Nitro core library
+
+# Link all libraries together
+target_link_libraries(
+        ContactsModule
+        fbjni::fbjni                              # <-- Facebook C++ JNI helpers
+        ReactAndroid::jsi                         # <-- RN: JSI
+        react-native-nitro-modules::NitroModules  # <-- NitroModules Core :)
+)
+
+# Link react-native (different prefab between RN 0.75 and RN 0.76)
+if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
+    target_link_libraries(
+        ContactsModule
+        ReactAndroid::reactnative                 # <-- RN: Native Modules umbrella prefab
+    )
+else()
+    target_link_libraries(
+        ContactsModule
+        ReactAndroid::react_nativemodule_core     # <-- RN: TurboModules Core
+    )
+endif()
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModule+autolinking.gradle b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModule+autolinking.gradle
new file mode 100644
index 000000000000..2d19cd2ced32
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModule+autolinking.gradle
@@ -0,0 +1,27 @@
+///
+/// ContactsModule+autolinking.gradle
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+/// This is a Gradle file that adds all files generated by Nitrogen
+/// to the current Gradle project.
+///
+/// To use it, add this to your build.gradle:
+/// ```gradle
+/// apply from: '../nitrogen/generated/android/ContactsModule+autolinking.gradle'
+/// ```
+
+logger.warn("[NitroModules] 🔥 ContactsModule is boosted by nitro!")
+
+android {
+  sourceSets {
+    main {
+      java.srcDirs += [
+        // Nitrogen files
+        "${project.projectDir}/../nitrogen/generated/android/kotlin"
+      ]
+    }
+  }
+}
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.cpp b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.cpp
new file mode 100644
index 000000000000..156ea811e509
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.cpp
@@ -0,0 +1,42 @@
+///
+/// ContactsModuleOnLoad.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#include "ContactsModuleOnLoad.hpp"
+
+#include <jni.h>
+#include <fbjni/fbjni.h>
+#include <NitroModules/HybridObjectRegistry.hpp>
+
+#include "JHybridContactsModuleSpec.hpp"
+#include <NitroModules/JNISharedPtr.hpp>
+#include <NitroModules/DefaultConstructableObject.hpp>
+
+namespace margelo::nitro::contacts {
+
+int initialize(JavaVM* vm) {
+  using namespace margelo::nitro;
+  using namespace margelo::nitro::contacts;
+  using namespace facebook;
+
+  return facebook::jni::initialize(vm, [] {
+    // Register native JNI methods
+    margelo::nitro::contacts::JHybridContactsModuleSpec::registerNatives();
+
+    // Register Nitro Hybrid Objects
+    HybridObjectRegistry::registerHybridObjectConstructor(
+      "ContactsModule",
+      []() -> std::shared_ptr<HybridObject> {
+        static DefaultConstructableObject<JHybridContactsModuleSpec::javaobject> object("com/margelo/nitro/contacts/HybridContactsModule");
+        auto instance = object.create();
+        auto globalRef = jni::make_global(instance);
+        return JNISharedPtr::make_shared_from_jni<JHybridContactsModuleSpec>(globalRef);
+      }
+    );
+  });
+}
+
+} // namespace margelo::nitro::contacts
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.hpp b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.hpp
new file mode 100644
index 000000000000..b71adaca07bf
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.hpp
@@ -0,0 +1,25 @@
+///
+/// ContactsModuleOnLoad.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#include <jni.h>
+#include <NitroModules/NitroDefines.hpp>
+
+namespace margelo::nitro::contacts {
+
+  /**
+   * Initializes the native (C++) part of ContactsModule, and autolinks all Hybrid Objects.
+   * Call this in your `JNI_OnLoad` function (probably inside `cpp-adapter.cpp`).
+   * Example:
+   * ```cpp (cpp-adapter.cpp)
+   * JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
+   *   return margelo::nitro::contacts::initialize(vm);
+   * }
+   * ```
+   */
+  int initialize(JavaVM* vm);
+
+} // namespace margelo::nitro::contacts
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.kt b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.kt
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/ContactsModuleOnLoad.kt
@@ -0,0 +1 @@
+
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JContact.hpp b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JContact.hpp
new file mode 100644
index 000000000000..bbd5354163a2
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JContact.hpp
@@ -0,0 +1,114 @@
+///
+/// JContact.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#include <fbjni/fbjni.h>
+#include "Contact.hpp"
+
+#include "JStringHolder.hpp"
+#include "StringHolder.hpp"
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace margelo::nitro::contacts {
+
+  using namespace facebook;
+
+  /**
+   * The C++ JNI bridge between the C++ struct "Contact" and the the Kotlin data class "Contact".
+   */
+  struct JContact final: public jni::JavaClass<JContact> {
+  public:
+    static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/contacts/Contact;";
+
+  public:
+    /**
+     * Convert this Java/Kotlin-based struct to the C++ struct Contact by copying all values to C++.
+     */
+    [[maybe_unused]]
+    Contact toCpp() const {
+      static const auto clazz = javaClassStatic();
+      static const auto fieldFirstName = clazz->getField<jni::JString>("firstName");
+      jni::local_ref<jni::JString> firstName = this->getFieldValue(fieldFirstName);
+      static const auto fieldLastName = clazz->getField<jni::JString>("lastName");
+      jni::local_ref<jni::JString> lastName = this->getFieldValue(fieldLastName);
+      static const auto fieldMiddleName = clazz->getField<jni::JString>("middleName");
+      jni::local_ref<jni::JString> middleName = this->getFieldValue(fieldMiddleName);
+      static const auto fieldPhoneNumbers = clazz->getField<jni::JArrayClass<JStringHolder>>("phoneNumbers");
+      jni::local_ref<jni::JArrayClass<JStringHolder>> phoneNumbers = this->getFieldValue(fieldPhoneNumbers);
+      static const auto fieldEmailAddresses = clazz->getField<jni::JArrayClass<JStringHolder>>("emailAddresses");
+      jni::local_ref<jni::JArrayClass<JStringHolder>> emailAddresses = this->getFieldValue(fieldEmailAddresses);
+      static const auto fieldImageData = clazz->getField<jni::JString>("imageData");
+      jni::local_ref<jni::JString> imageData = this->getFieldValue(fieldImageData);
+      static const auto fieldThumbnailImageData = clazz->getField<jni::JString>("thumbnailImageData");
+      jni::local_ref<jni::JString> thumbnailImageData = this->getFieldValue(fieldThumbnailImageData);
+      return Contact(
+        firstName != nullptr ? std::make_optional(firstName->toStdString()) : std::nullopt,
+        lastName != nullptr ? std::make_optional(lastName->toStdString()) : std::nullopt,
+        middleName != nullptr ? std::make_optional(middleName->toStdString()) : std::nullopt,
+        phoneNumbers != nullptr ? std::make_optional([&]() {
+          size_t __size = phoneNumbers->size();
+          std::vector<StringHolder> __vector;
+          __vector.reserve(__size);
+          for (size_t __i = 0; __i < __size; __i++) {
+            auto __element = phoneNumbers->getElement(__i);
+            __vector.push_back(__element->toCpp());
+          }
+          return __vector;
+        }()) : std::nullopt,
+        emailAddresses != nullptr ? std::make_optional([&]() {
+          size_t __size = emailAddresses->size();
+          std::vector<StringHolder> __vector;
+          __vector.reserve(__size);
+          for (size_t __i = 0; __i < __size; __i++) {
+            auto __element = emailAddresses->getElement(__i);
+            __vector.push_back(__element->toCpp());
+          }
+          return __vector;
+        }()) : std::nullopt,
+        imageData != nullptr ? std::make_optional(imageData->toStdString()) : std::nullopt,
+        thumbnailImageData != nullptr ? std::make_optional(thumbnailImageData->toStdString()) : std::nullopt
+      );
+    }
+
+  public:
+    /**
+     * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java.
+     */
+    [[maybe_unused]]
+    static jni::local_ref<JContact::javaobject> fromCpp(const Contact& value) {
+      return newInstance(
+        value.firstName.has_value() ? jni::make_jstring(value.firstName.value()) : nullptr,
+        value.lastName.has_value() ? jni::make_jstring(value.lastName.value()) : nullptr,
+        value.middleName.has_value() ? jni::make_jstring(value.middleName.value()) : nullptr,
+        value.phoneNumbers.has_value() ? [&]() {
+          size_t __size = value.phoneNumbers.value().size();
+          jni::local_ref<jni::JArrayClass<JStringHolder>> __array = jni::JArrayClass<JStringHolder>::newArray(__size);
+          for (size_t __i = 0; __i < __size; __i++) {
+            const auto& __element = value.phoneNumbers.value()[__i];
+            __array->setElement(__i, *JStringHolder::fromCpp(__element));
+          }
+          return __array;
+        }() : nullptr,
+        value.emailAddresses.has_value() ? [&]() {
+          size_t __size = value.emailAddresses.value().size();
+          jni::local_ref<jni::JArrayClass<JStringHolder>> __array = jni::JArrayClass<JStringHolder>::newArray(__size);
+          for (size_t __i = 0; __i < __size; __i++) {
+            const auto& __element = value.emailAddresses.value()[__i];
+            __array->setElement(__i, *JStringHolder::fromCpp(__element));
+          }
+          return __array;
+        }() : nullptr,
+        value.imageData.has_value() ? jni::make_jstring(value.imageData.value()) : nullptr,
+        value.thumbnailImageData.has_value() ? jni::make_jstring(value.thumbnailImageData.value()) : nullptr
+      );
+    }
+  };
+
+} // namespace margelo::nitro::contacts
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JContactFields.hpp b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JContactFields.hpp
new file mode 100644
index 000000000000..371b6607d105
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JContactFields.hpp
@@ -0,0 +1,76 @@
+///
+/// JContactFields.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#include <fbjni/fbjni.h>
+#include "ContactFields.hpp"
+
+namespace margelo::nitro::contacts {
+
+  using namespace facebook;
+
+  /**
+   * The C++ JNI bridge between the C++ enum "ContactFields" and the the Kotlin enum "ContactFields".
+   */
+  struct JContactFields final: public jni::JavaClass<JContactFields> {
+  public:
+    static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/contacts/ContactFields;";
+
+  public:
+    /**
+     * Convert this Java/Kotlin-based enum to the C++ enum ContactFields.
+     */
+    [[maybe_unused]]
+    ContactFields toCpp() const {
+      static const auto clazz = javaClassStatic();
+      static const auto fieldOrdinal = clazz->getField<int>("_ordinal");
+      int ordinal = this->getFieldValue(fieldOrdinal);
+      return static_cast<ContactFields>(ordinal);
+    }
+
+  public:
+    /**
+     * Create a Java/Kotlin-based enum with the given C++ enum's value.
+     */
+    [[maybe_unused]]
+    static jni::alias_ref<JContactFields> fromCpp(ContactFields value) {
+      static const auto clazz = javaClassStatic();
+      static const auto fieldFIRST_NAME = clazz->getStaticField<JContactFields>("FIRST_NAME");
+      static const auto fieldLAST_NAME = clazz->getStaticField<JContactFields>("LAST_NAME");
+      static const auto fieldMIDDLE_NAME = clazz->getStaticField<JContactFields>("MIDDLE_NAME");
+      static const auto fieldPHONE_NUMBERS = clazz->getStaticField<JContactFields>("PHONE_NUMBERS");
+      static const auto fieldEMAIL_ADDRESSES = clazz->getStaticField<JContactFields>("EMAIL_ADDRESSES");
+      static const auto fieldIMAGE_DATA = clazz->getStaticField<JContactFields>("IMAGE_DATA");
+      static const auto fieldTHUMBNAIL_IMAGE_DATA = clazz->getStaticField<JContactFields>("THUMBNAIL_IMAGE_DATA");
+      static const auto fieldGIVEN_NAME_KEY = clazz->getStaticField<JContactFields>("GIVEN_NAME_KEY");
+      
+      switch (value) {
+        case ContactFields::FIRST_NAME:
+          return clazz->getStaticFieldValue(fieldFIRST_NAME);
+        case ContactFields::LAST_NAME:
+          return clazz->getStaticFieldValue(fieldLAST_NAME);
+        case ContactFields::MIDDLE_NAME:
+          return clazz->getStaticFieldValue(fieldMIDDLE_NAME);
+        case ContactFields::PHONE_NUMBERS:
+          return clazz->getStaticFieldValue(fieldPHONE_NUMBERS);
+        case ContactFields::EMAIL_ADDRESSES:
+          return clazz->getStaticFieldValue(fieldEMAIL_ADDRESSES);
+        case ContactFields::IMAGE_DATA:
+          return clazz->getStaticFieldValue(fieldIMAGE_DATA);
+        case ContactFields::THUMBNAIL_IMAGE_DATA:
+          return clazz->getStaticFieldValue(fieldTHUMBNAIL_IMAGE_DATA);
+        case ContactFields::GIVEN_NAME_KEY:
+          return clazz->getStaticFieldValue(fieldGIVEN_NAME_KEY);
+        default:
+          std::string stringValue = std::to_string(static_cast<int>(value));
+          throw std::invalid_argument("Invalid enum value (" + stringValue + "!");
+      }
+    }
+  };
+
+} // namespace margelo::nitro::contacts
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JHybridContactsModuleSpec.cpp b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JHybridContactsModuleSpec.cpp
new file mode 100644
index 000000000000..e0505ee46d36
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JHybridContactsModuleSpec.cpp
@@ -0,0 +1,84 @@
+///
+/// JHybridContactsModuleSpec.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#include "JHybridContactsModuleSpec.hpp"
+
+// Forward declaration of `Contact` to properly resolve imports.
+namespace margelo::nitro::contacts { struct Contact; }
+// Forward declaration of `StringHolder` to properly resolve imports.
+namespace margelo::nitro::contacts { struct StringHolder; }
+// Forward declaration of `ContactFields` to properly resolve imports.
+namespace margelo::nitro::contacts { enum class ContactFields; }
+
+#include <NitroModules/Promise.hpp>
+#include <vector>
+#include "Contact.hpp"
+#include <NitroModules/JPromise.hpp>
+#include "JContact.hpp"
+#include <optional>
+#include <string>
+#include "StringHolder.hpp"
+#include "JStringHolder.hpp"
+#include "ContactFields.hpp"
+#include "JContactFields.hpp"
+
+namespace margelo::nitro::contacts {
+
+  jni::local_ref<JHybridContactsModuleSpec::jhybriddata> JHybridContactsModuleSpec::initHybrid(jni::alias_ref<jhybridobject> jThis) {
+    return makeCxxInstance(jThis);
+  }
+
+  void JHybridContactsModuleSpec::registerNatives() {
+    registerHybrid({
+      makeNativeMethod("initHybrid", JHybridContactsModuleSpec::initHybrid),
+    });
+  }
+
+  size_t JHybridContactsModuleSpec::getExternalMemorySize() noexcept {
+    static const auto method = _javaPart->getClass()->getMethod<jlong()>("getMemorySize");
+    return method(_javaPart);
+  }
+
+  // Properties
+  
+
+  // Methods
+  std::shared_ptr<Promise<std::vector<Contact>>> JHybridContactsModuleSpec::getAll(const std::vector<ContactFields>& keys) {
+    static const auto method = _javaPart->getClass()->getMethod<jni::local_ref<JPromise::javaobject>(jni::alias_ref<jni::JArrayClass<JContactFields>> /* keys */)>("getAll");
+    auto __result = method(_javaPart, [&]() {
+      size_t __size = keys.size();
+      jni::local_ref<jni::JArrayClass<JContactFields>> __array = jni::JArrayClass<JContactFields>::newArray(__size);
+      for (size_t __i = 0; __i < __size; __i++) {
+        const auto& __element = keys[__i];
+        __array->setElement(__i, *JContactFields::fromCpp(__element));
+      }
+      return __array;
+    }());
+    return [&]() {
+      auto __promise = Promise<std::vector<Contact>>::create();
+      __result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& __boxedResult) {
+        auto __result = jni::static_ref_cast<jni::JArrayClass<JContact>>(__boxedResult);
+        __promise->resolve([&]() {
+          size_t __size = __result->size();
+          std::vector<Contact> __vector;
+          __vector.reserve(__size);
+          for (size_t __i = 0; __i < __size; __i++) {
+            auto __element = __result->getElement(__i);
+            __vector.push_back(__element->toCpp());
+          }
+          return __vector;
+        }());
+      });
+      __result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
+        jni::JniException __jniError(__throwable);
+        __promise->reject(std::make_exception_ptr(__jniError));
+      });
+      return __promise;
+    }();
+  }
+
+} // namespace margelo::nitro::contacts
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JHybridContactsModuleSpec.hpp b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JHybridContactsModuleSpec.hpp
new file mode 100644
index 000000000000..6b94d3be37e7
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JHybridContactsModuleSpec.hpp
@@ -0,0 +1,62 @@
+///
+/// HybridContactsModuleSpec.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#include <NitroModules/JHybridObject.hpp>
+#include <fbjni/fbjni.h>
+#include "HybridContactsModuleSpec.hpp"
+
+
+
+
+namespace margelo::nitro::contacts {
+
+  using namespace facebook;
+
+  class JHybridContactsModuleSpec: public jni::HybridClass<JHybridContactsModuleSpec, JHybridObject>,
+                                   public virtual HybridContactsModuleSpec {
+  public:
+    static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/contacts/HybridContactsModuleSpec;";
+    static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
+    static void registerNatives();
+
+  protected:
+    // C++ constructor (called from Java via `initHybrid()`)
+    explicit JHybridContactsModuleSpec(jni::alias_ref<jhybridobject> jThis) :
+      HybridObject(HybridContactsModuleSpec::TAG),
+      _javaPart(jni::make_global(jThis)) {}
+
+  public:
+    virtual ~JHybridContactsModuleSpec() {
+      // Hermes GC can destroy JS objects on a non-JNI Thread.
+      jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); });
+    }
+
+  public:
+    size_t getExternalMemorySize() noexcept override;
+
+  public:
+    inline const jni::global_ref<JHybridContactsModuleSpec::javaobject>& getJavaPart() const noexcept {
+      return _javaPart;
+    }
+
+  public:
+    // Properties
+    
+
+  public:
+    // Methods
+    std::shared_ptr<Promise<std::vector<Contact>>> getAll(const std::vector<ContactFields>& keys) override;
+
+  private:
+    friend HybridBase;
+    using HybridBase::HybridBase;
+    jni::global_ref<JHybridContactsModuleSpec::javaobject> _javaPart;
+  };
+
+} // namespace margelo::nitro::contacts
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/c++/JStringHolder.hpp b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JStringHolder.hpp
new file mode 100644
index 000000000000..29695fe48d58
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/c++/JStringHolder.hpp
@@ -0,0 +1,52 @@
+///
+/// JStringHolder.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#include <fbjni/fbjni.h>
+#include "StringHolder.hpp"
+
+#include <string>
+
+namespace margelo::nitro::contacts {
+
+  using namespace facebook;
+
+  /**
+   * The C++ JNI bridge between the C++ struct "StringHolder" and the the Kotlin data class "StringHolder".
+   */
+  struct JStringHolder final: public jni::JavaClass<JStringHolder> {
+  public:
+    static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/contacts/StringHolder;";
+
+  public:
+    /**
+     * Convert this Java/Kotlin-based struct to the C++ struct StringHolder by copying all values to C++.
+     */
+    [[maybe_unused]]
+    StringHolder toCpp() const {
+      static const auto clazz = javaClassStatic();
+      static const auto fieldValue = clazz->getField<jni::JString>("value");
+      jni::local_ref<jni::JString> value = this->getFieldValue(fieldValue);
+      return StringHolder(
+        value->toStdString()
+      );
+    }
+
+  public:
+    /**
+     * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java.
+     */
+    [[maybe_unused]]
+    static jni::local_ref<JStringHolder::javaobject> fromCpp(const StringHolder& value) {
+      return newInstance(
+        jni::make_jstring(value.value)
+      );
+    }
+  };
+
+} // namespace margelo::nitro::contacts
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/Contact.kt b/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/Contact.kt
new file mode 100644
index 000000000000..a6d9e59a2b2b
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/Contact.kt
@@ -0,0 +1,27 @@
+///
+/// Contact.kt
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+package com.margelo.nitro.contacts
+
+import androidx.annotation.Keep
+import com.facebook.proguard.annotations.DoNotStrip
+import com.margelo.nitro.core.*
+
+/**
+ * Represents the JavaScript object/struct "Contact".
+ */
+@DoNotStrip
+@Keep
+data class Contact(
+  val firstName: String?,
+  val lastName: String?,
+  val middleName: String?,
+  val phoneNumbers: Array<StringHolder>?,
+  val emailAddresses: Array<StringHolder>?,
+  val imageData: String?,
+  val thumbnailImageData: String?
+)
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/ContactFields.kt b/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/ContactFields.kt
new file mode 100644
index 000000000000..841d6c82a32b
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/ContactFields.kt
@@ -0,0 +1,31 @@
+///
+/// ContactFields.kt
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+package com.margelo.nitro.contacts
+
+import androidx.annotation.Keep
+import com.facebook.proguard.annotations.DoNotStrip
+
+/**
+ * Represents the JavaScript enum/union "ContactFields".
+ */
+@DoNotStrip
+@Keep
+enum class ContactFields {
+  FIRST_NAME,
+  LAST_NAME,
+  MIDDLE_NAME,
+  PHONE_NUMBERS,
+  EMAIL_ADDRESSES,
+  IMAGE_DATA,
+  THUMBNAIL_IMAGE_DATA,
+  GIVEN_NAME_KEY;
+
+  @DoNotStrip
+  @Keep
+  private val _ordinal = ordinal
+}
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/HybridContactsModuleSpec.kt b/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/HybridContactsModuleSpec.kt
new file mode 100644
index 000000000000..63a118b8be57
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/HybridContactsModuleSpec.kt
@@ -0,0 +1,64 @@
+///
+/// HybridContactsModuleSpec.kt
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+package com.margelo.nitro.contacts
+
+import android.util.Log
+import androidx.annotation.Keep
+import com.facebook.jni.HybridData
+import com.facebook.proguard.annotations.DoNotStrip
+import com.margelo.nitro.core.*
+
+/**
+ * A Kotlin class representing the ContactsModule HybridObject.
+ * Implement this abstract class to create Kotlin-based instances of ContactsModule.
+ */
+@DoNotStrip
+@Keep
+@Suppress("RedundantSuppression", "KotlinJniMissingFunction", "PropertyName", "RedundantUnitReturnType", "unused")
+abstract class HybridContactsModuleSpec: HybridObject() {
+  @DoNotStrip
+  private var mHybridData: HybridData = initHybrid()
+
+  init {
+    // Pass this `HybridData` through to it's base class,
+    // to represent inheritance to JHybridObject on C++ side
+    super.updateNative(mHybridData)
+  }
+
+  /**
+   * Call from a child class to initialize HybridData with a child.
+   */
+  override fun updateNative(hybridData: HybridData) {
+    mHybridData = hybridData
+  }
+
+  // Properties
+  
+
+  // Methods
+  @DoNotStrip
+  @Keep
+  abstract fun getAll(keys: Array<ContactFields>): Promise<Array<Contact>>
+
+  private external fun initHybrid(): HybridData
+
+  companion object {
+    private const val TAG = "HybridContactsModuleSpec"
+    init {
+      try {
+        Log.i(TAG, "Loading ContactsModule C++ library...")
+        System.loadLibrary("ContactsModule")
+        Log.i(TAG, "Successfully loaded ContactsModule C++ library!")
+      } catch (e: Error) {
+        Log.e(TAG, "Failed to load ContactsModule C++ library! Is it properly installed and linked? " +
+                    "Is the name correct? (see `CMakeLists.txt`, at `add_library(...)`)", e)
+        throw e
+      }
+    }
+  }
+}
diff --git a/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/StringHolder.kt b/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/StringHolder.kt
new file mode 100644
index 000000000000..b6af53e53217
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/android/kotlin/com/margelo/nitro/contacts/StringHolder.kt
@@ -0,0 +1,21 @@
+///
+/// StringHolder.kt
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+package com.margelo.nitro.contacts
+
+import androidx.annotation.Keep
+import com.facebook.proguard.annotations.DoNotStrip
+import com.margelo.nitro.core.*
+
+/**
+ * Represents the JavaScript object/struct "StringHolder".
+ */
+@DoNotStrip
+@Keep
+data class StringHolder(
+  val value: String
+)
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule+autolinking.rb b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule+autolinking.rb
new file mode 100644
index 000000000000..35bc19c47bf7
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule+autolinking.rb
@@ -0,0 +1,58 @@
+#
+# ContactsModule+autolinking.rb
+# This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+# https://github.com/mrousavy/nitro
+# Copyright © 2024 Marc Rousavy @ Margelo
+#
+
+# This is a Ruby script that adds all files generated by Nitrogen
+# to the given podspec.
+#
+# To use it, add this to your .podspec:
+# ```ruby
+# Pod::Spec.new do |spec|
+#   # ...
+#
+#   # Add all files generated by Nitrogen
+#   load 'nitrogen/generated/ios/ContactsModule+autolinking.rb'
+#   add_nitrogen_files(spec)
+# end
+# ```
+
+def add_nitrogen_files(spec)
+  Pod::UI.puts "[NitroModules] 🔥 ContactsModule is boosted by nitro!"
+
+  spec.dependency "NitroModules"
+
+  current_source_files = Array(spec.attributes_hash['source_files'])
+  spec.source_files = current_source_files + [
+    # Generated cross-platform specs
+    "nitrogen/generated/shared/**/*.{h,hpp,c,cpp,swift}",
+    # Generated bridges for the cross-platform specs
+    "nitrogen/generated/ios/**/*.{h,hpp,c,cpp,mm,swift}",
+  ]
+
+  current_public_header_files = Array(spec.attributes_hash['public_header_files'])
+  spec.public_header_files = current_public_header_files + [
+    # Generated specs
+    "nitrogen/generated/shared/**/*.{h,hpp}",
+    # Swift to C++ bridging helpers
+    "nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.hpp"
+  ]
+
+  current_private_header_files = Array(spec.attributes_hash['private_header_files'])
+  spec.private_header_files = current_private_header_files + [
+    # iOS specific specs
+    "nitrogen/generated/ios/c++/**/*.{h,hpp}",
+  ]
+
+  current_pod_target_xcconfig = spec.attributes_hash['pod_target_xcconfig'] || {}
+  spec.pod_target_xcconfig = current_pod_target_xcconfig.merge({
+    # Use C++ 20
+    "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
+    # Enables C++ <-> Swift interop (by default it's only C)
+    "SWIFT_OBJC_INTEROP_MODE" => "objcxx",
+    # Enables stricter modular headers
+    "DEFINES_MODULE" => "YES",
+  })
+end
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.cpp b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.cpp
new file mode 100644
index 000000000000..4746dbadaa18
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.cpp
@@ -0,0 +1,33 @@
+///
+/// ContactsModule-Swift-Cxx-Bridge.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#include "ContactsModule-Swift-Cxx-Bridge.hpp"
+
+// Include C++ implementation defined types
+#include "ContactsModule-Swift-Cxx-Umbrella.hpp"
+#include "HybridContactsModuleSpecSwift.hpp"
+#include <NitroModules/HybridContext.hpp>
+
+namespace margelo::nitro::contacts::bridge::swift {
+
+  // pragma MARK: std::shared_ptr<margelo::nitro::contacts::HybridContactsModuleSpec>
+  std::shared_ptr<margelo::nitro::contacts::HybridContactsModuleSpec> create_std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_(void* _Nonnull swiftUnsafePointer) {
+    ContactsModule::HybridContactsModuleSpecCxx swiftPart = ContactsModule::HybridContactsModuleSpecCxxUnsafe::fromUnsafe(swiftUnsafePointer);
+    return HybridContext::getOrCreate<margelo::nitro::contacts::HybridContactsModuleSpecSwift>(swiftPart);
+  }
+  void* _Nonnull get_std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_(std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_ cppType) {
+    std::shared_ptr<margelo::nitro::contacts::HybridContactsModuleSpecSwift> swiftWrapper = std::dynamic_pointer_cast<margelo::nitro::contacts::HybridContactsModuleSpecSwift>(cppType);
+  #ifdef NITRO_DEBUG
+    if (swiftWrapper == nullptr) [[unlikely]] {
+      throw std::runtime_error("Class \"HybridContactsModuleSpec\" is not implemented in Swift!");
+    }
+  #endif
+    ContactsModule::HybridContactsModuleSpecCxx swiftPart = swiftWrapper->getSwiftPart();
+    return ContactsModule::HybridContactsModuleSpecCxxUnsafe::toUnsafe(swiftPart);
+  }
+
+} // namespace margelo::nitro::contacts::bridge::swift
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.hpp b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.hpp
new file mode 100644
index 000000000000..76d584613df2
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Bridge.hpp
@@ -0,0 +1,167 @@
+///
+/// ContactsModule-Swift-Cxx-Bridge.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+// Forward declarations of C++ defined types
+// Forward declaration of `ContactFields` to properly resolve imports.
+namespace margelo::nitro::contacts { enum class ContactFields; }
+// Forward declaration of `Contact` to properly resolve imports.
+namespace margelo::nitro::contacts { struct Contact; }
+// Forward declaration of `HybridContactsModuleSpec` to properly resolve imports.
+namespace margelo::nitro::contacts { class HybridContactsModuleSpec; }
+// Forward declaration of `StringHolder` to properly resolve imports.
+namespace margelo::nitro::contacts { struct StringHolder; }
+
+// Forward declarations of Swift defined types
+// Forward declaration of `HybridContactsModuleSpecCxx` to properly resolve imports.
+namespace ContactsModule { class HybridContactsModuleSpecCxx; }
+
+// Include C++ defined types
+#include "Contact.hpp"
+#include "ContactFields.hpp"
+#include "HybridContactsModuleSpec.hpp"
+#include "StringHolder.hpp"
+#include <NitroModules/Promise.hpp>
+#include <exception>
+#include <functional>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+/**
+ * Contains specialized versions of C++ templated types so they can be accessed from Swift,
+ * as well as helper functions to interact with those C++ types from Swift.
+ */
+namespace margelo::nitro::contacts::bridge::swift {
+
+  // pragma MARK: std::optional<std::string>
+  /**
+   * Specialized version of `std::optional<std::string>`.
+   */
+  using std__optional_std__string_ = std::optional<std::string>;
+  inline std::optional<std::string> create_std__optional_std__string_(const std::string& value) {
+    return std::optional<std::string>(value);
+  }
+  
+  // pragma MARK: std::vector<StringHolder>
+  /**
+   * Specialized version of `std::vector<StringHolder>`.
+   */
+  using std__vector_StringHolder_ = std::vector<StringHolder>;
+  inline std::vector<StringHolder> create_std__vector_StringHolder_(size_t size) {
+    std::vector<StringHolder> vector;
+    vector.reserve(size);
+    return vector;
+  }
+  
+  // pragma MARK: std::optional<std::vector<StringHolder>>
+  /**
+   * Specialized version of `std::optional<std::vector<StringHolder>>`.
+   */
+  using std__optional_std__vector_StringHolder__ = std::optional<std::vector<StringHolder>>;
+  inline std::optional<std::vector<StringHolder>> create_std__optional_std__vector_StringHolder__(const std::vector<StringHolder>& value) {
+    return std::optional<std::vector<StringHolder>>(value);
+  }
+  
+  // pragma MARK: std::vector<Contact>
+  /**
+   * Specialized version of `std::vector<Contact>`.
+   */
+  using std__vector_Contact_ = std::vector<Contact>;
+  inline std::vector<Contact> create_std__vector_Contact_(size_t size) {
+    std::vector<Contact> vector;
+    vector.reserve(size);
+    return vector;
+  }
+  
+  // pragma MARK: std::shared_ptr<Promise<std::vector<Contact>>>
+  /**
+   * Specialized version of `std::shared_ptr<Promise<std::vector<Contact>>>`.
+   */
+  using std__shared_ptr_Promise_std__vector_Contact___ = std::shared_ptr<Promise<std::vector<Contact>>>;
+  inline std::shared_ptr<Promise<std::vector<Contact>>> create_std__shared_ptr_Promise_std__vector_Contact___() {
+    return Promise<std::vector<Contact>>::create();
+  }
+  
+  // pragma MARK: std::function<void(const std::vector<Contact>& /* result */)>
+  /**
+   * Specialized version of `std::function<void(const std::vector<Contact>&)>`.
+   */
+  using Func_void_std__vector_Contact_ = std::function<void(const std::vector<Contact>& /* result */)>;
+  /**
+   * Wrapper class for a `std::function<void(const std::vector<Contact>& / * result * /)>`, this can be used from Swift.
+   */
+  class Func_void_std__vector_Contact__Wrapper final {
+  public:
+    explicit Func_void_std__vector_Contact__Wrapper(const std::function<void(const std::vector<Contact>& /* result */)>& func): _function(func) {}
+    explicit Func_void_std__vector_Contact__Wrapper(std::function<void(const std::vector<Contact>& /* result */)>&& func): _function(std::move(func)) {}
+    inline void call(std::vector<Contact> result) const {
+      _function(result);
+    }
+  private:
+    std::function<void(const std::vector<Contact>& /* result */)> _function;
+  };
+  inline Func_void_std__vector_Contact_ create_Func_void_std__vector_Contact_(void* _Nonnull closureHolder, void(* _Nonnull call)(void* _Nonnull /* closureHolder */, std::vector<Contact>), void(* _Nonnull destroy)(void* _Nonnull)) {
+    std::shared_ptr<void> sharedClosureHolder(closureHolder, destroy);
+    return Func_void_std__vector_Contact_([sharedClosureHolder, call](const std::vector<Contact>& result) -> void {
+      call(sharedClosureHolder.get(), result);
+    });
+  }
+  inline std::shared_ptr<Func_void_std__vector_Contact__Wrapper> share_Func_void_std__vector_Contact_(const Func_void_std__vector_Contact_& value) {
+    return std::make_shared<Func_void_std__vector_Contact__Wrapper>(value);
+  }
+  
+  // pragma MARK: std::function<void(const std::exception_ptr& /* error */)>
+  /**
+   * Specialized version of `std::function<void(const std::exception_ptr&)>`.
+   */
+  using Func_void_std__exception_ptr = std::function<void(const std::exception_ptr& /* error */)>;
+  /**
+   * Wrapper class for a `std::function<void(const std::exception_ptr& / * error * /)>`, this can be used from Swift.
+   */
+  class Func_void_std__exception_ptr_Wrapper final {
+  public:
+    explicit Func_void_std__exception_ptr_Wrapper(const std::function<void(const std::exception_ptr& /* error */)>& func): _function(func) {}
+    explicit Func_void_std__exception_ptr_Wrapper(std::function<void(const std::exception_ptr& /* error */)>&& func): _function(std::move(func)) {}
+    inline void call(std::exception_ptr error) const {
+      _function(error);
+    }
+  private:
+    std::function<void(const std::exception_ptr& /* error */)> _function;
+  };
+  inline Func_void_std__exception_ptr create_Func_void_std__exception_ptr(void* _Nonnull closureHolder, void(* _Nonnull call)(void* _Nonnull /* closureHolder */, std::exception_ptr), void(* _Nonnull destroy)(void* _Nonnull)) {
+    std::shared_ptr<void> sharedClosureHolder(closureHolder, destroy);
+    return Func_void_std__exception_ptr([sharedClosureHolder, call](const std::exception_ptr& error) -> void {
+      call(sharedClosureHolder.get(), error);
+    });
+  }
+  inline std::shared_ptr<Func_void_std__exception_ptr_Wrapper> share_Func_void_std__exception_ptr(const Func_void_std__exception_ptr& value) {
+    return std::make_shared<Func_void_std__exception_ptr_Wrapper>(value);
+  }
+  
+  // pragma MARK: std::vector<ContactFields>
+  /**
+   * Specialized version of `std::vector<ContactFields>`.
+   */
+  using std__vector_ContactFields_ = std::vector<ContactFields>;
+  inline std::vector<ContactFields> create_std__vector_ContactFields_(size_t size) {
+    std::vector<ContactFields> vector;
+    vector.reserve(size);
+    return vector;
+  }
+  
+  // pragma MARK: std::shared_ptr<margelo::nitro::contacts::HybridContactsModuleSpec>
+  /**
+   * Specialized version of `std::shared_ptr<margelo::nitro::contacts::HybridContactsModuleSpec>`.
+   */
+  using std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_ = std::shared_ptr<margelo::nitro::contacts::HybridContactsModuleSpec>;
+  std::shared_ptr<margelo::nitro::contacts::HybridContactsModuleSpec> create_std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_(void* _Nonnull swiftUnsafePointer);
+  void* _Nonnull get_std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_(std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_ cppType);
+
+} // namespace margelo::nitro::contacts::bridge::swift
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Umbrella.hpp b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Umbrella.hpp
new file mode 100644
index 000000000000..6f38d7c7e417
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModule-Swift-Cxx-Umbrella.hpp
@@ -0,0 +1,54 @@
+///
+/// ContactsModule-Swift-Cxx-Umbrella.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+// Forward declarations of C++ defined types
+// Forward declaration of `ContactFields` to properly resolve imports.
+namespace margelo::nitro::contacts { enum class ContactFields; }
+// Forward declaration of `Contact` to properly resolve imports.
+namespace margelo::nitro::contacts { struct Contact; }
+// Forward declaration of `HybridContactsModuleSpec` to properly resolve imports.
+namespace margelo::nitro::contacts { class HybridContactsModuleSpec; }
+// Forward declaration of `StringHolder` to properly resolve imports.
+namespace margelo::nitro::contacts { struct StringHolder; }
+
+// Include C++ defined types
+#include "Contact.hpp"
+#include "ContactFields.hpp"
+#include "HybridContactsModuleSpec.hpp"
+#include "StringHolder.hpp"
+#include <NitroModules/Promise.hpp>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+// C++ helpers for Swift
+#include "ContactsModule-Swift-Cxx-Bridge.hpp"
+
+// Common C++ types used in Swift
+#include <NitroModules/ArrayBufferHolder.hpp>
+#include <NitroModules/AnyMapHolder.hpp>
+#include <NitroModules/HybridContext.hpp>
+#include <NitroModules/RuntimeError.hpp>
+
+// Forward declarations of Swift defined types
+// Forward declaration of `HybridContactsModuleSpecCxx` to properly resolve imports.
+namespace ContactsModule { class HybridContactsModuleSpecCxx; }
+
+// Include Swift defined types
+#if __has_include("ContactsModule-Swift.h")
+// This header is generated by Xcode/Swift on every app build.
+// If it cannot be found, make sure the Swift module's name (= podspec name) is actually "ContactsModule".
+#include "ContactsModule-Swift.h"
+// Same as above, but used when building with frameworks (`use_frameworks`)
+#elif __has_include(<ContactsModule/ContactsModule-Swift.h>)
+#include <ContactsModule/ContactsModule-Swift.h>
+#else
+#error ContactsModule's autogenerated Swift header cannot be found! Make sure the Swift module's name (= podspec name) is actually "ContactsModule", and try building the app first.
+#endif
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModuleAutolinking.mm b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModuleAutolinking.mm
new file mode 100644
index 000000000000..e769fb6a6806
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModuleAutolinking.mm
@@ -0,0 +1,33 @@
+///
+/// ContactsModuleAutolinking.mm
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#import <Foundation/Foundation.h>
+#import <NitroModules/HybridObjectRegistry.hpp>
+#import "ContactsModule-Swift-Cxx-Umbrella.hpp"
+#import <type_traits>
+
+#include "HybridContactsModuleSpecSwift.hpp"
+
+@interface ContactsModuleAutolinking : NSObject
+@end
+
+@implementation ContactsModuleAutolinking
+
++ (void) load {
+  using namespace margelo::nitro;
+  using namespace margelo::nitro::contacts;
+
+  HybridObjectRegistry::registerHybridObjectConstructor(
+    "ContactsModule",
+    []() -> std::shared_ptr<HybridObject> {
+      std::shared_ptr<margelo::nitro::contacts::HybridContactsModuleSpec> hybridObject = ContactsModule::ContactsModuleAutolinking::createContactsModule();
+      return hybridObject;
+    }
+  );
+}
+
+@end
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModuleAutolinking.swift b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModuleAutolinking.swift
new file mode 100644
index 000000000000..15d9d9b9064e
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/ContactsModuleAutolinking.swift
@@ -0,0 +1,26 @@
+///
+/// ContactsModuleAutolinking.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+public final class ContactsModuleAutolinking {
+  public typealias bridge = margelo.nitro.contacts.bridge.swift
+
+  /**
+   * Creates an instance of a Swift class that implements `HybridContactsModuleSpec`,
+   * and wraps it in a Swift class that can directly interop with C++ (`HybridContactsModuleSpecCxx`)
+   *
+   * This is generated by Nitrogen and will initialize the class specified
+   * in the `"autolinking"` property of `nitro.json` (in this case, `HybridContactsModule`).
+   */
+  public static func createContactsModule() -> bridge.std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_ {
+    let hybridObject = HybridContactsModule()
+    return { () -> bridge.std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_ in
+      let __cxxWrapped = HybridContactsModuleSpecCxx(hybridObject)
+      let __pointer = HybridContactsModuleSpecCxxUnsafe.toUnsafe(__cxxWrapped)
+      return bridge.create_std__shared_ptr_margelo__nitro__contacts__HybridContactsModuleSpec_(__pointer)
+    }()
+  }
+}
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/c++/HybridContactsModuleSpecSwift.cpp b/modules/ContactsNitroModule/nitrogen/generated/ios/c++/HybridContactsModuleSpecSwift.cpp
new file mode 100644
index 000000000000..71151f3c1883
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/c++/HybridContactsModuleSpecSwift.cpp
@@ -0,0 +1,11 @@
+///
+/// HybridContactsModuleSpecSwift.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#include "HybridContactsModuleSpecSwift.hpp"
+
+namespace margelo::nitro::contacts {
+} // namespace margelo::nitro::contacts
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/c++/HybridContactsModuleSpecSwift.hpp b/modules/ContactsNitroModule/nitrogen/generated/ios/c++/HybridContactsModuleSpecSwift.hpp
new file mode 100644
index 000000000000..dbb4fe829dc2
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/c++/HybridContactsModuleSpecSwift.hpp
@@ -0,0 +1,82 @@
+///
+/// HybridContactsModuleSpecSwift.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#include "HybridContactsModuleSpec.hpp"
+
+// Forward declaration of `HybridContactsModuleSpecCxx` to properly resolve imports.
+namespace ContactsModule { class HybridContactsModuleSpecCxx; }
+
+// Forward declaration of `Contact` to properly resolve imports.
+namespace margelo::nitro::contacts { struct Contact; }
+// Forward declaration of `StringHolder` to properly resolve imports.
+namespace margelo::nitro::contacts { struct StringHolder; }
+// Forward declaration of `ContactFields` to properly resolve imports.
+namespace margelo::nitro::contacts { enum class ContactFields; }
+
+#include <NitroModules/Promise.hpp>
+#include <vector>
+#include "Contact.hpp"
+#include <optional>
+#include <string>
+#include "StringHolder.hpp"
+#include "ContactFields.hpp"
+
+#if __has_include(<NitroModules/HybridContext.hpp>)
+#include <NitroModules/HybridContext.hpp>
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+
+#include "ContactsModule-Swift-Cxx-Umbrella.hpp"
+
+namespace margelo::nitro::contacts {
+
+  /**
+   * The C++ part of HybridContactsModuleSpecCxx.swift.
+   *
+   * HybridContactsModuleSpecSwift (C++) accesses HybridContactsModuleSpecCxx (Swift), and might
+   * contain some additional bridging code for C++ <> Swift interop.
+   *
+   * Since this obviously introduces an overhead, I hope at some point in
+   * the future, HybridContactsModuleSpecCxx can directly inherit from the C++ class HybridContactsModuleSpec
+   * to simplify the whole structure and memory management.
+   */
+  class HybridContactsModuleSpecSwift: public virtual HybridContactsModuleSpec {
+  public:
+    // Constructor from a Swift instance
+    explicit HybridContactsModuleSpecSwift(const ContactsModule::HybridContactsModuleSpecCxx& swiftPart):
+      HybridObject(HybridContactsModuleSpec::TAG),
+      _swiftPart(swiftPart) { }
+
+  public:
+    // Get the Swift part
+    inline ContactsModule::HybridContactsModuleSpecCxx getSwiftPart() noexcept { return _swiftPart; }
+
+  public:
+    // Get memory pressure
+    inline size_t getExternalMemorySize() noexcept override {
+      return _swiftPart.getMemorySize();
+    }
+
+  public:
+    // Properties
+    
+
+  public:
+    // Methods
+    inline std::shared_ptr<Promise<std::vector<Contact>>> getAll(const std::vector<ContactFields>& keys) override {
+      auto __result = _swiftPart.getAll(keys);
+      return __result;
+    }
+
+  private:
+    ContactsModule::HybridContactsModuleSpecCxx _swiftPart;
+  };
+
+} // namespace margelo::nitro::contacts
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/Contact.swift b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/Contact.swift
new file mode 100644
index 000000000000..404d6ba86b25
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/Contact.swift
@@ -0,0 +1,251 @@
+///
+/// Contact.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+import NitroModules
+
+/**
+ * Represents an instance of `Contact`, backed by a C++ struct.
+ */
+public typealias Contact = margelo.nitro.contacts.Contact
+
+public extension Contact {
+  private typealias bridge = margelo.nitro.contacts.bridge.swift
+
+  /**
+   * Create a new instance of `Contact`.
+   */
+  init(firstName: String?, lastName: String?, middleName: String?, phoneNumbers: [StringHolder]?, emailAddresses: [StringHolder]?, imageData: String?, thumbnailImageData: String?) {
+    self.init({ () -> bridge.std__optional_std__string_ in
+      if let __unwrappedValue = firstName {
+        return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
+      } else {
+        return .init()
+      }
+    }(), { () -> bridge.std__optional_std__string_ in
+      if let __unwrappedValue = lastName {
+        return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
+      } else {
+        return .init()
+      }
+    }(), { () -> bridge.std__optional_std__string_ in
+      if let __unwrappedValue = middleName {
+        return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
+      } else {
+        return .init()
+      }
+    }(), { () -> bridge.std__optional_std__vector_StringHolder__ in
+      if let __unwrappedValue = phoneNumbers {
+        return bridge.create_std__optional_std__vector_StringHolder__({ () -> bridge.std__vector_StringHolder_ in
+          var __vector = bridge.create_std__vector_StringHolder_(__unwrappedValue.count)
+          for __item in __unwrappedValue {
+            __vector.push_back(__item)
+          }
+          return __vector
+        }())
+      } else {
+        return .init()
+      }
+    }(), { () -> bridge.std__optional_std__vector_StringHolder__ in
+      if let __unwrappedValue = emailAddresses {
+        return bridge.create_std__optional_std__vector_StringHolder__({ () -> bridge.std__vector_StringHolder_ in
+          var __vector = bridge.create_std__vector_StringHolder_(__unwrappedValue.count)
+          for __item in __unwrappedValue {
+            __vector.push_back(__item)
+          }
+          return __vector
+        }())
+      } else {
+        return .init()
+      }
+    }(), { () -> bridge.std__optional_std__string_ in
+      if let __unwrappedValue = imageData {
+        return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
+      } else {
+        return .init()
+      }
+    }(), { () -> bridge.std__optional_std__string_ in
+      if let __unwrappedValue = thumbnailImageData {
+        return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
+      } else {
+        return .init()
+      }
+    }())
+  }
+
+  var firstName: String? {
+    @inline(__always)
+    get {
+      return { () -> String? in
+        if let __unwrapped = self.__firstName.value {
+          return String(__unwrapped)
+        } else {
+          return nil
+        }
+      }()
+    }
+    @inline(__always)
+    set {
+      self.__firstName = { () -> bridge.std__optional_std__string_ in
+        if let __unwrappedValue = newValue {
+          return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
+        } else {
+          return .init()
+        }
+      }()
+    }
+  }
+  
+  var lastName: String? {
+    @inline(__always)
+    get {
+      return { () -> String? in
+        if let __unwrapped = self.__lastName.value {
+          return String(__unwrapped)
+        } else {
+          return nil
+        }
+      }()
+    }
+    @inline(__always)
+    set {
+      self.__lastName = { () -> bridge.std__optional_std__string_ in
+        if let __unwrappedValue = newValue {
+          return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
+        } else {
+          return .init()
+        }
+      }()
+    }
+  }
+  
+  var middleName: String? {
+    @inline(__always)
+    get {
+      return { () -> String? in
+        if let __unwrapped = self.__middleName.value {
+          return String(__unwrapped)
+        } else {
+          return nil
+        }
+      }()
+    }
+    @inline(__always)
+    set {
+      self.__middleName = { () -> bridge.std__optional_std__string_ in
+        if let __unwrappedValue = newValue {
+          return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
+        } else {
+          return .init()
+        }
+      }()
+    }
+  }
+  
+  var phoneNumbers: [StringHolder]? {
+    @inline(__always)
+    get {
+      return { () -> [StringHolder]? in
+        if let __unwrapped = self.__phoneNumbers.value {
+          return __unwrapped.map({ __item in __item })
+        } else {
+          return nil
+        }
+      }()
+    }
+    @inline(__always)
+    set {
+      self.__phoneNumbers = { () -> bridge.std__optional_std__vector_StringHolder__ in
+        if let __unwrappedValue = newValue {
+          return bridge.create_std__optional_std__vector_StringHolder__({ () -> bridge.std__vector_StringHolder_ in
+            var __vector = bridge.create_std__vector_StringHolder_(__unwrappedValue.count)
+            for __item in __unwrappedValue {
+              __vector.push_back(__item)
+            }
+            return __vector
+          }())
+        } else {
+          return .init()
+        }
+      }()
+    }
+  }
+  
+  var emailAddresses: [StringHolder]? {
+    @inline(__always)
+    get {
+      return { () -> [StringHolder]? in
+        if let __unwrapped = self.__emailAddresses.value {
+          return __unwrapped.map({ __item in __item })
+        } else {
+          return nil
+        }
+      }()
+    }
+    @inline(__always)
+    set {
+      self.__emailAddresses = { () -> bridge.std__optional_std__vector_StringHolder__ in
+        if let __unwrappedValue = newValue {
+          return bridge.create_std__optional_std__vector_StringHolder__({ () -> bridge.std__vector_StringHolder_ in
+            var __vector = bridge.create_std__vector_StringHolder_(__unwrappedValue.count)
+            for __item in __unwrappedValue {
+              __vector.push_back(__item)
+            }
+            return __vector
+          }())
+        } else {
+          return .init()
+        }
+      }()
+    }
+  }
+  
+  var imageData: String? {
+    @inline(__always)
+    get {
+      return { () -> String? in
+        if let __unwrapped = self.__imageData.value {
+          return String(__unwrapped)
+        } else {
+          return nil
+        }
+      }()
+    }
+    @inline(__always)
+    set {
+      self.__imageData = { () -> bridge.std__optional_std__string_ in
+        if let __unwrappedValue = newValue {
+          return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
+        } else {
+          return .init()
+        }
+      }()
+    }
+  }
+  
+  var thumbnailImageData: String? {
+    @inline(__always)
+    get {
+      return { () -> String? in
+        if let __unwrapped = self.__thumbnailImageData.value {
+          return String(__unwrapped)
+        } else {
+          return nil
+        }
+      }()
+    }
+    @inline(__always)
+    set {
+      self.__thumbnailImageData = { () -> bridge.std__optional_std__string_ in
+        if let __unwrappedValue = newValue {
+          return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
+        } else {
+          return .init()
+        }
+      }()
+    }
+  }
+}
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/ContactFields.swift b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/ContactFields.swift
new file mode 100644
index 000000000000..ce38940795d9
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/ContactFields.swift
@@ -0,0 +1,64 @@
+///
+/// ContactFields.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+/**
+ * Represents the JS union `ContactFields`, backed by a C++ enum.
+ */
+public typealias ContactFields = margelo.nitro.contacts.ContactFields
+
+public extension ContactFields {
+  /**
+   * Get a ContactFields for the given String value, or
+   * return `nil` if the given value was invalid/unknown.
+   */
+  init?(fromString string: String) {
+    switch string {
+      case "FIRST_NAME":
+        self = .firstName
+      case "LAST_NAME":
+        self = .lastName
+      case "MIDDLE_NAME":
+        self = .middleName
+      case "PHONE_NUMBERS":
+        self = .phoneNumbers
+      case "EMAIL_ADDRESSES":
+        self = .emailAddresses
+      case "IMAGE_DATA":
+        self = .imageData
+      case "THUMBNAIL_IMAGE_DATA":
+        self = .thumbnailImageData
+      case "GIVEN_NAME_KEY":
+        self = .givenNameKey
+      default:
+        return nil
+    }
+  }
+
+  /**
+   * Get the String value this ContactFields represents.
+   */
+  var stringValue: String {
+    switch self {
+      case .firstName:
+        return "FIRST_NAME"
+      case .lastName:
+        return "LAST_NAME"
+      case .middleName:
+        return "MIDDLE_NAME"
+      case .phoneNumbers:
+        return "PHONE_NUMBERS"
+      case .emailAddresses:
+        return "EMAIL_ADDRESSES"
+      case .imageData:
+        return "IMAGE_DATA"
+      case .thumbnailImageData:
+        return "THUMBNAIL_IMAGE_DATA"
+      case .givenNameKey:
+        return "GIVEN_NAME_KEY"
+    }
+  }
+}
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/HybridContactsModuleSpec.swift b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/HybridContactsModuleSpec.swift
new file mode 100644
index 000000000000..611110efca1d
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/HybridContactsModuleSpec.swift
@@ -0,0 +1,36 @@
+///
+/// HybridContactsModuleSpec.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+import Foundation
+import NitroModules
+
+/**
+ * A Swift protocol representing the ContactsModule HybridObject.
+ * Implement this protocol to create Swift-based instances of ContactsModule.
+ *
+ * When implementing this protocol, make sure to initialize `hybridContext` - example:
+ * ```
+ * public class HybridContactsModule : HybridContactsModuleSpec {
+ *   // Initialize HybridContext
+ *   var hybridContext = margelo.nitro.HybridContext()
+ *
+ *   // Return size of the instance to inform JS GC about memory pressure
+ *   var memorySize: Int {
+ *     return getSizeOf(self)
+ *   }
+ *
+ *   // ...
+ * }
+ * ```
+ */
+public protocol HybridContactsModuleSpec: AnyObject, HybridObjectSpec {
+  // Properties
+  
+
+  // Methods
+  func getAll(keys: [ContactFields]) throws -> Promise<[Contact]>
+}
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/HybridContactsModuleSpecCxx.swift b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/HybridContactsModuleSpecCxx.swift
new file mode 100644
index 000000000000..156cdf86bd7f
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/HybridContactsModuleSpecCxx.swift
@@ -0,0 +1,123 @@
+///
+/// HybridContactsModuleSpecCxx.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+import Foundation
+import NitroModules
+
+/**
+ * Helper class for converting instances of `HybridContactsModuleSpecCxx` from- and to unsafe pointers.
+ * This is useful to pass Swift classes to C++, without having to strongly type the C++ function signature.
+ * The actual Swift type can be included in the .cpp file, without having to forward-declare anything in .hpp.
+ */
+public final class HybridContactsModuleSpecCxxUnsafe {
+  /**
+   * Casts a `HybridContactsModuleSpecCxx` instance to a retained unsafe raw pointer.
+   * This acquires one additional strong reference on the object!
+   */
+  public static func toUnsafe(_ instance: HybridContactsModuleSpecCxx) -> UnsafeMutableRawPointer {
+    return Unmanaged.passRetained(instance).toOpaque()
+  }
+
+  /**
+   * Casts an unsafe pointer to a `HybridContactsModuleSpecCxx`.
+   * The pointer has to be a retained opaque `Unmanaged<HybridContactsModuleSpecCxx>`.
+   * This removes one strong reference from the object!
+   */
+  public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> HybridContactsModuleSpecCxx {
+    return Unmanaged<HybridContactsModuleSpecCxx>.fromOpaque(pointer).takeRetainedValue()
+  }
+}
+
+/**
+ * A class implementation that bridges HybridContactsModuleSpec over to C++.
+ * In C++, we cannot use Swift protocols - so we need to wrap it in a class to make it strongly defined.
+ *
+ * Also, some Swift types need to be bridged with special handling:
+ * - Enums need to be wrapped in Structs, otherwise they cannot be accessed bi-directionally (Swift bug: https://github.com/swiftlang/swift/issues/75330)
+ * - Other HybridObjects need to be wrapped/unwrapped from the Swift TCxx wrapper
+ * - Throwing methods need to be wrapped with a Result<T, Error> type, as exceptions cannot be propagated to C++
+ */
+public class HybridContactsModuleSpecCxx {
+  /**
+   * The Swift <> C++ bridge's namespace (`margelo::nitro::contacts::bridge::swift`)
+   * from `ContactsModule-Swift-Cxx-Bridge.hpp`.
+   * This contains specialized C++ templates, and C++ helper functions that can be accessed from Swift.
+   */
+  public typealias bridge = margelo.nitro.contacts.bridge.swift
+
+  /**
+   * Holds an instance of the `HybridContactsModuleSpec` Swift protocol.
+   */
+  private var __implementation: any HybridContactsModuleSpec
+
+  /**
+   * Create a new `HybridContactsModuleSpecCxx` that wraps the given `HybridContactsModuleSpec`.
+   * All properties and methods bridge to C++ types.
+   */
+  public init(_ implementation: some HybridContactsModuleSpec) {
+    self.__implementation = implementation
+    /* no base class */
+  }
+
+  /**
+   * Get the actual `HybridContactsModuleSpec` instance this class wraps.
+   */
+  @inline(__always)
+  public func getHybridContactsModuleSpec() -> any HybridContactsModuleSpec {
+    return __implementation
+  }
+
+  /**
+   * Contains a (weak) reference to the C++ HybridObject to cache it.
+   */
+  public var hybridContext: margelo.nitro.HybridContext {
+    @inline(__always)
+    get {
+      return self.__implementation.hybridContext
+    }
+    @inline(__always)
+    set {
+      self.__implementation.hybridContext = newValue
+    }
+  }
+
+  /**
+   * Get the memory size of the Swift class (plus size of any other allocations)
+   * so the JS VM can properly track it and garbage-collect the JS object if needed.
+   */
+  @inline(__always)
+  public var memorySize: Int {
+    return self.__implementation.memorySize
+  }
+
+  // Properties
+  
+
+  // Methods
+  @inline(__always)
+  public func getAll(keys: bridge.std__vector_ContactFields_) -> bridge.std__shared_ptr_Promise_std__vector_Contact___ {
+    do {
+      let __result = try self.__implementation.getAll(keys: keys.map({ __item in __item }))
+      return { () -> bridge.std__shared_ptr_Promise_std__vector_Contact___ in
+        let __promise = bridge.create_std__shared_ptr_Promise_std__vector_Contact___()
+        __result
+          .then({ __result in __promise.pointee.resolve({ () -> bridge.std__vector_Contact_ in
+        var __vector = bridge.create_std__vector_Contact_(__result.count)
+        for __item in __result {
+          __vector.push_back(__item)
+        }
+        return __vector
+      }()) })
+          .catch({ __error in __promise.pointee.reject(__error.toCpp()) })
+        return __promise
+      }()
+    } catch {
+      let __message = "\(error.localizedDescription)"
+      fatalError("Swift errors can currently not be propagated to C++! See https://github.com/swiftlang/swift/issues/75290 (Error: \(__message))")
+    }
+  }
+}
diff --git a/modules/ContactsNitroModule/nitrogen/generated/ios/swift/StringHolder.swift b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/StringHolder.swift
new file mode 100644
index 000000000000..477279082456
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/ios/swift/StringHolder.swift
@@ -0,0 +1,35 @@
+///
+/// StringHolder.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+import NitroModules
+
+/**
+ * Represents an instance of `StringHolder`, backed by a C++ struct.
+ */
+public typealias StringHolder = margelo.nitro.contacts.StringHolder
+
+public extension StringHolder {
+  private typealias bridge = margelo.nitro.contacts.bridge.swift
+
+  /**
+   * Create a new instance of `StringHolder`.
+   */
+  init(value: String) {
+    self.init(std.string(value))
+  }
+
+  var value: String {
+    @inline(__always)
+    get {
+      return String(self.__value)
+    }
+    @inline(__always)
+    set {
+      self.__value = std.string(newValue)
+    }
+  }
+}
diff --git a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/Contact.hpp b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/Contact.hpp
new file mode 100644
index 000000000000..6e4a5bd0c27e
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/Contact.hpp
@@ -0,0 +1,96 @@
+///
+/// Contact.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#if __has_include(<NitroModules/JSIConverter.hpp>)
+#include <NitroModules/JSIConverter.hpp>
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+#if __has_include(<NitroModules/NitroDefines.hpp>)
+#include <NitroModules/NitroDefines.hpp>
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+
+// Forward declaration of `StringHolder` to properly resolve imports.
+namespace margelo::nitro::contacts { struct StringHolder; }
+
+#include <optional>
+#include <string>
+#include <vector>
+#include "StringHolder.hpp"
+
+namespace margelo::nitro::contacts {
+
+  /**
+   * A struct which can be represented as a JavaScript object (Contact).
+   */
+  struct Contact {
+  public:
+    std::optional<std::string> firstName     SWIFT_PRIVATE;
+    std::optional<std::string> lastName     SWIFT_PRIVATE;
+    std::optional<std::string> middleName     SWIFT_PRIVATE;
+    std::optional<std::vector<StringHolder>> phoneNumbers     SWIFT_PRIVATE;
+    std::optional<std::vector<StringHolder>> emailAddresses     SWIFT_PRIVATE;
+    std::optional<std::string> imageData     SWIFT_PRIVATE;
+    std::optional<std::string> thumbnailImageData     SWIFT_PRIVATE;
+
+  public:
+    explicit Contact(std::optional<std::string> firstName, std::optional<std::string> lastName, std::optional<std::string> middleName, std::optional<std::vector<StringHolder>> phoneNumbers, std::optional<std::vector<StringHolder>> emailAddresses, std::optional<std::string> imageData, std::optional<std::string> thumbnailImageData): firstName(firstName), lastName(lastName), middleName(middleName), phoneNumbers(phoneNumbers), emailAddresses(emailAddresses), imageData(imageData), thumbnailImageData(thumbnailImageData) {}
+  };
+
+} // namespace margelo::nitro::contacts
+
+namespace margelo::nitro {
+
+  using namespace margelo::nitro::contacts;
+
+  // C++ Contact <> JS Contact (object)
+  template <>
+  struct JSIConverter<Contact> {
+    static inline Contact fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
+      jsi::Object obj = arg.asObject(runtime);
+      return Contact(
+        JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "firstName")),
+        JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "lastName")),
+        JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "middleName")),
+        JSIConverter<std::optional<std::vector<StringHolder>>>::fromJSI(runtime, obj.getProperty(runtime, "phoneNumbers")),
+        JSIConverter<std::optional<std::vector<StringHolder>>>::fromJSI(runtime, obj.getProperty(runtime, "emailAddresses")),
+        JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "imageData")),
+        JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "thumbnailImageData"))
+      );
+    }
+    static inline jsi::Value toJSI(jsi::Runtime& runtime, const Contact& arg) {
+      jsi::Object obj(runtime);
+      obj.setProperty(runtime, "firstName", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.firstName));
+      obj.setProperty(runtime, "lastName", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.lastName));
+      obj.setProperty(runtime, "middleName", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.middleName));
+      obj.setProperty(runtime, "phoneNumbers", JSIConverter<std::optional<std::vector<StringHolder>>>::toJSI(runtime, arg.phoneNumbers));
+      obj.setProperty(runtime, "emailAddresses", JSIConverter<std::optional<std::vector<StringHolder>>>::toJSI(runtime, arg.emailAddresses));
+      obj.setProperty(runtime, "imageData", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.imageData));
+      obj.setProperty(runtime, "thumbnailImageData", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.thumbnailImageData));
+      return obj;
+    }
+    static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
+      if (!value.isObject()) {
+        return false;
+      }
+      jsi::Object obj = value.getObject(runtime);
+      if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "firstName"))) return false;
+      if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "lastName"))) return false;
+      if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "middleName"))) return false;
+      if (!JSIConverter<std::optional<std::vector<StringHolder>>>::canConvert(runtime, obj.getProperty(runtime, "phoneNumbers"))) return false;
+      if (!JSIConverter<std::optional<std::vector<StringHolder>>>::canConvert(runtime, obj.getProperty(runtime, "emailAddresses"))) return false;
+      if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "imageData"))) return false;
+      if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "thumbnailImageData"))) return false;
+      return true;
+    }
+  };
+
+} // namespace margelo::nitro
diff --git a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/ContactFields.hpp b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/ContactFields.hpp
new file mode 100644
index 000000000000..c3e8c115465e
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/ContactFields.hpp
@@ -0,0 +1,102 @@
+///
+/// ContactFields.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#if __has_include(<NitroModules/NitroHash.hpp>)
+#include <NitroModules/NitroHash.hpp>
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+#if __has_include(<NitroModules/JSIConverter.hpp>)
+#include <NitroModules/JSIConverter.hpp>
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+#if __has_include(<NitroModules/NitroDefines.hpp>)
+#include <NitroModules/NitroDefines.hpp>
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+
+namespace margelo::nitro::contacts {
+
+  /**
+   * An enum which can be represented as a JavaScript union (ContactFields).
+   */
+  enum class ContactFields {
+    FIRST_NAME      SWIFT_NAME(firstName) = 0,
+    LAST_NAME      SWIFT_NAME(lastName) = 1,
+    MIDDLE_NAME      SWIFT_NAME(middleName) = 2,
+    PHONE_NUMBERS      SWIFT_NAME(phoneNumbers) = 3,
+    EMAIL_ADDRESSES      SWIFT_NAME(emailAddresses) = 4,
+    IMAGE_DATA      SWIFT_NAME(imageData) = 5,
+    THUMBNAIL_IMAGE_DATA      SWIFT_NAME(thumbnailImageData) = 6,
+    GIVEN_NAME_KEY      SWIFT_NAME(givenNameKey) = 7,
+  } CLOSED_ENUM;
+
+} // namespace margelo::nitro::contacts
+
+namespace margelo::nitro {
+
+  using namespace margelo::nitro::contacts;
+
+  // C++ ContactFields <> JS ContactFields (union)
+  template <>
+  struct JSIConverter<ContactFields> {
+    static inline ContactFields fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
+      std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, arg);
+      switch (hashString(unionValue.c_str(), unionValue.size())) {
+        case hashString("FIRST_NAME"): return ContactFields::FIRST_NAME;
+        case hashString("LAST_NAME"): return ContactFields::LAST_NAME;
+        case hashString("MIDDLE_NAME"): return ContactFields::MIDDLE_NAME;
+        case hashString("PHONE_NUMBERS"): return ContactFields::PHONE_NUMBERS;
+        case hashString("EMAIL_ADDRESSES"): return ContactFields::EMAIL_ADDRESSES;
+        case hashString("IMAGE_DATA"): return ContactFields::IMAGE_DATA;
+        case hashString("THUMBNAIL_IMAGE_DATA"): return ContactFields::THUMBNAIL_IMAGE_DATA;
+        case hashString("GIVEN_NAME_KEY"): return ContactFields::GIVEN_NAME_KEY;
+        default: [[unlikely]]
+          throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum ContactFields - invalid value!");
+      }
+    }
+    static inline jsi::Value toJSI(jsi::Runtime& runtime, ContactFields arg) {
+      switch (arg) {
+        case ContactFields::FIRST_NAME: return JSIConverter<std::string>::toJSI(runtime, "FIRST_NAME");
+        case ContactFields::LAST_NAME: return JSIConverter<std::string>::toJSI(runtime, "LAST_NAME");
+        case ContactFields::MIDDLE_NAME: return JSIConverter<std::string>::toJSI(runtime, "MIDDLE_NAME");
+        case ContactFields::PHONE_NUMBERS: return JSIConverter<std::string>::toJSI(runtime, "PHONE_NUMBERS");
+        case ContactFields::EMAIL_ADDRESSES: return JSIConverter<std::string>::toJSI(runtime, "EMAIL_ADDRESSES");
+        case ContactFields::IMAGE_DATA: return JSIConverter<std::string>::toJSI(runtime, "IMAGE_DATA");
+        case ContactFields::THUMBNAIL_IMAGE_DATA: return JSIConverter<std::string>::toJSI(runtime, "THUMBNAIL_IMAGE_DATA");
+        case ContactFields::GIVEN_NAME_KEY: return JSIConverter<std::string>::toJSI(runtime, "GIVEN_NAME_KEY");
+        default: [[unlikely]]
+          throw std::invalid_argument("Cannot convert ContactFields to JS - invalid value: "
+                                    + std::to_string(static_cast<int>(arg)) + "!");
+      }
+    }
+    static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
+      if (!value.isString()) {
+        return false;
+      }
+      std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, value);
+      switch (hashString(unionValue.c_str(), unionValue.size())) {
+        case hashString("FIRST_NAME"):
+        case hashString("LAST_NAME"):
+        case hashString("MIDDLE_NAME"):
+        case hashString("PHONE_NUMBERS"):
+        case hashString("EMAIL_ADDRESSES"):
+        case hashString("IMAGE_DATA"):
+        case hashString("THUMBNAIL_IMAGE_DATA"):
+        case hashString("GIVEN_NAME_KEY"):
+          return true;
+        default:
+          return false;
+      }
+    }
+  };
+
+} // namespace margelo::nitro
diff --git a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/HybridContactsModuleSpec.cpp b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/HybridContactsModuleSpec.cpp
new file mode 100644
index 000000000000..eba17de8d910
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/HybridContactsModuleSpec.cpp
@@ -0,0 +1,21 @@
+///
+/// HybridContactsModuleSpec.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#include "HybridContactsModuleSpec.hpp"
+
+namespace margelo::nitro::contacts {
+
+  void HybridContactsModuleSpec::loadHybridMethods() {
+    // load base methods/properties
+    HybridObject::loadHybridMethods();
+    // load custom methods/properties
+    registerHybrids(this, [](Prototype& prototype) {
+      prototype.registerHybridMethod("getAll", &HybridContactsModuleSpec::getAll);
+    });
+  }
+
+} // namespace margelo::nitro::contacts
diff --git a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/HybridContactsModuleSpec.hpp b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/HybridContactsModuleSpec.hpp
new file mode 100644
index 000000000000..6c298086f493
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/HybridContactsModuleSpec.hpp
@@ -0,0 +1,68 @@
+///
+/// HybridContactsModuleSpec.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#if __has_include(<NitroModules/HybridObject.hpp>)
+#include <NitroModules/HybridObject.hpp>
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+
+// Forward declaration of `Contact` to properly resolve imports.
+namespace margelo::nitro::contacts { struct Contact; }
+// Forward declaration of `ContactFields` to properly resolve imports.
+namespace margelo::nitro::contacts { enum class ContactFields; }
+
+#include <NitroModules/Promise.hpp>
+#include <vector>
+#include "Contact.hpp"
+#include "ContactFields.hpp"
+
+namespace margelo::nitro::contacts {
+
+  using namespace margelo::nitro;
+
+  /**
+   * An abstract base class for `ContactsModule`
+   * Inherit this class to create instances of `HybridContactsModuleSpec` in C++.
+   * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual.
+   * @example
+   * ```cpp
+   * class HybridContactsModule: public HybridContactsModuleSpec {
+   * public:
+   *   HybridContactsModule(...): HybridObject(TAG) { ... }
+   *   // ...
+   * };
+   * ```
+   */
+  class HybridContactsModuleSpec: public virtual HybridObject {
+    public:
+      // Constructor
+      explicit HybridContactsModuleSpec(): HybridObject(TAG) { }
+
+      // Destructor
+      virtual ~HybridContactsModuleSpec() { }
+
+    public:
+      // Properties
+      
+
+    public:
+      // Methods
+      virtual std::shared_ptr<Promise<std::vector<Contact>>> getAll(const std::vector<ContactFields>& keys) = 0;
+
+    protected:
+      // Hybrid Setup
+      void loadHybridMethods() override;
+
+    protected:
+      // Tag for logging
+      static constexpr auto TAG = "ContactsModule";
+  };
+
+} // namespace margelo::nitro::contacts
diff --git a/modules/ContactsNitroModule/nitrogen/generated/shared/c++/StringHolder.hpp b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/StringHolder.hpp
new file mode 100644
index 000000000000..1a666ed1faca
--- /dev/null
+++ b/modules/ContactsNitroModule/nitrogen/generated/shared/c++/StringHolder.hpp
@@ -0,0 +1,68 @@
+///
+/// StringHolder.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © 2024 Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#if __has_include(<NitroModules/JSIConverter.hpp>)
+#include <NitroModules/JSIConverter.hpp>
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+#if __has_include(<NitroModules/NitroDefines.hpp>)
+#include <NitroModules/NitroDefines.hpp>
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+
+
+
+#include <string>
+
+namespace margelo::nitro::contacts {
+
+  /**
+   * A struct which can be represented as a JavaScript object (StringHolder).
+   */
+  struct StringHolder {
+  public:
+    std::string value     SWIFT_PRIVATE;
+
+  public:
+    explicit StringHolder(std::string value): value(value) {}
+  };
+
+} // namespace margelo::nitro::contacts
+
+namespace margelo::nitro {
+
+  using namespace margelo::nitro::contacts;
+
+  // C++ StringHolder <> JS StringHolder (object)
+  template <>
+  struct JSIConverter<StringHolder> {
+    static inline StringHolder fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
+      jsi::Object obj = arg.asObject(runtime);
+      return StringHolder(
+        JSIConverter<std::string>::fromJSI(runtime, obj.getProperty(runtime, "value"))
+      );
+    }
+    static inline jsi::Value toJSI(jsi::Runtime& runtime, const StringHolder& arg) {
+      jsi::Object obj(runtime);
+      obj.setProperty(runtime, "value", JSIConverter<std::string>::toJSI(runtime, arg.value));
+      return obj;
+    }
+    static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
+      if (!value.isObject()) {
+        return false;
+      }
+      jsi::Object obj = value.getObject(runtime);
+      if (!JSIConverter<std::string>::canConvert(runtime, obj.getProperty(runtime, "value"))) return false;
+      return true;
+    }
+  };
+
+} // namespace margelo::nitro
diff --git a/modules/ContactsNitroModule/package.json b/modules/ContactsNitroModule/package.json
new file mode 100644
index 000000000000..6f70882a2193
--- /dev/null
+++ b/modules/ContactsNitroModule/package.json
@@ -0,0 +1,103 @@
+{
+  "name": "contacts-nitro-module",
+  "version": "0.0.1",
+  "main": "src/index",
+  "react-native": "src/index",
+  "description": "React Native Contacts Module with Nitro optimization",
+  "source": "src/index",
+  "files": [
+    "src",
+    "react-native.config.js",
+    "lib",
+    "android/build.gradle",
+    "android/gradle.properties",
+    "android/CMakeLists.txt",
+    "android/src",
+    "ios/**/*.h",
+    "ios/**/*.m",
+    "ios/**/*.mm",
+    "ios/**/*.cpp",
+    "ios/**/*.swift",
+    "app.plugin.js",
+    "*.podspec",
+    "README.md"
+  ],
+  "scripts": {
+    "postinstall": "tsc || exit 0;",
+    "typecheck": "tsc --noEmit",
+    "clean": "del-cli android/build node_modules/**/android/build lib",
+    "lint": "eslint \"**/*.{js,ts,tsx}\" --fix",
+    "lint-ci": "eslint \"**/*.{js,ts,tsx}\" -f @jamesacarr/github-actions",
+    "typescript": "tsc --noEmit false",
+    "specs-debug": "bun run --filter=\"**\" typescript && bun nitro-codegen --logLevel=\"debug\"",
+    "specs": "bun nitro-codegen"
+  },
+  "keywords": [
+    "react-native",
+    "nitro"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/mrousavy/nitro.git"
+  },
+  "author": "Marc Rousavy <me@mrousavy.com> (https://github.com/mrousavy)",
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/mrousavy/nitro/issues"
+  },
+  "homepage": "https://github.com/mrousavy/nitro#readme",
+  "publishConfig": {
+    "registry": "https://registry.npmjs.org/"
+  },
+  "devDependencies": {
+    "@react-native/eslint-config": "^0.75.2",
+    "@types/jest": "^29.5.12",
+    "@types/react": "^18.3.4",
+    "del-cli": "^5.1.0",
+    "eslint": "^8.57.0",
+    "eslint-config-prettier": "^9.1.0",
+    "eslint-plugin-prettier": "^5.2.1",
+    "nitro-codegen": "0.18.1",
+    "prettier": "^3.3.3",
+    "react": "^18.3.1",
+    "react-native": "0.75.2",
+    "react-native-nitro-modules": "*",
+    "typescript": "^5.5.4"
+  },
+  "peerDependencies": {
+    "react": "*",
+    "react-native": "*"
+  },
+  "eslintConfig": {
+    "root": true,
+    "extends": [
+      "@react-native",
+      "prettier"
+    ],
+    "plugins": ["prettier"],
+    "rules": {
+      "prettier/prettier": [
+        "warn",
+        {
+          "quoteProps": "consistent",
+          "singleQuote": true,
+          "tabWidth": 2,
+          "trailingComma": "es5",
+          "useTabs": false
+        }
+      ]
+    }
+  },
+  "eslintIgnore": [
+    "node_modules/",
+    "lib/"
+  ],
+  "prettier": {
+    "quoteProps": "consistent",
+    "singleQuote": true,
+    "tabWidth": 2,
+    "trailingComma": "es5",
+    "useTabs": false,
+    "semi": false
+  }
+}
diff --git a/modules/ContactsNitroModule/src/ContactsModule.nitro.ts b/modules/ContactsNitroModule/src/ContactsModule.nitro.ts
new file mode 100644
index 000000000000..8df839f550e8
--- /dev/null
+++ b/modules/ContactsNitroModule/src/ContactsModule.nitro.ts
@@ -0,0 +1,29 @@
+import type { HybridObject } from 'react-native-nitro-modules'
+
+interface StringHolder {
+  value: string
+}
+
+export interface Contact {
+  firstName?: string
+  lastName?: string
+  middleName?: string
+  phoneNumbers?: StringHolder[]
+  emailAddresses?: StringHolder[]
+  imageData?: string
+  thumbnailImageData?: string
+}
+export type ContactFields =
+  | 'FIRST_NAME'
+  | 'LAST_NAME'
+  | 'MIDDLE_NAME'
+  | 'PHONE_NUMBERS'
+  | 'EMAIL_ADDRESSES'
+  | 'IMAGE_DATA'
+  | 'THUMBNAIL_IMAGE_DATA'
+  | 'GIVEN_NAME_KEY'
+
+export interface ContactsModule
+  extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
+  getAll(keys: ContactFields[]): Promise<Contact[]>
+}
diff --git a/modules/ContactsNitroModule/src/index.ts b/modules/ContactsNitroModule/src/index.ts
new file mode 100644
index 000000000000..acc7c7c1fb76
--- /dev/null
+++ b/modules/ContactsNitroModule/src/index.ts
@@ -0,0 +1,8 @@
+import type { ContactsModule } from './ContactsModule.nitro'
+import type { Contact } from './ContactsModule.nitro'
+import { NitroModules } from 'react-native-nitro-modules'
+
+export const ContactsNitroModule =
+  NitroModules.createHybridObject<ContactsModule>('ContactsModule')
+
+export type { Contact }
diff --git a/modules/ContactsNitroModule/tsconfig.json b/modules/ContactsNitroModule/tsconfig.json
new file mode 100644
index 000000000000..e30dc47ac169
--- /dev/null
+++ b/modules/ContactsNitroModule/tsconfig.json
@@ -0,0 +1,29 @@
+{
+  "include": ["src"],
+  "compilerOptions": {
+    "composite": true,
+    "outDir": "lib",
+    "rootDir": "src",
+    "allowUnreachableCode": false,
+    "allowUnusedLabels": false,
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "jsx": "react",
+    "lib": ["esnext"],
+    "module": "esnext",
+    "moduleResolution": "node",
+    "noEmit": false,
+    "noFallthroughCasesInSwitch": true,
+    "noImplicitReturns": true,
+    "noImplicitUseStrict": false,
+    "noStrictGenericChecks": false,
+    "noUncheckedIndexedAccess": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "resolveJsonModule": true,
+    "skipLibCheck": true,
+    "strict": true,
+    "target": "esnext",
+    "verbatimModuleSyntax": true
+  }
+}
diff --git a/package-lock.json b/package-lock.json
index 947e3a4cc1bc..2cc6cda447d9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,9 @@
       "version": "9.0.76-6",
       "hasInstallScript": true,
       "license": "MIT",
+      "workspaces": [
+        "modules/ContactsNitroModule"
+      ],
       "dependencies": {
         "@dotlottie/react-player": "^1.6.3",
         "@expensify/react-native-live-markdown": "0.1.207",
@@ -46,6 +49,7 @@
         "awesome-phonenumber": "^5.4.0",
         "babel-polyfill": "^6.26.0",
         "canvas-size": "^1.2.6",
+        "contacts-nitro-module": "./modules/ContactsNitroModule",
         "core-js": "^3.32.0",
         "date-fns": "^4.1.0",
         "date-fns-tz": "^3.2.0",
@@ -95,6 +99,7 @@
         "react-native-launch-arguments": "^4.0.2",
         "react-native-localize": "^2.2.6",
         "react-native-modal": "^13.0.0",
+        "react-native-nitro-modules": "^0.18.1",
         "react-native-onyx": "2.0.86",
         "react-native-pager-view": "6.5.1",
         "react-native-pdf": "6.7.3",
@@ -275,10 +280,114 @@
         "webpack-dev-server": "^5.0.4",
         "webpack-merge": "^5.8.0",
         "xlsx": "file:vendor/xlsx-0.20.3.tgz"
+      }
+    },
+    "modules/ContactsNitroModule": {
+      "name": "contacts-nitro-module",
+      "version": "0.0.1",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "devDependencies": {
+        "@react-native/eslint-config": "^0.75.2",
+        "@types/jest": "^29.5.12",
+        "@types/react": "^18.3.4",
+        "del-cli": "^5.1.0",
+        "eslint": "^8.57.0",
+        "eslint-config-prettier": "^9.1.0",
+        "eslint-plugin-prettier": "^5.2.1",
+        "nitro-codegen": "0.18.1",
+        "prettier": "^3.3.3",
+        "react": "^18.3.1",
+        "react-native": "0.75.2",
+        "react-native-nitro-modules": "*",
+        "typescript": "^5.5.4"
+      },
+      "peerDependencies": {
+        "react": "*",
+        "react-native": "*"
+      }
+    },
+    "modules/ContactsNitroModule/node_modules/@types/jest": {
+      "version": "29.5.14",
+      "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz",
+      "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "expect": "^29.0.0",
+        "pretty-format": "^29.0.0"
+      }
+    },
+    "modules/ContactsNitroModule/node_modules/@types/react": {
+      "version": "18.3.17",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.17.tgz",
+      "integrity": "sha512-opAQ5no6LqJNo9TqnxBKsgnkIYHozW9KSTlFVoSUJYh1Fl/sswkEoqIugRSm7tbh6pABtYjGAjW+GOS23j8qbw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/prop-types": "*",
+        "csstype": "^3.0.2"
+      }
+    },
+    "modules/ContactsNitroModule/node_modules/eslint-plugin-prettier": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz",
+      "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "prettier-linter-helpers": "^1.0.0",
+        "synckit": "^0.9.1"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint-plugin-prettier"
+      },
+      "peerDependencies": {
+        "@types/eslint": ">=8.0.0",
+        "eslint": ">=8.0.0",
+        "eslint-config-prettier": "*",
+        "prettier": ">=3.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/eslint": {
+          "optional": true
+        },
+        "eslint-config-prettier": {
+          "optional": true
+        }
+      }
+    },
+    "modules/ContactsNitroModule/node_modules/prettier": {
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
+      "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "prettier": "bin/prettier.cjs"
       },
       "engines": {
-        "node": "20.18.0",
-        "npm": "10.8.2"
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/prettier/prettier?sponsor=1"
+      }
+    },
+    "modules/ContactsNitroModule/node_modules/typescript": {
+      "version": "5.7.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
+      "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
       }
     },
     "node_modules/@actions/core": {
@@ -7199,6 +7308,19 @@
         "node": ">=14"
       }
     },
+    "node_modules/@pkgr/core": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
+      "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/unts"
+      }
+    },
     "node_modules/@pmmmwh/react-refresh-webpack-plugin": {
       "version": "0.5.15",
       "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz",
@@ -9309,6 +9431,83 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/@react-native/eslint-config": {
+      "version": "0.75.4",
+      "resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.75.4.tgz",
+      "integrity": "sha512-3KBHYwp4HnBdaCFx9KDPvQY+sGrv5fHX2qDkXGKmN3uYBz+zfnMQXTiht6OuBbWULUF0y0o8m+uH1yYAn/V9mw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/core": "^7.20.0",
+        "@babel/eslint-parser": "^7.20.0",
+        "@react-native/eslint-plugin": "0.75.4",
+        "@typescript-eslint/eslint-plugin": "^7.1.1",
+        "@typescript-eslint/parser": "^7.1.1",
+        "eslint-config-prettier": "^8.5.0",
+        "eslint-plugin-eslint-comments": "^3.2.0",
+        "eslint-plugin-ft-flow": "^2.0.1",
+        "eslint-plugin-jest": "^27.9.0",
+        "eslint-plugin-react": "^7.30.1",
+        "eslint-plugin-react-hooks": "^4.6.0",
+        "eslint-plugin-react-native": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "eslint": ">=8",
+        "prettier": ">=2"
+      }
+    },
+    "node_modules/@react-native/eslint-config/node_modules/eslint-config-prettier": {
+      "version": "8.10.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz",
+      "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "eslint-config-prettier": "bin/cli.js"
+      },
+      "peerDependencies": {
+        "eslint": ">=7.0.0"
+      }
+    },
+    "node_modules/@react-native/eslint-config/node_modules/eslint-plugin-jest": {
+      "version": "27.9.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz",
+      "integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/utils": "^5.10.0"
+      },
+      "engines": {
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+      },
+      "peerDependencies": {
+        "@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0",
+        "eslint": "^7.0.0 || ^8.0.0",
+        "jest": "*"
+      },
+      "peerDependenciesMeta": {
+        "@typescript-eslint/eslint-plugin": {
+          "optional": true
+        },
+        "jest": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@react-native/eslint-plugin": {
+      "version": "0.75.4",
+      "resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.75.4.tgz",
+      "integrity": "sha512-1kEZzC8UKi3baHnH7tBVCNpF4aoAmT7g7hEa5/rtZ+Z7vcpaxeY6wjNYt3j02Z9n310yX0NKDJox30CqvzEvsg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/@react-native/gradle-plugin": {
       "version": "0.75.2",
       "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.2.tgz",
@@ -13105,6 +13304,44 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/@ts-morph/common": {
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.25.0.tgz",
+      "integrity": "sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "minimatch": "^9.0.4",
+        "path-browserify": "^1.0.1",
+        "tinyglobby": "^0.2.9"
+      }
+    },
+    "node_modules/@ts-morph/common/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/@ts-morph/common/node_modules/minimatch": {
+      "version": "9.0.5",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/@tsconfig/node10": {
       "version": "1.0.9",
       "dev": true,
@@ -13623,6 +13860,13 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/minimist": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
+      "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/ms": {
       "version": "0.7.34",
       "dev": true,
@@ -13664,6 +13908,13 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/normalize-package-data": {
+      "version": "2.4.4",
+      "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
+      "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/parse-json": {
       "version": "4.0.0",
       "dev": true,
@@ -15496,6 +15747,16 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/arrify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+      "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/asap": {
       "version": "2.0.6",
       "license": "MIT"
@@ -17290,6 +17551,38 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/camelcase-keys": {
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz",
+      "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "camelcase": "^6.3.0",
+        "map-obj": "^4.1.0",
+        "quick-lru": "^5.1.1",
+        "type-fest": "^1.2.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/camelcase-keys/node_modules/type-fest": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
+      "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
+      "dev": true,
+      "license": "(MIT OR CC0-1.0)",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/camelize": {
       "version": "1.0.1",
       "license": "MIT",
@@ -17653,6 +17946,13 @@
         "node": ">= 0.12.0"
       }
     },
+    "node_modules/code-block-writer": {
+      "version": "13.0.3",
+      "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz",
+      "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/collect-v8-coverage": {
       "version": "1.0.1",
       "dev": true,
@@ -18144,6 +18444,10 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/contacts-nitro-module": {
+      "resolved": "modules/ContactsNitroModule",
+      "link": true
+    },
     "node_modules/content-disposition": {
       "version": "0.5.4",
       "dev": true,
@@ -18858,6 +19162,56 @@
         }
       }
     },
+    "node_modules/decamelize": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz",
+      "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/decamelize-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
+      "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "decamelize": "^1.1.0",
+        "map-obj": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/decamelize-keys/node_modules/decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/decamelize-keys/node_modules/map-obj": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+      "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/decimal.js": {
       "version": "10.4.3",
       "dev": true,
@@ -19061,6 +19415,184 @@
         "node": ">=6"
       }
     },
+    "node_modules/del-cli": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/del-cli/-/del-cli-5.1.0.tgz",
+      "integrity": "sha512-xwMeh2acluWeccsfzE7VLsG3yTr7nWikbfw+xhMnpRrF15pGSkw+3/vJZWlGoE4I86UiLRNHicmKt4tkIX9Jtg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "del": "^7.1.0",
+        "meow": "^10.1.3"
+      },
+      "bin": {
+        "del": "cli.js",
+        "del-cli": "cli.js"
+      },
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/del-cli/node_modules/aggregate-error": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz",
+      "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "clean-stack": "^4.0.0",
+        "indent-string": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/del-cli/node_modules/clean-stack": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz",
+      "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "escape-string-regexp": "5.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/del-cli/node_modules/del": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/del/-/del-7.1.0.tgz",
+      "integrity": "sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "globby": "^13.1.2",
+        "graceful-fs": "^4.2.10",
+        "is-glob": "^4.0.3",
+        "is-path-cwd": "^3.0.0",
+        "is-path-inside": "^4.0.0",
+        "p-map": "^5.5.0",
+        "rimraf": "^3.0.2",
+        "slash": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/del-cli/node_modules/escape-string-regexp": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/del-cli/node_modules/globby": {
+      "version": "13.2.2",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz",
+      "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "dir-glob": "^3.0.1",
+        "fast-glob": "^3.3.0",
+        "ignore": "^5.2.4",
+        "merge2": "^1.4.1",
+        "slash": "^4.0.0"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/del-cli/node_modules/indent-string": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
+      "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/del-cli/node_modules/is-path-cwd": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-3.0.0.tgz",
+      "integrity": "sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/del-cli/node_modules/is-path-inside": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz",
+      "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/del-cli/node_modules/p-map": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz",
+      "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "aggregate-error": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/del-cli/node_modules/slash": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
+      "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/del/node_modules/array-union": {
       "version": "1.0.2",
       "dev": true,
@@ -23403,6 +23935,16 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/hard-rejection": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
+      "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/has": {
       "version": "1.0.3",
       "dev": true,
@@ -24796,6 +25338,16 @@
         "node": ">=6"
       }
     },
+    "node_modules/is-plain-obj": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+      "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/is-plain-object": {
       "version": "5.0.0",
       "dev": true,
@@ -28446,6 +28998,19 @@
         "tmpl": "1.0.5"
       }
     },
+    "node_modules/map-obj": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
+      "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/map-or-similar": {
       "version": "1.5.0",
       "dev": true,
@@ -28652,6 +29217,92 @@
       "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz",
       "integrity": "sha512-OcjA+jzjOYzKmKS6IQVALHLVz+rNTMPoJvCztFaZxwG14wtAW7VRZjwTQu06vKCYOxh4jVnik7ya0SXTB0W+xA=="
     },
+    "node_modules/meow": {
+      "version": "10.1.5",
+      "resolved": "https://registry.npmjs.org/meow/-/meow-10.1.5.tgz",
+      "integrity": "sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/minimist": "^1.2.2",
+        "camelcase-keys": "^7.0.0",
+        "decamelize": "^5.0.0",
+        "decamelize-keys": "^1.1.0",
+        "hard-rejection": "^2.1.0",
+        "minimist-options": "4.1.0",
+        "normalize-package-data": "^3.0.2",
+        "read-pkg-up": "^8.0.0",
+        "redent": "^4.0.0",
+        "trim-newlines": "^4.0.2",
+        "type-fest": "^1.2.2",
+        "yargs-parser": "^20.2.9"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/meow/node_modules/indent-string": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
+      "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/meow/node_modules/redent": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz",
+      "integrity": "sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "indent-string": "^5.0.0",
+        "strip-indent": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/meow/node_modules/strip-indent": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz",
+      "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "min-indent": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/meow/node_modules/type-fest": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
+      "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
+      "dev": true,
+      "license": "(MIT OR CC0-1.0)",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/merge-descriptors": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
@@ -29247,6 +29898,21 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/minimist-options": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
+      "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "arrify": "^1.0.1",
+        "is-plain-obj": "^1.1.0",
+        "kind-of": "^6.0.3"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/minipass": {
       "version": "3.3.6",
       "license": "ISC",
@@ -29484,6 +30150,37 @@
       "version": "1.0.5",
       "license": "MIT"
     },
+    "node_modules/nitro-codegen": {
+      "version": "0.18.1",
+      "resolved": "https://registry.npmjs.org/nitro-codegen/-/nitro-codegen-0.18.1.tgz",
+      "integrity": "sha512-gDOHIIFFY89Ibo/Q8Dlzx4Rk9fCaGnby4Er5Dh1xV4J5hMqTfqo2VjG+RxScdUTYy/SKOc0UsB2faQybs5+GDw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
+        "chalk": "^5.3.0",
+        "react-native-nitro-modules": "^0.18.1",
+        "ts-morph": "^24.0.0",
+        "yargs": "^17.7.2",
+        "zod": "^3.23.8"
+      },
+      "bin": {
+        "nitro-codegen": "lib/index.js"
+      }
+    },
+    "node_modules/nitro-codegen/node_modules/chalk": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+      "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^12.17.0 || ^14.13 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
     "node_modules/no-case": {
       "version": "3.0.4",
       "dev": true,
@@ -29717,6 +30414,22 @@
         "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
       }
     },
+    "node_modules/normalize-package-data": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
+      "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "dependencies": {
+        "hosted-git-info": "^4.0.1",
+        "is-core-module": "^2.5.0",
+        "semver": "^7.3.4",
+        "validate-npm-package-license": "^3.0.1"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/normalize-path": {
       "version": "3.0.0",
       "license": "MIT",
@@ -32173,6 +32886,17 @@
         "react-native": ">=0.65.0"
       }
     },
+    "node_modules/react-native-nitro-modules": {
+      "version": "0.18.2",
+      "resolved": "https://registry.npmjs.org/react-native-nitro-modules/-/react-native-nitro-modules-0.18.2.tgz",
+      "integrity": "sha512-eHsq1cRfm/Bz1Nq7KctTqxAqhzVSNo0WGX281xARZh+vOq8633Qxn1NHRZ5/Rno2Bla6HOXlUW6RoW0wKM/7kg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "react": "*",
+        "react-native": "*"
+      }
+    },
     "node_modules/react-native-onyx": {
       "version": "2.0.86",
       "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.86.tgz",
@@ -32974,6 +33698,69 @@
         "node": ">=6"
       }
     },
+    "node_modules/read-pkg": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz",
+      "integrity": "sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/normalize-package-data": "^2.4.0",
+        "normalize-package-data": "^3.0.2",
+        "parse-json": "^5.2.0",
+        "type-fest": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/read-pkg-up": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz",
+      "integrity": "sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "find-up": "^5.0.0",
+        "read-pkg": "^6.0.0",
+        "type-fest": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/read-pkg-up/node_modules/type-fest": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
+      "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
+      "dev": true,
+      "license": "(MIT OR CC0-1.0)",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/read-pkg/node_modules/type-fest": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
+      "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
+      "dev": true,
+      "license": "(MIT OR CC0-1.0)",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/readable-stream": {
       "version": "2.3.8",
       "license": "MIT",
@@ -34373,6 +35160,17 @@
       "version": "0.0.2",
       "dev": true
     },
+    "node_modules/spdx-correct": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+      "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "spdx-expression-parse": "^3.0.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
     "node_modules/spdx-exceptions": {
       "version": "2.3.0",
       "dev": true,
@@ -35085,6 +35883,23 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/synckit": {
+      "version": "0.9.2",
+      "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
+      "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@pkgr/core": "^0.1.0",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/unts"
+      }
+    },
     "node_modules/tabbable": {
       "version": "6.2.0",
       "license": "MIT"
@@ -35518,6 +36333,48 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/tinyglobby": {
+      "version": "0.2.10",
+      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz",
+      "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fdir": "^6.4.2",
+        "picomatch": "^4.0.2"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/tinyglobby/node_modules/fdir": {
+      "version": "6.4.2",
+      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz",
+      "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==",
+      "dev": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/tinyglobby/node_modules/picomatch": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+      "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
     "node_modules/tinyqueue": {
       "version": "2.0.3",
       "license": "ISC"
@@ -35633,6 +36490,19 @@
         "tree-kill": "cli.js"
       }
     },
+    "node_modules/trim-newlines": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz",
+      "integrity": "sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/trim-right": {
       "version": "1.0.1",
       "license": "MIT",
@@ -35737,12 +36607,15 @@
         "node": ">=10"
       }
     },
-    "node_modules/ts-jest/node_modules/yargs-parser": {
-      "version": "21.1.1",
+    "node_modules/ts-morph": {
+      "version": "24.0.0",
+      "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-24.0.0.tgz",
+      "integrity": "sha512-2OAOg/Ob5yx9Et7ZX4CvTCc0UFoZHwLEJ+dpDPSUi5TgwwlTlX47w+iFRrEwzUZwYACjq83cgjS/Da50Ga37uw==",
       "dev": true,
-      "license": "ISC",
-      "engines": {
-        "node": ">=12"
+      "license": "MIT",
+      "dependencies": {
+        "@ts-morph/common": "~0.25.0",
+        "code-block-writer": "^13.0.3"
       }
     },
     "node_modules/ts-node": {
@@ -36518,6 +37391,17 @@
       "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz",
       "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA=="
     },
+    "node_modules/validate-npm-package-license": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "spdx-correct": "^3.0.0",
+        "spdx-expression-parse": "^3.0.0"
+      }
+    },
     "node_modules/validate-npm-package-name": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
@@ -37573,18 +38457,20 @@
         "node": ">=12"
       }
     },
-    "node_modules/yargs/node_modules/y18n": {
-      "version": "5.0.8",
+    "node_modules/yargs-parser": {
+      "version": "21.1.1",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
       "license": "ISC",
       "engines": {
-        "node": ">=10"
+        "node": ">=12"
       }
     },
-    "node_modules/yargs/node_modules/yargs-parser": {
-      "version": "21.1.1",
+    "node_modules/yargs/node_modules/y18n": {
+      "version": "5.0.8",
       "license": "ISC",
       "engines": {
-        "node": ">=12"
+        "node": ">=10"
       }
     },
     "node_modules/yauzl": {
diff --git a/package.json b/package.json
index 02ef81489e01..30db788aca9c 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
     "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure",
     "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc",
     "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=0 --cache --cache-location=node_modules/.cache/eslint",
-    "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 eslint --max-warnings=0 --config ./.eslintrc.changed.js $(git diff --diff-filter=AM --name-only origin/main HEAD -- \"*.ts\" \"*.tsx\")",
+    "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 eslint --max-warnings=0 --config ./.eslintrc.changed.js $(git diff --diff-filter=AM --name-only origin/main HEAD -- \"*.ts\" \"*.tsx\" \":!modules/**\")",
     "lint-watch": "npx eslint-watch --watch --changed",
     "shellcheck": "./scripts/shellCheck.sh",
     "prettier": "prettier --write .",
@@ -109,6 +109,7 @@
     "awesome-phonenumber": "^5.4.0",
     "babel-polyfill": "^6.26.0",
     "canvas-size": "^1.2.6",
+    "contacts-nitro-module": "./modules/ContactsNitroModule",
     "core-js": "^3.32.0",
     "date-fns": "^4.1.0",
     "date-fns-tz": "^3.2.0",
@@ -159,6 +160,7 @@
     "react-native-localize": "^2.2.6",
     "react-native-modal": "^13.0.0",
     "react-native-onyx": "2.0.86",
+    "react-native-nitro-modules": "^0.18.1",
     "react-native-pager-view": "6.5.1",
     "react-native-pdf": "6.7.3",
     "react-native-performance": "^5.1.0",
@@ -374,17 +376,7 @@
       ]
     }
   },
-  "electronmon": {
-    "patterns": [
-      "!src/**",
-      "!ios/**",
-      "!android/**",
-      "!tests/**",
-      "*.test.*"
-    ]
-  },
-  "engines": {
-    "node": "20.18.0",
-    "npm": "10.8.2"
-  }
+  "workspaces": [
+    "modules/ContactsNitroModule"
+  ]
 }
diff --git a/react-native.config.js b/react-native.config.js
index 773375378acd..ffbc8f5c65cb 100644
--- a/react-native.config.js
+++ b/react-native.config.js
@@ -1,3 +1,5 @@
+const path = require('path');
+const pak = require('./modules/ContactsNitroModule/package.json');
 const iosSourceDir = process.env.PROJECT_ROOT_PATH ? process.env.PROJECT_ROOT_PATH + 'ios' : 'ios';
 const androidSourceDir = process.env.PROJECT_ROOT_PATH ? process.env.PROJECT_ROOT_PATH + 'android' : 'android';
 
@@ -7,4 +9,9 @@ module.exports = {
         android: {sourceDir: androidSourceDir},
     },
     assets: ['./assets/fonts/native'],
+    dependencies: {
+        [pak.name]: {
+            root: path.join(__dirname, 'modules', 'ContactsNitroModule'),
+        },
+    },
 };
diff --git a/src/CONST.ts b/src/CONST.ts
index 204ccaccf394..4724e6175b2e 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -6430,6 +6430,14 @@ const CONST = {
         },
     },
 
+    DEVICE_CONTACT: {
+        FIRST_NAME: 'FIRST_NAME',
+        LAST_NAME: 'LAST_NAME',
+        PHONE_NUMBERS: 'PHONE_NUMBERS',
+        EMAIL_ADDRESSES: 'EMAIL_ADDRESSES',
+        IMAGE_DATA: 'IMAGE_DATA',
+    },
+
     HYBRID_APP: {
         REORDERING_REACT_NATIVE_ACTIVITY_TO_FRONT: 'reorderingReactNativeActivityToFront',
     },
diff --git a/src/components/ContactPermissionModal/index.native.tsx b/src/components/ContactPermissionModal/index.native.tsx
new file mode 100644
index 000000000000..825c8bc4afbe
--- /dev/null
+++ b/src/components/ContactPermissionModal/index.native.tsx
@@ -0,0 +1,73 @@
+import React, {useEffect, useState} from 'react';
+import {InteractionManager} from 'react-native';
+import {RESULTS} from 'react-native-permissions';
+import ConfirmModal from '@components/ConfirmModal';
+import * as Illustrations from '@components/Icon/Illustrations';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import {getContactPermission} from '@libs/ContactPermission';
+import type {ContactPermissionModalProps} from './types';
+
+function ContactPermissionModal({startPermissionFlow, resetPermissionFlow, onDeny, onGrant}: ContactPermissionModalProps) {
+    const [isModalVisible, setIsModalVisible] = useState(false);
+
+    const styles = useThemeStyles();
+    const {translate} = useLocalize();
+
+    useEffect(() => {
+        if (!startPermissionFlow) {
+            return;
+        }
+        getContactPermission().then((status) => {
+            if (status === RESULTS.GRANTED || status === RESULTS.LIMITED) {
+                return onGrant();
+            }
+            if (status === RESULTS.BLOCKED) {
+                return;
+            }
+            setIsModalVisible(true);
+        });
+        // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- We only want to run this effect when startPermissionFlow changes
+    }, [startPermissionFlow]);
+
+    const handleGrantPermission = () => {
+        setIsModalVisible(false);
+        InteractionManager.runAfterInteractions(onGrant);
+    };
+
+    const handleDenyPermission = () => {
+        onDeny(RESULTS.DENIED);
+        setIsModalVisible(false);
+    };
+
+    const handleCloseModal = () => {
+        setIsModalVisible(false);
+        resetPermissionFlow();
+    };
+
+    return (
+        <ConfirmModal
+            isVisible={isModalVisible}
+            onConfirm={handleGrantPermission}
+            onCancel={handleDenyPermission}
+            onBackdropPress={handleCloseModal}
+            confirmText={translate('common.continue')}
+            cancelText={translate('common.notNow')}
+            prompt={translate('contact.importContactsText')}
+            promptStyles={[styles.textLabelSupportingEmptyValue, styles.mb4]}
+            title={translate('contact.importContactsTitle')}
+            titleContainerStyles={[styles.mt2, styles.mb0]}
+            titleStyles={[styles.textHeadline]}
+            iconSource={Illustrations.ToddWithPhones}
+            iconFill={false}
+            iconWidth={176}
+            iconHeight={178}
+            shouldCenterIcon
+            shouldReverseStackedButtons
+        />
+    );
+}
+
+ContactPermissionModal.displayName = 'ContactPermissionModal';
+
+export default ContactPermissionModal;
diff --git a/src/components/ContactPermissionModal/index.tsx b/src/components/ContactPermissionModal/index.tsx
new file mode 100644
index 000000000000..3f7e25bac590
--- /dev/null
+++ b/src/components/ContactPermissionModal/index.tsx
@@ -0,0 +1,10 @@
+import type {ContactPermissionModalProps} from './types';
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function ContactPermissionModal(props: ContactPermissionModalProps) {
+    return null;
+}
+
+ContactPermissionModal.displayName = 'ContactPermissionModal';
+
+export default ContactPermissionModal;
diff --git a/src/components/ContactPermissionModal/types.ts b/src/components/ContactPermissionModal/types.ts
new file mode 100644
index 000000000000..5c831410656f
--- /dev/null
+++ b/src/components/ContactPermissionModal/types.ts
@@ -0,0 +1,19 @@
+import type {PermissionStatus} from 'react-native-permissions';
+
+type ContactPermissionModalProps = {
+    /** A callback to call when the permission has been granted */
+    onGrant: () => void;
+
+    /** A callback to call when the permission has been denied */
+    onDeny: (permission: PermissionStatus) => void;
+
+    /** Should start the permission flow? */
+    startPermissionFlow: boolean;
+
+    /** Reset the permission flow */
+    resetPermissionFlow: () => void;
+};
+
+export default {};
+
+export type {ContactPermissionModalProps};
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 375d478a8d6d..67ade7433258 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -496,6 +496,13 @@ const translations = {
         allowPermission: 'allow location access in settings',
         tryAgain: 'and try again.',
     },
+    contact: {
+        importContacts: 'Import contacts',
+        importContactsTitle: 'Import your contacts',
+        importContactsText: 'Import contacts from your phone so your favorite people are always a tap away.',
+        importContactsExplanation: 'so your favorite people are always a tap away.',
+        importContactsNativeText: 'Just one more step! Give us the green light to import your contacts.',
+    },
     anonymousReportFooter: {
         logoTagline: 'Join the discussion.',
     },
diff --git a/src/languages/es.ts b/src/languages/es.ts
index bd2dd6fae4d4..44d3bd7464fe 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -491,6 +491,13 @@ const translations = {
         allowPermission: 'habilita el permiso de ubicación en la configuración',
         tryAgain: 'e inténtalo de nuevo.',
     },
+    contact: {
+        importContacts: 'Importar contactos',
+        importContactsTitle: 'Importa tus contactos',
+        importContactsText: 'Importa contactos desde tu teléfono para que tus personas favoritas siempre estén a un toque de distancia.',
+        importContactsExplanation: 'para que tus personas favoritas estén siempre a un toque de distancia.',
+        importContactsNativeText: '¡Solo un paso más! Danos luz verde para importar tus contactos.',
+    },
     anonymousReportFooter: {
         logoTagline: 'Únete a la discusión.',
     },
diff --git a/src/libs/ContactImport/index.native.tsx b/src/libs/ContactImport/index.native.tsx
new file mode 100644
index 000000000000..564bbba6805f
--- /dev/null
+++ b/src/libs/ContactImport/index.native.tsx
@@ -0,0 +1,32 @@
+import {ContactsNitroModule} from 'contacts-nitro-module';
+import type {Contact} from 'contacts-nitro-module';
+import {RESULTS} from 'react-native-permissions';
+import type {PermissionStatus} from 'react-native-permissions';
+import {requestContactPermission} from '@libs/ContactPermission';
+import CONST from '@src/CONST';
+import type {ContactImportResult} from './types';
+
+function contactImport(): Promise<ContactImportResult> {
+    let permissionStatus: PermissionStatus = RESULTS.UNAVAILABLE;
+
+    return requestContactPermission()
+        .then((response: PermissionStatus) => {
+            permissionStatus = response;
+            if (response === RESULTS.GRANTED) {
+                return ContactsNitroModule.getAll([
+                    CONST.DEVICE_CONTACT.FIRST_NAME,
+                    CONST.DEVICE_CONTACT.LAST_NAME,
+                    CONST.DEVICE_CONTACT.PHONE_NUMBERS,
+                    CONST.DEVICE_CONTACT.EMAIL_ADDRESSES,
+                    CONST.DEVICE_CONTACT.IMAGE_DATA,
+                ]);
+            }
+            return [] as Contact[];
+        })
+        .then((deviceContacts) => ({
+            contactList: Array.isArray(deviceContacts) ? deviceContacts : [],
+            permissionStatus,
+        }));
+}
+
+export default contactImport;
diff --git a/src/libs/ContactImport/index.tsx b/src/libs/ContactImport/index.tsx
new file mode 100644
index 000000000000..b3fe3dc27868
--- /dev/null
+++ b/src/libs/ContactImport/index.tsx
@@ -0,0 +1,11 @@
+import {RESULTS} from 'react-native-permissions';
+import type {ContactImportResult} from './types';
+
+const contactImport = (): Promise<ContactImportResult> => {
+    return Promise.resolve({
+        contactList: [],
+        permissionStatus: RESULTS.UNAVAILABLE,
+    });
+};
+
+export default contactImport;
diff --git a/src/libs/ContactImport/types.ts b/src/libs/ContactImport/types.ts
new file mode 100644
index 000000000000..8247bdf8e2ec
--- /dev/null
+++ b/src/libs/ContactImport/types.ts
@@ -0,0 +1,20 @@
+import type {PermissionStatus} from 'react-native-permissions';
+
+type StringHolder = {
+    value: string;
+};
+
+type ContactImportResult = {
+    contactList: DeviceContact[] | [];
+    permissionStatus: PermissionStatus;
+};
+
+type DeviceContact = {
+    firstName?: string;
+    lastName?: string;
+    emailAddresses?: StringHolder[];
+    phoneNumbers?: Array<{value: string}>;
+    imageData?: string;
+};
+
+export type {StringHolder, ContactImportResult, DeviceContact};
diff --git a/src/libs/ContactPermission/index.android.ts b/src/libs/ContactPermission/index.android.ts
new file mode 100644
index 000000000000..8ef0eace7135
--- /dev/null
+++ b/src/libs/ContactPermission/index.android.ts
@@ -0,0 +1,11 @@
+import {check, PERMISSIONS, request} from 'react-native-permissions';
+
+function requestContactPermission() {
+    return request(PERMISSIONS.ANDROID.READ_CONTACTS);
+}
+
+function getContactPermission() {
+    return check(PERMISSIONS.ANDROID.READ_CONTACTS);
+}
+
+export {requestContactPermission, getContactPermission};
diff --git a/src/libs/ContactPermission/index.ios.ts b/src/libs/ContactPermission/index.ios.ts
new file mode 100644
index 000000000000..590373c9bd7d
--- /dev/null
+++ b/src/libs/ContactPermission/index.ios.ts
@@ -0,0 +1,11 @@
+import {check, PERMISSIONS, request} from 'react-native-permissions';
+
+function requestContactPermission() {
+    return request(PERMISSIONS.IOS.CONTACTS);
+}
+
+function getContactPermission() {
+    return check(PERMISSIONS.IOS.CONTACTS);
+}
+
+export {requestContactPermission, getContactPermission};
diff --git a/src/libs/ContactPermission/index.ts b/src/libs/ContactPermission/index.ts
new file mode 100644
index 000000000000..0216a5022dda
--- /dev/null
+++ b/src/libs/ContactPermission/index.ts
@@ -0,0 +1,16 @@
+import {RESULTS} from 'react-native-permissions';
+import type {PermissionStatus} from 'react-native-permissions';
+
+function requestContactPermission(): Promise<PermissionStatus> {
+    return new Promise((resolve) => {
+        resolve(RESULTS.GRANTED);
+    });
+}
+
+function getContactPermission(): Promise<PermissionStatus> {
+    return new Promise((resolve) => {
+        resolve(RESULTS.GRANTED);
+    });
+}
+
+export {requestContactPermission, getContactPermission};
diff --git a/src/libs/ContactUtils.ts b/src/libs/ContactUtils.ts
new file mode 100644
index 000000000000..5769e6cde2a4
--- /dev/null
+++ b/src/libs/ContactUtils.ts
@@ -0,0 +1,50 @@
+import CONST from '@src/CONST';
+import type {PersonalDetails} from '@src/types/onyx';
+import type {DeviceContact, StringHolder} from './ContactImport/types';
+import * as OptionsListUtils from './OptionsListUtils';
+import {getAvatarForContact} from './RandomAvatarUtils';
+
+function sortEmailObjects(emails?: StringHolder[]): string[] {
+    if (!emails?.length) {
+        return [];
+    }
+
+    const expensifyDomain = CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN.toLowerCase();
+
+    return emails
+        .filter((email) => email?.value)
+        .map((email) => email.value)
+        .sort((a, b) => {
+            const isExpensifyA = a.toLowerCase().includes(expensifyDomain);
+            const isExpensifyB = b.toLowerCase().includes(expensifyDomain);
+
+            // Prioritize Expensify emails, then sort alphabetically
+            return isExpensifyA !== isExpensifyB ? Number(isExpensifyB) - Number(isExpensifyA) : a.localeCompare(b);
+        });
+}
+
+const getContacts = (deviceContacts: DeviceContact[] | []): Array<OptionsListUtils.SearchOption<PersonalDetails>> => {
+    return deviceContacts
+        .map((contact) => {
+            const email = sortEmailObjects(contact?.emailAddresses ?? [])?.at(0) ?? '';
+            // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+            const avatarSource = (contact?.imageData || getAvatarForContact(`${contact?.firstName}${email}${contact?.lastName}`)) ?? '';
+            const phoneNumber = contact.phoneNumbers?.[0]?.value ?? '';
+            const firstName = contact?.firstName ?? '';
+            const lastName = contact?.lastName ?? '';
+
+            return OptionsListUtils.getUserToInviteContactOption({
+                selectedOptions: [],
+                optionsToExclude: [],
+                searchValue: email || phoneNumber || firstName || '',
+                firstName,
+                lastName,
+                email,
+                phone: phoneNumber,
+                avatar: avatarSource,
+            });
+        })
+        .filter((contact): contact is OptionsListUtils.SearchOption<PersonalDetails> => contact !== null);
+};
+
+export default getContacts;
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index a7f738790f92..0f82bd43db17 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -115,6 +115,11 @@ type GetUserToInviteConfig = {
     searchValue: string;
     optionsToExclude?: Array<Partial<ReportUtils.OptionData>>;
     reportActions?: ReportActions;
+    firstName?: string;
+    lastName?: string;
+    email?: string;
+    phone?: string;
+    avatar?: UserUtils.AvatarSource;
     shouldAcceptName?: boolean;
 } & Pick<GetOptionsConfig, 'selectedOptions' | 'showChatPreviewLine'>;
 
@@ -351,25 +356,29 @@ function getParticipantsOption(participant: ReportUtils.OptionData | Participant
     const detail = getPersonalDetailsForAccountIDs([participant.accountID ?? -1], personalDetails)[participant.accountID ?? -1];
     // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
     const login = detail?.login || participant.login || '';
-    const displayName = LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(detail, login || participant.text));
+    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+    const displayName = participant?.displayName || LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(detail, login || participant.text));
 
     return {
         keyForList: String(detail?.accountID),
         login,
         accountID: detail?.accountID ?? -1,
         text: displayName,
-        firstName: detail?.firstName ?? '',
-        lastName: detail?.lastName ?? '',
+        // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+        firstName: (detail?.firstName || ('firstName' in participant ? participant.firstName : '')) ?? '',
+        // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+        lastName: (detail?.lastName || ('lastName' in participant ? participant.lastName : '')) ?? '',
         alternateText: LocalePhoneNumber.formatPhoneNumber(login) || displayName,
         icons: [
             {
-                source: detail?.avatar ?? FallbackAvatar,
+                source: ('avatar' in participant ? participant.avatar : detail?.avatar) ?? FallbackAvatar,
                 name: login,
                 type: CONST.ICON_TYPE_AVATAR,
                 id: detail?.accountID,
             },
         ],
-        phoneNumber: detail?.phoneNumber ?? '',
+        // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+        phoneNumber: (detail?.phoneNumber || participant?.phoneNumber) ?? '',
         selected: participant.selected,
         isSelected: participant.selected,
         searchText: participant.searchText ?? undefined,
@@ -1083,6 +1092,101 @@ function getUserToInviteOption({
     return userToInvite;
 }
 
+function getUserToInviteContactOption({
+    searchValue,
+    optionsToExclude = [],
+    selectedOptions = [],
+    firstName,
+    lastName,
+    email,
+    phone,
+    avatar,
+}: GetUserToInviteConfig): SearchOption<PersonalDetails> | null {
+    // If email is provided, use it as the primary identifier
+    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+    const effectiveSearchValue = email || searchValue;
+
+    // Handle phone number parsing for either provided phone or searchValue
+    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+    const phoneToCheck = phone || searchValue;
+    const parsedPhoneNumber = PhoneNumber.parsePhoneNumber(LoginUtils.appendCountryCode(Str.removeSMSDomain(phoneToCheck)));
+
+    const isCurrentUserLogin = isCurrentUser({login: effectiveSearchValue} as PersonalDetails);
+    const isInSelectedOption = selectedOptions.some((option) => 'login' in option && option.login === effectiveSearchValue);
+
+    // Validate email (either provided email or searchValue)
+    const isValidEmail = Str.isValidEmail(effectiveSearchValue) && !Str.isDomainEmail(effectiveSearchValue) && !Str.endsWith(effectiveSearchValue, CONST.SMS.DOMAIN);
+
+    const isValidPhoneNumber = parsedPhoneNumber.possible && Str.isValidE164Phone(LoginUtils.getPhoneNumberWithoutSpecialChars(parsedPhoneNumber.number?.input ?? ''));
+
+    const isInOptionToExclude =
+        optionsToExclude.findIndex((optionToExclude) => 'login' in optionToExclude && optionToExclude.login === PhoneNumber.addSMSDomainIfPhoneNumber(effectiveSearchValue).toLowerCase()) !==
+        -1;
+
+    if (!effectiveSearchValue || isCurrentUserLogin || isInSelectedOption || (!isValidEmail && !isValidPhoneNumber) || isInOptionToExclude) {
+        return null;
+    }
+
+    // Generates an optimistic account ID for new users not yet saved in Onyx
+    const optimisticAccountID = UserUtils.generateAccountID(effectiveSearchValue);
+
+    // Construct display name if firstName/lastName are provided
+    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+    const displayName = firstName && lastName ? `${firstName} ${lastName}` : firstName || lastName || effectiveSearchValue;
+
+    // Create the base user details that will be used in both item and participantsList
+    const userDetails = {
+        accountID: optimisticAccountID,
+        // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+        avatar: avatar || FallbackAvatar,
+        firstName: firstName ?? '',
+        lastName: lastName ?? '',
+        displayName,
+        login: effectiveSearchValue,
+        pronouns: '',
+        phoneNumber: phone ?? '',
+        validated: true,
+    };
+
+    const userToInvite = {
+        item: userDetails,
+        text: displayName,
+        alternateText: displayName !== effectiveSearchValue ? effectiveSearchValue : undefined,
+        brickRoadIndicator: null,
+        icons: [
+            {
+                source: userDetails.avatar,
+                type: CONST.ICON_TYPE_AVATAR,
+                name: effectiveSearchValue,
+                id: optimisticAccountID,
+            },
+        ],
+        tooltipText: null,
+        participantsList: [userDetails],
+        accountID: optimisticAccountID,
+        login: effectiveSearchValue,
+        reportID: '',
+        phoneNumber: phone ?? '',
+        hasDraftComment: false,
+        keyForList: optimisticAccountID.toString(),
+        isDefaultRoom: false,
+        isPinned: false,
+        isWaitingOnBankAccount: false,
+        isIOUReportOwner: false,
+        iouReportAmount: 0,
+        isChatRoom: false,
+        shouldShowSubscript: false,
+        isPolicyExpenseChat: false,
+        isOwnPolicyExpenseChat: false,
+        isExpenseReport: false,
+        lastMessageText: '',
+        isBold: true,
+        isOptimisticAccount: true,
+    };
+
+    return userToInvite;
+}
+
 /**
  * Options are reports and personal details. This function filters out the options that are not valid to be displayed.
  */
@@ -1830,6 +1934,7 @@ export {
     getFirstKeyForList,
     canCreateOptimisticPersonalDetailOption,
     getUserToInviteOption,
+    getUserToInviteContactOption,
     getPersonalDetailSearchTerms,
     getCurrentUserSearchTerms,
     getEmptyOptions,
@@ -1839,4 +1944,4 @@ export {
     hasReportErrors,
 };
 
-export type {Section, SectionBase, MemberForList, Options, OptionList, SearchOption, PayeePersonalDetails, Option, OptionTree};
+export type {MemberForList, Options, OptionList, SearchOption, PayeePersonalDetails, Option, OptionTree, GetUserToInviteConfig, Section, SectionBase};
diff --git a/src/libs/RandomAvatarUtils.ts b/src/libs/RandomAvatarUtils.ts
new file mode 100644
index 000000000000..2aad23b13583
--- /dev/null
+++ b/src/libs/RandomAvatarUtils.ts
@@ -0,0 +1,99 @@
+import type {FC} from 'react';
+import type {SvgProps} from 'react-native-svg';
+import Avatar1 from '@assets/images/avatars/user/default-avatar_1.svg';
+import Avatar2 from '@assets/images/avatars/user/default-avatar_2.svg';
+import Avatar3 from '@assets/images/avatars/user/default-avatar_3.svg';
+import Avatar4 from '@assets/images/avatars/user/default-avatar_4.svg';
+import Avatar5 from '@assets/images/avatars/user/default-avatar_5.svg';
+import Avatar6 from '@assets/images/avatars/user/default-avatar_6.svg';
+import Avatar7 from '@assets/images/avatars/user/default-avatar_7.svg';
+import Avatar8 from '@assets/images/avatars/user/default-avatar_8.svg';
+import Avatar9 from '@assets/images/avatars/user/default-avatar_9.svg';
+import Avatar10 from '@assets/images/avatars/user/default-avatar_10.svg';
+import Avatar11 from '@assets/images/avatars/user/default-avatar_11.svg';
+import Avatar12 from '@assets/images/avatars/user/default-avatar_12.svg';
+import Avatar13 from '@assets/images/avatars/user/default-avatar_13.svg';
+import Avatar14 from '@assets/images/avatars/user/default-avatar_14.svg';
+import Avatar15 from '@assets/images/avatars/user/default-avatar_15.svg';
+import Avatar16 from '@assets/images/avatars/user/default-avatar_16.svg';
+import Avatar17 from '@assets/images/avatars/user/default-avatar_17.svg';
+import Avatar18 from '@assets/images/avatars/user/default-avatar_18.svg';
+import Avatar19 from '@assets/images/avatars/user/default-avatar_19.svg';
+import Avatar20 from '@assets/images/avatars/user/default-avatar_20.svg';
+import Avatar21 from '@assets/images/avatars/user/default-avatar_21.svg';
+import Avatar22 from '@assets/images/avatars/user/default-avatar_22.svg';
+import Avatar23 from '@assets/images/avatars/user/default-avatar_23.svg';
+import Avatar24 from '@assets/images/avatars/user/default-avatar_24.svg';
+
+type AvatarComponent = FC<SvgProps>;
+type AvatarArray = readonly AvatarComponent[];
+
+const avatars: AvatarArray = [
+    Avatar1,
+    Avatar2,
+    Avatar3,
+    Avatar4,
+    Avatar5,
+    Avatar6,
+    Avatar7,
+    Avatar8,
+    Avatar9,
+    Avatar10,
+    Avatar11,
+    Avatar12,
+    Avatar13,
+    Avatar14,
+    Avatar15,
+    Avatar16,
+    Avatar17,
+    Avatar18,
+    Avatar19,
+    Avatar20,
+    Avatar21,
+    Avatar22,
+    Avatar23,
+    Avatar24,
+] as const;
+
+const AVATAR_LENGTH: number = avatars.length;
+const DEFAULT_AVATAR: AvatarComponent = Avatar1;
+
+// Prime numbers for better distribution
+const MULTIPLIER = AVATAR_LENGTH + 7; // First prime after length
+const OFFSET = AVATAR_LENGTH - 11; // First prime before length
+
+/**
+ * Generate a deterministic avatar based on multiple letters from the name.
+ * Uses a rolling hash of the first 5 letters (or available letters if name is shorter)
+ * for better distribution while maintaining deterministic results.
+ *
+ * @example
+ * // These will always return the same avatar for the same name
+ * const avatar1 = getAvatarForContact("Jonathan")  // Uses 'Jonat' for hash
+ * const avatar2 = getAvatarForContact("Jane")  // Uses 'Jane' for hash
+ * const avatar3 = getAvatarForContact("J")     // Uses 'J' for hash
+ *
+ * @param name - Contact name or null/undefined
+ * @returns Avatar component
+ */
+const getAvatarForContact = (name?: string | null): AvatarComponent => {
+    if (!name?.length) {
+        return DEFAULT_AVATAR;
+    }
+
+    // Take up to first 8 characters, or all if name is shorter
+    const chars = name.slice(0, 8);
+
+    // Create a rolling hash from the characters
+    let hash = 0;
+    for (let i = 0; i < chars.length; i++) {
+        const charCode = chars.charCodeAt(i);
+        // Use position-based multiplier for better distribution
+        hash = (hash * MULTIPLIER + charCode * (i + 1) + OFFSET) % AVATAR_LENGTH;
+    }
+
+    return avatars.at(Math.abs(hash)) ?? DEFAULT_AVATAR;
+};
+
+export type {AvatarComponent};
+export {getAvatarForContact};
diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx
index 07e34d9692b1..6147ee3e8d47 100644
--- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx
+++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx
@@ -1,10 +1,14 @@
 import lodashIsEqual from 'lodash/isEqual';
 import lodashPick from 'lodash/pick';
 import lodashReject from 'lodash/reject';
-import React, {memo, useCallback, useEffect, useMemo} from 'react';
+import React, {memo, useCallback, useEffect, useMemo, useState} from 'react';
 import type {GestureResponderEvent} from 'react-native';
+import {InteractionManager, Linking, View} from 'react-native';
 import {useOnyx} from 'react-native-onyx';
+import {RESULTS} from 'react-native-permissions';
+import type {PermissionStatus} from 'react-native-permissions';
 import Button from '@components/Button';
+import ContactPermissionModal from '@components/ContactPermissionModal';
 import EmptySelectionListContent from '@components/EmptySelectionListContent';
 import FormHelpMessage from '@components/FormHelpMessage';
 import * as Expensicons from '@components/Icon/Expensicons';
@@ -14,6 +18,7 @@ import {useOptionsList} from '@components/OptionListContextProvider';
 import ReferralProgramCTA from '@components/ReferralProgramCTA';
 import SelectionList from '@components/SelectionList';
 import InviteMemberListItem from '@components/SelectionList/InviteMemberListItem';
+import Text from '@components/Text';
 import useDebouncedState from '@hooks/useDebouncedState';
 import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners';
 import useLocalize from '@hooks/useLocalize';
@@ -21,11 +26,16 @@ import useNetwork from '@hooks/useNetwork';
 import usePolicy from '@hooks/usePolicy';
 import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus';
 import useThemeStyles from '@hooks/useThemeStyles';
+import contactImport from '@libs/ContactImport';
+import type {ContactImportResult} from '@libs/ContactImport/types';
+import {getContactPermission} from '@libs/ContactPermission';
+import getContacts from '@libs/ContactUtils';
 import * as DeviceCapabilities from '@libs/DeviceCapabilities';
 import Navigation from '@libs/Navigation/Navigation';
 import * as OptionsListUtils from '@libs/OptionsListUtils';
 import * as PolicyUtils from '@libs/PolicyUtils';
 import * as ReportUtils from '@libs/ReportUtils';
+import saveLastRoute from '@libs/saveLastRoute';
 import * as SubscriptionUtils from '@libs/SubscriptionUtils';
 import * as Policy from '@userActions/Policy/Policy';
 import * as Report from '@userActions/Report';
@@ -33,6 +43,7 @@ import type {IOUAction, IOUType} from '@src/CONST';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
+import type {PersonalDetails} from '@src/types/onyx';
 import type {Participant} from '@src/types/onyx/IOU';
 
 type MoneyRequestParticipantsSelectorProps = {
@@ -69,6 +80,9 @@ function MoneyRequestParticipantsSelector({
 }: MoneyRequestParticipantsSelectorProps) {
     const {translate} = useLocalize();
     const styles = useThemeStyles();
+    const [softPermissionModalVisible, setSoftPermissionModalVisible] = useState(false);
+    const [contactPermissionState, setContactPermissionState] = useState<PermissionStatus>(RESULTS.UNAVAILABLE);
+    const showImportContacts = !(contactPermissionState === RESULTS.GRANTED || contactPermissionState === RESULTS.LIMITED);
     const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState('');
     const referralContentType = iouType === CONST.IOU.TYPE.PAY ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.PAY_SOMEONE : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SUBMIT_EXPENSE;
     const {isOffline} = useNetwork();
@@ -83,6 +97,8 @@ function MoneyRequestParticipantsSelector({
     const {options, areOptionsInitialized} = useOptionsList({
         shouldInitialize: didScreenTransitionEnd,
     });
+    const [contacts, setContacts] = useState<Array<OptionsListUtils.SearchOption<PersonalDetails>>>([]);
+    const [textInputAutoFocus, setTextInputAutoFocus] = useState(softPermissionModalVisible);
     const cleanSearchTerm = useMemo(() => debouncedSearchTerm.trim().toLowerCase(), [debouncedSearchTerm]);
     const offlineMessage: string = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : '';
 
@@ -108,7 +124,7 @@ function MoneyRequestParticipantsSelector({
         const optionList = OptionsListUtils.getValidOptions(
             {
                 reports: options.reports,
-                personalDetails: options.personalDetails,
+                personalDetails: options.personalDetails.concat(contacts),
             },
             {
                 betas,
@@ -132,7 +148,7 @@ function MoneyRequestParticipantsSelector({
             ...optionList,
             ...orderedOptions,
         };
-    }, [action, areOptionsInitialized, betas, didScreenTransitionEnd, iouType, isCategorizeOrShareAction, options.personalDetails, options.reports, participants]);
+    }, [action, contacts, areOptionsInitialized, betas, didScreenTransitionEnd, iouType, isCategorizeOrShareAction, options.personalDetails, options.reports, participants]);
 
     const chatOptions = useMemo(() => {
         if (!areOptionsInitialized) {
@@ -156,6 +172,16 @@ function MoneyRequestParticipantsSelector({
         return newOptions;
     }, [areOptionsInitialized, defaultOptions, debouncedSearchTerm, participants, isPaidGroupPolicy, isCategorizeOrShareAction, action]);
 
+    const inputHelperText = useMemo(
+        () =>
+            OptionsListUtils.getHeaderMessage(
+                (chatOptions.personalDetails ?? []).length + (chatOptions.recentReports ?? []).length !== 0,
+                !!chatOptions?.userToInvite,
+                debouncedSearchTerm.trim(),
+                participants.some((participant) => OptionsListUtils.getPersonalDetailSearchTerms(participant).join(' ').toLowerCase().includes(cleanSearchTerm)),
+            ),
+        [chatOptions.personalDetails, chatOptions.recentReports, chatOptions?.userToInvite, cleanSearchTerm, debouncedSearchTerm, participants],
+    );
     /**
      * Returns the sections needed for the OptionsSelector
      * @returns {Array}
@@ -203,12 +229,10 @@ function MoneyRequestParticipantsSelector({
             });
         }
 
-        const headerMessage = OptionsListUtils.getHeaderMessage(
-            (chatOptions.personalDetails ?? []).length + (chatOptions.recentReports ?? []).length !== 0,
-            !!chatOptions?.userToInvite,
-            debouncedSearchTerm.trim(),
-            participants.some((participant) => OptionsListUtils.getPersonalDetailSearchTerms(participant).join(' ').toLowerCase().includes(cleanSearchTerm)),
-        );
+        let headerMessage = '';
+        if (!showImportContacts) {
+            headerMessage = inputHelperText;
+        }
 
         return [newSections, headerMessage];
     }, [
@@ -221,19 +245,38 @@ function MoneyRequestParticipantsSelector({
         chatOptions.userToInvite,
         personalDetails,
         translate,
-        cleanSearchTerm,
+        showImportContacts,
+        inputHelperText,
     ]);
 
+    const handleContactImport = useCallback(() => {
+        contactImport().then(({contactList, permissionStatus}: ContactImportResult) => {
+            setContactPermissionState(permissionStatus);
+            const usersFromContact = getContacts(contactList);
+            setContacts(usersFromContact);
+        });
+    }, []);
+
+    useEffect(() => {
+        setSoftPermissionModalVisible(true);
+        getContactPermission().then((status) => {
+            setContactPermissionState(status);
+            if (status !== RESULTS.BLOCKED && status !== RESULTS.UNAVAILABLE) {
+                setSoftPermissionModalVisible(true);
+            }
+        });
+    }, []);
+
     /**
      * Adds a single participant to the expense
      *
      * @param {Object} option
      */
     const addSingleParticipant = useCallback(
-        (option: Participant) => {
+        (option: Participant & OptionsListUtils.Option) => {
             const newParticipants: Participant[] = [
                 {
-                    ...lodashPick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText', 'policyID'),
+                    ...lodashPick(option, 'accountID', 'login', 'isPolicyExpenseChat', 'reportID', 'searchText', 'policyID', 'text', 'phoneNumber'),
                     selected: true,
                     iouType,
                 },
@@ -343,21 +386,57 @@ function MoneyRequestParticipantsSelector({
 
     const shouldShowReferralBanner = !isDismissed && iouType !== CONST.IOU.TYPE.INVOICE && !shouldShowListEmptyContent;
 
+    const goToSettings = useCallback(() => {
+        Linking.openSettings();
+        // In the case of ios, the App reloads when we update contact permission from settings
+        // we are saving last route so we can navigate to it after app reload
+        saveLastRoute();
+    }, []);
+
     const headerContent = useMemo(() => {
-        if (!shouldDisplayTrackExpenseButton) {
-            return;
-        }
+        const importContacts =
+            showImportContacts && inputHelperText ? (
+                <View style={[styles.ph5, styles.pb5, styles.flexRow]}>
+                    <Text style={[styles.textLabel, styles.colorMuted, styles.minHeight5]}>
+                        {`${translate('common.noResultsFound')}. `}
+                        <Text
+                            style={[styles.textLabel, styles.minHeight5, styles.link]}
+                            onPress={goToSettings}
+                        >
+                            {translate('contact.importContactsTitle')}
+                        </Text>{' '}
+                        {translate('contact.importContactsExplanation')}
+                    </Text>
+                </View>
+            ) : null;
 
         // We only display the track expense button if the user is coming from the combined submit/track flow.
-        return (
+        const expenseButton = shouldDisplayTrackExpenseButton ? (
             <MenuItem
                 title={translate('iou.justTrackIt')}
                 shouldShowRightIcon
                 icon={Expensicons.Coins}
                 onPress={onTrackExpensePress}
             />
+        ) : null;
+
+        return (
+            <>
+                {importContacts}
+                {expenseButton}
+            </>
         );
-    }, [shouldDisplayTrackExpenseButton, translate, onTrackExpensePress]);
+    }, [showImportContacts, inputHelperText, shouldDisplayTrackExpenseButton, translate, onTrackExpensePress, styles, goToSettings]);
+
+    const handleSoftPermissionDeny = useCallback(() => {
+        setSoftPermissionModalVisible(false);
+    }, []);
+
+    const handleSoftPermissionGrant = useCallback(() => {
+        setSoftPermissionModalVisible(false);
+        InteractionManager.runAfterInteractions(handleContactImport);
+        setTextInputAutoFocus(true);
+    }, [handleContactImport]);
 
     const footerContent = useMemo(() => {
         if (isDismissed && !shouldShowSplitBillErrorMessage && !participants.length) {
@@ -432,27 +511,58 @@ function MoneyRequestParticipantsSelector({
         [isIOUSplit, addParticipantToSelection, addSingleParticipant],
     );
 
+    const listFooterComponent = useMemo(() => {
+        if (!showImportContacts) {
+            return null;
+        }
+        return (
+            <MenuItem
+                title={translate('contact.importContacts')}
+                icon={Expensicons.UserPlus}
+                onPress={goToSettings}
+                shouldShowRightIcon
+                style={styles.mb3}
+            />
+        );
+    }, [goToSettings, showImportContacts, styles.mb3, translate]);
+
+    const EmptySelectionListContentWithPermission = useMemo(() => {
+        return <EmptySelectionListContent contentType={iouType} />;
+    }, [iouType]);
+
     return (
-        <SelectionList
-            onConfirm={handleConfirmSelection}
-            sections={areOptionsInitialized ? sections : CONST.EMPTY_ARRAY}
-            ListItem={InviteMemberListItem}
-            textInputValue={searchTerm}
-            textInputLabel={translate('selectionList.nameEmailOrPhoneNumber')}
-            textInputHint={offlineMessage}
-            onChangeText={setSearchTerm}
-            shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
-            onSelectRow={onSelectRow}
-            shouldSingleExecuteRowSelect
-            headerContent={headerContent}
-            footerContent={footerContent}
-            listEmptyContent={<EmptySelectionListContent contentType={iouType} />}
-            headerMessage={header}
-            showLoadingPlaceholder={showLoadingPlaceholder}
-            canSelectMultiple={isIOUSplit && isAllowedToSplit}
-            isLoadingNewOptions={!!isSearchingForReports}
-            shouldShowListEmptyContent={shouldShowListEmptyContent}
-        />
+        <>
+            {softPermissionModalVisible && (
+                <ContactPermissionModal
+                    startPermissionFlow={softPermissionModalVisible}
+                    resetPermissionFlow={handleSoftPermissionDeny}
+                    onGrant={handleSoftPermissionGrant}
+                    onDeny={handleSoftPermissionDeny}
+                />
+            )}
+            <SelectionList
+                onConfirm={handleConfirmSelection}
+                sections={areOptionsInitialized ? sections : CONST.EMPTY_ARRAY}
+                ListItem={InviteMemberListItem}
+                textInputValue={searchTerm}
+                textInputLabel={translate('selectionList.nameEmailOrPhoneNumber')}
+                textInputHint={offlineMessage}
+                onChangeText={setSearchTerm}
+                shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
+                onSelectRow={onSelectRow}
+                shouldSingleExecuteRowSelect
+                headerContent={headerContent}
+                footerContent={footerContent}
+                listEmptyContent={EmptySelectionListContentWithPermission}
+                listFooterContent={listFooterComponent}
+                headerMessage={header}
+                showLoadingPlaceholder={showLoadingPlaceholder}
+                canSelectMultiple={isIOUSplit && isAllowedToSplit}
+                isLoadingNewOptions={!!isSearchingForReports}
+                shouldShowListEmptyContent={shouldShowListEmptyContent}
+                textInputAutoFocus={textInputAutoFocus}
+            />
+        </>
     );
 }
 
diff --git a/tsconfig.json b/tsconfig.json
index 869627446f09..f1d7d0282c55 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -21,9 +21,30 @@
             "@pages/*": ["./src/pages/*"],
             "@styles/*": ["./src/styles/*"],
             "@src/*": ["./src/*"],
-            "@userActions/*": ["./src/libs/actions/*"]
+            "@userActions/*": ["./src/libs/actions/*"],
+            "contacts-nitro-module": ["./modules/ContactsNitroModule"],
+            "contacts-nitro-module/*": ["./modules/ContactsNitroModule/*"]
         }
     },
-    "include": ["src", "desktop", "web", "website", "docs", "assets", "config", "tests", "jest", "__mocks__", ".github/**/*", ".storybook/**/*", "scripts"],
+    "include": [
+        "src",
+        "desktop",
+        "web",
+        "website",
+        "docs",
+        "assets",
+        "config",
+        "tests",
+        "jest",
+        "__mocks__",
+        ".github/**/*",
+        ".storybook/**/*",
+        "scripts",
+        "modules/index.ts",
+        "**/*.nitro/*.ts",
+        "**/*.nitro/*.tsx",
+        "modules/ContactsNitroModule/lib/**/*",
+        "modules/ContactsNitroModule/lib/*.d.ts"
+    ],
     "exclude": ["**/node_modules/*", "**/dist/*", ".github/actions/**/index.js", "**/docs/*"]
 }
