A React Native Native Module (Legacy Architecture) that provides access to store-level age signals, including Android Play Age Range and iOS Declared Age, to assist with state-level age verification compliance (e.g., Texas).
npm install @milkinteractive/react-native-age-range| Android | iOS |
|---|---|
| Step 1 | Step 2 | Modal |
|:------:|:------:|:-----:|
| !iOS Screenshot 1 | !iOS Screenshot 2 | !iOS Modal |
| iOS UI | Android UI |
|:------:|:----------:|
| !iOS Screenshot | !Android Screenshot |




A production-grade React Native module for verifiable age signals.
Seamlessly integrate with Apple's Declared Age Range API (iOS 26+) and Google Play Age Signals API to meet state-level age verification compliance (e.g., Texas, Utah, Louisiana) without handling sensitive PII yourself.
> โ ๏ธ COMPLIANCE NOTICE: Texas SB2420 requires apps to consume age signals from app stores starting January 1, 2026. Similar laws in Utah (May 7, 2026) and Louisiana (July 1, 2026) are also taking effect. This package provides the necessary integration for React Native apps.
Keywords: age verification, texas sb2420, age gate, parental controls, coppa compliance, react native age verification, google play age signals, ios declared age range, app store age verification, react native compliance, child safety, age appropriate design code
---
- ๐ก๏ธ Privacy-First: Leverages OS-level store APIs. No access to birthdates or PII โ only age range classifications.
- ๐ iOS Integration: Native support for DeclaredAgeRange framework (iOS 26.0+).
- ๐ค Android Integration: Official wrapper for Google Play AgeSignalsApi.
- ๐งช Mock Mode: Built-in developer tools to simulate all age scenarios on Simulators and Emulators.
- โก Zero Config Mocks: Verification logic works out-of-the-box for development.
- ๐ฑ Broad Compatibility: Works with any React Native version (0.60+) โ uses legacy native module architecture.
``mermaid`
graph TD
RN[React Native JS] -->|Standard Interface| Bridge[Native Module Bridge]
Bridge -->|Android| PlayService[Google Play Services]
Bridge -->|iOS| AppleAPI[Apple Declared Age Range]
PlayService -->|Status| Result[Verified / Supervised / Error]
AppleAPI -->|Status| Result
subgraph Privacy Shield
PlayService
AppleAPI
end
`sh`
npm install @milkinteractive/react-native-age-rangeor
yarn add @milkinteractive/react-native-age-range
1. Framework Requirements:
- iOS 26.0+ is required for the DeclaredAgeRange API to function.
- Older versions will return a fallback/unavailable response.
2. Install Pods:
`sh`
cd ios && pod install
3. Entitlements (Critical):
- You must enable the Declared Age Range capability in Xcode.
- Go to Project Target -> Signing & Capabilities -> + Capability -> Declared Age Range.
- Note: This capability typically requires a paid Apple Developer Program membership. "Personal Team" profiles may not support it.
4. โ ๏ธ Apple API Limitations:
- Minimum Range Duration: Age thresholds must create ranges of at least 2 years.
- Example: Thresholds 10, 13, 16 work because they create: Under 10, 10-12 (2 yrs), 13-15 (2 yrs), 16+.13, 14, 21
- Invalid Example: would fail because 13-14 is only 1 year.10, 13, 16
- Common working combinations: or 13, 16, 18 or 13, 17, 21.
No manual configuration required. The package automatically bundles com.google.android.play:age-signals.
- Requirement: Device must have Google Play Services installed.
`typescript
import {
getAndroidPlayAgeRangeStatus,
requestIOSDeclaredAgeRange,
isIOSEligibleForAgeFeatures,
isAndroidEligibleForAgeFeatures,
} from '@milkinteractive/react-native-age-range';
import { Platform } from 'react-native';
// ๐ค Android Example
async function checkAndroid() {
if (Platform.OS !== 'android') return;
const result = await getAndroidPlayAgeRangeStatus();
if (result.userStatus === 'OVER_AGE') {
// โ
User is a verified adult
grantAccess();
} else if (result.userStatus === 'UNDER_AGE') {
// โ ๏ธ User is supervised (e.g. Family Link)
// result.ageLower and result.ageUpper are available (e.g., 13-17)
enableRestrictedMode(result.ageLower, result.ageUpper);
} else {
// โ Verification failed or unknown
handleError(result.error);
}
}
// ๐ iOS Example
async function checkIOS() {
if (Platform.OS !== 'ios') return;
// Request discrete age signals (e.g. 13+, 17+, 21+)
const result = await requestIOSDeclaredAgeRange(13, 17, 21);
if (result.error) {
// โ API error (e.g., iOS < 26.0, missing entitlement)
console.error('iOS Signal Failed:', result.error);
return;
}
if (result.status === 'sharing') {
// โ
User shared their age range
console.log(Confirmed Range: ${result.lowerBound} - ${result.upperBound});Declaration: ${result.ageRangeDeclaration}
console.log();
} else {
// โ User declined
console.log('User declined to share age range');
}
}
// ๐ Check Eligibility (should age verification be shown?)
async function checkEligibility() {
const result = Platform.OS === 'ios'
? await isIOSEligibleForAgeFeatures()
: await isAndroidEligibleForAgeFeatures();
if (result.error) {
console.log('Eligibility check failed:', result.error);
return false;
}
if (result.isEligible) {
// User is in a region requiring age verification (e.g., Texas)
// Proceed with age verification flow
return true;
}
// User is not subject to age verification requirements
return false;
}
`
This package also supports Apple's PermissionKit framework for apps that need parental consent flows. Use these APIs when the parentalControls flags from requestIOSDeclaredAgeRange() indicate they're needed.
After calling requestIOSDeclaredAgeRange(), check the parentalControls object:
`typescript
const ageResult = await requestIOSDeclaredAgeRange(13, 17, 21);
if (ageResult.parentalControls?.significantAppChangeApprovalRequired) {
// App updates require parental approval โ use requestIOSSignificantChangeApproval()
}
if (ageResult.parentalControls?.communicationLimits) {
// Communication with unknown contacts requires approval โ use requestIOSCommunicationPermission()
}
`
When your app has significant updates that require parental consent:
`typescript
import { requestIOSSignificantChangeApproval } from '@milkinteractive/react-native-age-range';
const result = await requestIOSSignificantChangeApproval();
if (result.status === 'pending') {
// Request sent to parent/guardian via Messages
// Show "waiting for approval" UI
} else if (result.error) {
console.error('Significant change request failed:', result.error);
}
`
For apps with chat/messaging features, request permission to communicate with unknown contacts:
`typescript
import {
getIOSKnownCommunicationHandles,
requestIOSCommunicationPermission,
} from '@milkinteractive/react-native-age-range';
// 1. Check which contacts are already known (approved)
const knownResult = await getIOSKnownCommunicationHandles([
{ handle: 'user@example.com', handleKind: 'email' },
{ handle: 'gamer123', handleKind: 'custom' },
]);
// 2. Request permission for unknown contacts
const unknownContacts = contacts.filter(
c => !knownResult.knownHandles.includes(c.handle)
);
if (unknownContacts.length > 0) {
const permResult = await requestIOSCommunicationPermission(
unknownContacts.map(c => ({
handle: c.handle,
handleKind: 'custom',
displayName: c.displayName, // Shown to parent in approval request
})),
['message', 'call'] // Requested communication actions
);
}
`
- iOS 26.0+ for Significant Change API
- iOS 26.2+ for Communication Limits API
- Family Sharing must be enabled on the device
- Communication Limits must be enabled by parent/guardian
- No additional entitlements required (uses system permissions)
> Note: PermissionKit features require a real device with Family Sharing configured. Simulator testing has limited functionality.
Testing store APIs usually requires signed production builds. This library includes a powerful Mock Mode for development.
`typescript`
// Simulate a Supervised User (Age 13-17)
const mockResult = await getAndroidPlayAgeRangeStatus({
isMock: true,
mockStatus: 'UNDER_AGE',
mockAgeLower: 13,
mockAgeUpper: 17
});
| Parameter | Type | Default | Description |
|---|---|---|---|
| config.isMock | boolean | false | Enable to return fake data. |config.mockStatus
| | enum | 'OVER_AGE' | See status values below |config.mockErrorCode
| | number | null | Simulate API error code (e.g. -1). |
Returns: PromiseuserStatus
- : User verification status:OVER_AGE
- - Verified adult (18+)UNDER_AGE
- - Supervised account (child/teen)UNDER_AGE_APPROVAL_PENDING
- - Supervised, parent hasn't approved pending significant changesUNDER_AGE_APPROVAL_DENIED
- - Supervised, parent denied approval for significant changesUNKNOWN
- - Status could not be determinedinstallId
- : Unique installation identifierageLower
- / ageUpper: Age range bounds (for supervised users)mostRecentApprovalDate
- : Date of last approved significant changeerror
- / errorCode: Error information if request failed
| Parameter | Type | Description |
|---|---|---|
| threshold[1-3] | number | Age thresholds to verify. Must create 2+ year ranges. |
โ ๏ธ Apple API Constraint: Thresholds must result in age ranges of at least 2 years duration.
- โ
Valid: 10, 13, 16 โ Creates ranges: <10, 10-12, 13-15, 16+13, 17, 21
- โ
Valid: โ Creates ranges: <13, 13-16, 17-20, 21+13, 14, 21
- โ Invalid: โ 13-14 is only 1 year (API will reject)
Returns: Promisestatus
- : 'sharing' | 'declined' | nulllowerBound
- : number | null - Lower age of user's rangeupperBound
- : number | null - Upper age of user's rangeageRangeDeclaration
- : How the age was verified:selfDeclared
- - User declared their own ageguardianDeclared
- - Guardian set the age (children in iCloud family)governmentIDChecked
- / guardianGovernmentIDChecked - Verified via government IDpaymentChecked
- / guardianPaymentChecked - Verified via payment methodcheckedByOtherMethod
- / guardianCheckedByOtherMethod - Other verificationparentalControls
- : { communicationLimits?: boolean, significantAppChangeApprovalRequired?: boolean } - Active parental controlserror
- : string | null - Error message if API unavailable or request failed
Returns: PromiseisEligible
- : boolean - true if user should be shown age verificationerror
- : string | null - Error message if API unavailable
Requirements: iOS 26.2+ for accurate results. Returns isEligible: false with error on older versions.
Returns: PromiseisEligible
- : boolean - true if user should be shown age verification (in Texas, Utah, Louisiana, etc.)error
- : string | null - Error message if API unavailable
Note: Makes a lightweight API call to determine eligibility.
Requirements: iOS 26.0+
Returns: Promisestatus
- : 'approved' | 'denied' | 'pending' | null - Current approval statuserror
- : string | null - Error message if request failed
Usage: Call when parentalControls.significantAppChangeApprovalRequired is true.
Requirements: iOS 26.2+
| Parameter | Type | Description |
|---|---|---|
| contacts | CommunicationContact[] | Contacts to request permission for |actions
| | CommunicationAction[] | Optional: ['message'], ['call'], ['video'] (default: ['message']) |
CommunicationContact:
- handle: string - Unique identifier (phone, email, username)handleKind
- : 'phoneNumber' | 'email' | 'custom'displayName
- : string (optional) - Shown to parent in approval request
Returns: Promisegranted
- : boolean - Whether the permission dialog was shown successfullyerror
- : string | null - Error message if request failed
Requirements: iOS 26.2+
| Parameter | Type | Description |
|---|---|---|
| handles | CommunicationContact[] | Handles to check |
Returns: PromiseknownHandles
- : string[] - Array of handle values that are known/approvederror
- : string | null - Error message if check failed
| Error Code | Meaning | Solution |
|---|---|---|
| Error 0 | Missing Entitlement | 1. Add Declared Age Range` capability in Xcode.
2. Ensure you are using a Paid Developer Account. Personal teams often block this API.
3. Real Device Only: This API does NOT work on Simulators. |
| Error -1 | API Unavailable | Device is running an iOS version older than 26.0. |
| Code | Error | Description | Retryable |
|---|---|---|---|
| -1 | API_NOT_AVAILABLE | Play Store app version might be old. | Yes |
| -2 | PLAY_STORE_NOT_FOUND | No Play Store app found. | Yes |
| -3 | NETWORK_ERROR | No network connection. | Yes |
| -4 | PLAY_SERVICES_NOT_FOUND | Play Services unavailable or old. | Yes |
| -5 | CANNOT_BIND_TO_SERVICE | Failed to bind to Play Store service. | Yes |
| -6 | PLAY_STORE_VERSION_OUTDATED | Play Store app needs update. | Yes |
| -7 | PLAY_SERVICES_VERSION_OUTDATED | Play Services needs update. | Yes |
| -8 | CLIENT_TRANSIENT_ERROR | Transient client error. Retry with backoff. | Yes |
| -9 | APP_NOT_OWNED | App not installed by Google Play. | No |
| -100 | INTERNAL_ERROR | Unknown internal error. | No |
Apple iOS:
- Declared Age Range Framework - Official Apple Developer Documentation
- WWDC25: Deliver age-appropriate experiences in your app - Session video explaining implementation
- PermissionKit Framework - Official PermissionKit Documentation
- WWDC25: Enhance child safety with PermissionKit - PermissionKit session video
Google Android:
- Play Age Signals Overview - Introduction and concepts
- Use Play Age Signals API - Implementation guide
- Test your Play Age Signals API integration - Testing with FakeAgeSignalsManager
- Play Age Signals Release Notes - Version history and updates
---
See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT
---
Made with โค๏ธ for React Native developers navigating age verification compliance.