How do you treat extensions, widgets and intents as a public API of the product: shared modules, data contracts, versioning, testing and operations?
Predict first
An app update renames a field in the JSON snapshot that its widget reads, and users’ widgets update later than the app. What is the most robust approach?
// A: rename the field and ship, since the widget will update soon
// B: add the new field, keep the old one for a release, and make the widget decode defensively with fallbacks
// C: delete all shared data on update
- A
- B
- C
Mental model. A product with many extensions is a restaurant with a takeaway window, a delivery service and a catering van, all using the same kitchen. Menu items (intents and schemas) must keep their names, recipes must be readable by every outlet even mid-change, and someone must notice when the van breaks down, because the customer only sees that dinner did not arrive.
Once an app has a widget, a notification service extension, a share extension, a watch app, a Top Shelf and App Intents, it is no longer one program but a family of processes that share data and must keep working together across app updates, extension updates, OS updates and partial failures. The mature approach is to treat the extension surface as a public API of the product with an owner, contracts and tests. Architecture: put shared logic in lean, dependency-light shared modules (models, storage access, intent definitions, networking primitives), linked by every target, and keep UI and heavy dependencies per target; ensure the shared module does not drag in large frameworks, because every extension pays its memory. Draw the data boundary explicitly: what the app writes to the app group (a small, versioned, display-ready snapshot) and what extensions read, so an extension never needs the app’s full model or a network call to render. Contracts and versioning: version every shared schema; extensions must decode defensively (ignore unknown keys, tolerate missing ones, fall back to a placeholder), because an extension can be newer or older than the app that wrote the data, and users update at different times (and a watch app can lag the phone by a release); never change a field’s meaning, only add; keep deep-link URL formats and App Intent identifiers stable (renaming an intent or parameter breaks users’ saved shortcuts); keep the Keychain access group, app-group identifiers and bundle ids stable (changing them loses data). Limits as requirements: record each extension’s memory and time budgets and test against them (Instruments on the extension process, memory graphs, tests with large datasets and slow networks), and set CI checks for size and linked frameworks.
Operations and quality. Observability: extensions fail silently (killed, no UI), so log and report their health: counters for timeline generation success, notification-service deadline hits, intent failures and decode errors, sent through a lightweight path (written to the shared container and uploaded by the app, or MetricKit and OSLog with subsystem filters). Testing: unit-test intents and timeline providers as plain Swift (call perform() and timeline(for:in:) directly), snapshot-test widget views per family and rendering mode (including accented and redacted), integration-test the app-to-extension data flow with a shared container on a real device, test cold start with no prior state, and test upgrade paths (old data, new extension and the reverse). Release: ship the extension with the app in one binary but gate behaviour with remote flags and kill switches so a misbehaving widget or NSE can be disabled without a release; stage rollouts; and watch crash and jetsam reports per extension bundle id (they appear separately). Product and privacy: extensions expose data on the Lock Screen, in widgets, on the watch and in Spotlight, so decide per surface what is shown when the device is locked (privacySensitive, redaction), respect the user’s settings, minimise data in shared storage and keep a retention and sign-out policy (clear the shared container, Spotlight index and widget timelines on sign-out). Ownership: assign an owner for the surface, keep a short catalogue (what extensions exist, the entitlements, the data they read and the limits they run under), and review any change to shared schemas, intents or deep links as an API change. The payoff is that a widget, a share extension and a Siri shortcut stay reliable through every release, which is what users notice.
Extensions and apps update at different times, so shared schemas should change additively and be decoded defensively.
| If it changes | |
|---|---|
| Shared data schema in the app group | older extension or watch app cannot read it; defensive decoding and additive changes |
| App Intent and parameter identifiers | users’ saved shortcuts, widgets and automations stop working |
| Deep-link URL formats | widget, notification and Top Shelf taps open the wrong screen |
| App-group and keychain-group identifiers | shared data and credentials become unreachable |
| Memory and time budgets | the system silently kills the extension, which shows as blank or missing UI |
Common traps
- Changing a shared schema or intent identifier in one release. Extensions, the watch app and saved shortcuts running an older version break; add, do not rename, and decode defensively.
- No telemetry for extensions. They are killed silently, so failures go unseen; count and report health from the shared container.
They might follow up with
- What belongs in the shared data boundary? A small, versioned, display-ready snapshot the extension can render without the app’s full model or a network call.
- How do you test an extension’s limits? Attach Instruments to its process on a device and run with large data and slow networks.
Related questions
- 19.1.1 What is an app extension, how is it launched, and what limits do extensions run under?
- 19.1.2 How do app groups, shared containers and keychain access groups let an app and its extensions share data, and…
- 19.5.1 What are App Intents, App Entities and App Shortcuts, how do they replace SiriKit and Intents extensions, and…
← What is a tvOS Top Shelf extension, how do static and dynamic content…