Highlight
PermissionKit solves a narrow problem: when a child wants to start a conversation with a stranger inside an app, how does the app safely get the parent’s permission?
Core content
A child receives a first message from a stranger inside an app. Every social, chat, and game app that targets all ages runs into this. The developer needs a chain: let the child ask the parent, let the parent decide where they already are, and ship the result back to the app. Building this yourself means an account system, push, and a parent-side UI. The cost is huge.
On iOS, iPadOS, and macOS 26, Apple wires this into Messages. The new PermissionKit framework builds on Family Sharing. The child taps a button in your app, and the system opens a pre-filled message in Messages addressed to a parent or guardian in the family group. The parent can decline right from the Messages bubble, or tap in to read the full context and approve. The decision goes back to the child and is delivered to your app in the background (00:30).
Two preconditions: the user must be in a Family Sharing group, and the parent must have turned on Communication Limits for the child. If either is missing, the API returns a default response (02:22). To tell whether the current user is a child, use your own account system, or use the new Declared Age Range API shipped the same year (01:43).
Detailed content
Step one: tailor the UI for the child account. For unknown senders, hide message previews, avatars, and other content that may not be appropriate for a child by default. To check whether a handle is known, call CommunicationLimits.current.knownHandles(in:) (04:03):
import PermissionKit
let knownHandles = await CommunicationLimits.current.knownHandles(in: conversation.participants)
if knownHandles.isSuperset(of: conversation.participants) {
// Show content
} else {
// Hide content
}
Key points:
CommunicationLimits.currentis the singleton entry. Every cross-process permission query goes through it.knownHandles(in:)is async. The system runs an optimized batch query and returns the subset of the input that it recognizes.isSuperset(of:)checks whether every participant in the conversation is a known handle. One unknown participant flips you to the hide branch.- This API does not manage your own database. The docs suggest merging it with your existing contacts system.
Step two: build a PermissionQuestion. The minimum is a handle (05:15):
import PermissionKit
var question = PermissionQuestion(handles: [
CommunicationHandle(value: "dragonslayer42", kind: .custom),
CommunicationHandle(value: "progamer67", kind: .custom)
])
Key points:
CommunicationHandle.valuecan be a phone number, an email, or a custom username.kind: .custommarks a non-system identifier such as a username.- A handle alone gives the parent only an ID. There is little to decide on.
The better path is to fill in metadata so the parent sees a name and an avatar (05:38):
import PermissionKit
let people = [
PersonInformation(
handle: CommunicationHandle(value: "dragonslayer42", kind: .custom),
nameComponents: nameComponents,
avatarImage: profilePic
),
PersonInformation(
handle: CommunicationHandle(value: "progamer67", kind: .custom)
)
]
var topic = CommunicationTopic(personInformation: people)
topic.actions = [.message]
var question = PermissionQuestion(communicationTopic: topic)
Key points:
PersonInformationbundles the handle,nameComponents, andavatarImage. The more metadata, the better the parent’s decision.CommunicationTopiccarries the question.actionsdecides the wording on the parent’s side (.message,.call,.video, and so on).- The system shows the metadata to the parent in the approval UI, on the condition that you legally hold this information (05:29).
Step three: hand the question to the system. In SwiftUI, use CommunicationLimitsButton (06:25):
import PermissionKit
import SwiftUI
struct ContentView: View {
let question: PermissionQuestion<CommunicationTopic>
var body: some View {
// ...
CommunicationLimitsButton(question: question) {
Label("Ask Permission", systemImage: "paperplane")
}
}
}
Key points:
CommunicationLimitsButtonis the SwiftUI control PermissionKit provides. It binds to a question.- When the child taps it, the system shows an alert. The child confirms, or chooses in-person authorization with the Screen Time passcode.
- After the child confirms Ask, the system opens a pre-filled Messages compose window addressed to a parent in the Family Sharing group.
UIKit goes through the singleton (06:43):
import PermissionKit
import UIKit
try await CommunicationLimits.current.ask(question, in: viewController)
Key points:
ask(_:in:)is an async throws method. Pass aUIViewControllerand let the system pick where to present from.- The AppKit equivalent is
try await CommunicationLimits.current.ask(question, in: window), taking anNSWindow(06:54).
Step four: handle the parent’s reply. The parent taps approve or decline in Messages. The child’s device wakes in the background, and your app receives a callback. Listen with an AsyncSequence (07:19):
import PermissionKit
import SwiftUI
struct ChatsView: View {
@State var isShowingResponseAlert = false
var body: some View {
List {
// ...
}
.task {
let updates = CommunicationLimits.current.updates
for await update in updates {
// Received a response!
self.isShowingResponseAlert = true
}
}
}
}
Key points:
CommunicationLimits.current.updatesis an AsyncSequence. It streams the parent’s approval results.for awaitruns inside the.taskmodifier. It starts when the view appears and cancels when the view disappears.- The app is woken by a background launch. Read the update fast and write it to disk or refresh the UI.
- The decision is also delivered to the child by notification, so you do not need to fire your own local notification (09:30).
Key takeaways
-
What to do: add a “ask a parent for permission” shortcut in apps that target all ages
- Why it is worth doing: building your own parental approval system costs a lot. PermissionKit hands you the channel, the template, the push, and the receipt through Messages. The work drops from a full subsystem to a few dozen lines.
- How to start: identify child users with the Declared Age Range API or your own account system. Then drop a
CommunicationLimitsButtoninto the unknown-sender flow and wrap the current handle in aPermissionQuestion.
-
What to do: treat metadata as a conversion-rate metric
- Why it is worth doing: the name, avatar, and action type the parent sees drive the approve rate. With metadata missing, parents tend to decline outright.
- How to start: fill in
nameComponentsandavatarImagewhen buildingPersonInformation. If you only have a username, at least setCommunicationTopic.actionscorrectly to message, call, or video so the system uses the right verb.
-
What to do: sync approval results to your own server for cross-platform consistency
- Why it is worth doing: PermissionKit only covers the Apple ecosystem. Child users on the web and Android need the same protection.
- How to start: in the
for awaitoverCommunicationLimits.current.updates, post each update to your backend as the source of truth. Other clients read the toggle from the backend instead of each maintaining its own copy.
-
What to do: degrade gracefully when the family group is missing or Communication Limits is off
- Why it is worth doing: the API returns a default response. If the UI does not explain it, the child thinks the button is broken, and the parent thinks the app is silently allowing things.
- How to start: before calling ask, prompt that “the parent needs to enable Communication Limits in Settings first.” Show clear copy on the default-response branch, and offer in-person authorization with the Screen Time passcode.
Related sessions
- Deliver age-appropriate experiences in your app — full intro to the Declared Age Range API. It is a prerequisite for PermissionKit.
- Discover Apple-Hosted Background Assets — how to safely prefetch resources during background launch. Pairs with PermissionKit’s background launch.
- Dive deeper into Writing Tools — another example of system-level text capabilities. The pattern matches PermissionKit: do not reinvent the wheel, reuse the system experience.
- Explore enhancements to your spatial business app — the new visionOS 26 APIs also focus on family and child scenarios. A good lateral reference.
Comments
GitHub Issues · utterances