Skip to content
Shiny.NET
Commercial SupportGuaranteed response times on Shiny bug fixes, straight from the maintainers. Plans from US$1,500 a year.See the plans

Age Signals

IAgeSignals reads the user’s age range from the app store. On iOS and macOS 26+ it uses Apple’s Declared Age Range; on Android, Google Play Age Signals. Use it to comply with the app store accountability laws or to gate features by age.

Under these laws the stores verify age and get parental consent. The app must read the signal the store gives it and act on it. Texas SB 2420 has been in force since June 2026, when the Fifth Circuit stayed the injunctions against it. Utah, Louisiana and California (AB 1043, from 2027) have passed laws of the same shape.

GitHub GitHub stars for shinyorg/shiny
Downloads NuGet downloads for Shiny.AgeSignals
Frameworks
.NET
.NET MAUI
Operating Systems
Android
iOS
macOS
Terminal window
dotnet add package Shiny.AgeSignals
using Shiny;
builder.Services.AddAgeSignals();

Enable the Declared Age Range capability on the App ID, then add the entitlement:

<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
<CustomEntitlements Include="com.apple.developer.declared-age-range" Type="Boolean" Value="true" Visible="false" />
</ItemGroup>

Declared Age Range is Swift-only, so the package ships a small Swift framework that it embeds in your app. Nothing to add yourself. It loads on any iOS version. Before 26, every call reports NotSupported.

  • The app must be installed from Google Play. Sideloaded builds fail with AgeSignalsErrorReason.AppNotOwned.
  • Play returns its default bands, 0-12, 13-15, 16-17 and 18+. To use your own, set up to three minimum ages in Play Console; you can’t set them at runtime.

The Play Age Signals library (0.0.4) is bound inside the package and its binding is internal, so nothing extra appears in your app’s API.

using Shiny.AgeSignals;
public class AgeGate(IAgeSignals ageSignals)
{
public async Task<bool> CanUseChat()
{
try
{
var signal = await ageSignals.RequestAgeRange(13, 16, 18);
return signal.Status switch
{
AgeSignalStatus.NotSupported => true, // no store age API - your own policy applies
AgeSignalStatus.Shared => signal.IsAtLeast(16) == true, // null means unknown
_ => false // Declined, VerificationRequired
};
}
catch (AgeSignalsException)
{
return false; // the store couldn't answer - use the most protective experience
}
}
}
  • RequestAgeRange(gate1, gate2, gate3) takes Apple’s age gates: one to three ascending ages that bound the returned range. Apple may present a sheet. Google Play ignores the gates and shows its own prompt only outside the US law states; in those states users verify in the Play Store itself.
  • IsAtLeast(age) and IsAdult return bool?. null means the store can’t tell: nothing was shared, or the band straddles the age. Never read null as yes or no.
  • Nothing is cached. Consent can be revoked and a birthday can cross a band. Ask at the moments the law cares about: first launch, before a gated feature, and after an update.
AgeSignal
Status Shared, Declined, VerificationRequired (Play: the user must verify in the Play Store), NotSupported
LowerBound / UpperBound Inclusive bounds. UpperBound is null for the top band (e.g. 18+); Apple leaves LowerBound null for the lowest band
Source How the range was established, weakest first, so you can require a minimum: signal.Source >= AgeRangeSource.Confirmed
CommunicationLimits Apple: the guardian has communication limits on
SignificantChange Play: guardian approval of your significant changes (Approved, Pending, Declined)
SignificantChangeApprovedThrough Play: everything effective before this date is approved
InstallId Play: the supervised install’s id. Send it to your server to match revoked-approval notifications
AgeRangeSource Apple Google Play
SelfDeclared selfDeclared TIER_A
GuardianDeclared guardianDeclared TIER_B
Confirmed confirmed (and the deprecated 26.2 *Checked cases) TIER_C: card, email, selfie estimation, ID
IdVerified - TIER_D: government ID plus selfie, or digital ID

GetEligibility() shows no UI, so it is safe to call at startup:

var e = await ageSignals.GetEligibility();
if (e.AgeRangeRequired == true)
{
// the store says this app must get the age range for this user
}
AgeSignalsEligibility
IsSupported The platform has a store age API
IsEligible Apple isEligibleForAgeFeatures (26.2+)
AgeRangeRequired Apple declaredAgeRangeRequired (26.4+)
SignificantChangeRequiresParentalConsent Apple (26.4+)
SignificantChangeRequiresAdultNotification Apple (26.4+)

Values the platform can’t answer are null. Android has no eligibility API, so on Android everything but IsSupported is null.

Some laws require a guardian to approve significant changes to an app a child already has. On iOS 26.4+, show Apple’s acknowledgment:

await ageSignals.ShowSignificantUpdate("Adds chat with other players");

It returns false where the platform has no such sheet. On Google Play, declare significant changes in Play Console and read the guardian’s answer from AgeSignal.SignificantChange.

AgeSignalsException.Reason says why the store couldn’t answer:

Reason
Network, Transient Retry later
StoreUnavailable Play Store or Play services missing or outdated
AppNotOwned Play: not installed from Google Play
NotAvailable, InvalidAccount The store can’t provide an age range for this device or account
NoUserInterface No window or activity to present from

NativeCode carries Play’s AgeSignalsErrorCode. Gates that aren’t positive and ascending throw ArgumentOutOfRangeException.

iOS 26+ Declared Age Range. Regulatory flags from 26.4, confirmed from 26.5
macOS 26+ Declared Age Range; no significant-update sheet
Android Play Age Signals 0.0.4
Mac Catalyst, Windows, Linux, Blazor NotSupported