App Store Connect, TestFlight and App Review
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages. |
A signed archive on a Mac is not yet an app anyone can install. This page follows FieldNotes from that archive to the App Store: creating the app record in App Store Connect, uploading and processing the build, testing it with TestFlight, filling in the product page’s privacy, accessibility and age-rating declarations, passing App Review and releasing in phases. It uses this section’s 2026 baseline: iOS, iPadOS, macOS, watchOS and visionOS 27, Xcode 27 and Swift 6.4, with a deployment minimum of 26. App Store rules change several times a year, so every requirement below gives the date it applies from and its official source, checked on 2026-09-24.
The signing that makes an upload possible (certificates, the App Store Connect provisioning profile,
FieldNotes.entitlements, Info.plist) is covered on
Membership, Signing, Capabilities and Info.plist, and
automating this page’s steps on
CI/CD with Xcode Cloud and the Command Line. The page adds
ExportOptions.plist and two small Swift helpers to FieldNotes: FieldNotesInstallSource, which tells the app
whether it came from the App Store, TestFlight or an alternative source, and ReviewPromptModifier, which asks for a
rating at a sensible moment. The React Native view of the same pipeline is in the iOS Release Builds section of
Building and Publishing.
From Archive to the App Store
Every FieldNotes release follows the same path. A build is archived and uploaded, Apple processes it, the team tests it internally, a wider group tests it after a light beta review, and the final build is submitted for App Review and released gradually:
Figure: The release sequence
The steps run in this order because each gate is cheaper than the next. Processing catches signing and packaging errors in minutes. Internal testers need no review. External testing needs a beta review, which is lighter than App Review and usually required only for the first build of each version. App Review is last, and phased release limits the damage of a bug that got past all of them.
Platform availability
| API or feature | iOS | iPadOS | macOS | watchOS | visionOS |
|---|---|---|---|---|---|
App Store Connect app record and upload |
Yes |
Yes (iOS platform) |
Yes |
Yes (with the iOS app) |
Yes |
TestFlight |
Yes |
Yes |
Yes |
Yes (installed with the iPhone app) |
Yes |
In-app screenshot feedback (TestFlight 2.3 or later) |
Yes |
Yes |
Yes |
Not available (email) |
Yes |
Phased release for automatic updates |
Yes |
Yes |
Yes |
Yes (with the iOS app) |
Not listed by Apple |
Accessibility Nutrition Labels on the product page |
New in 26 |
New in 26 |
New in 26 |
New in 26 |
New in 26 |
|
Yes |
Yes |
Yes |
Yes |
Yes |
|
Yes |
Yes |
Yes |
Not available |
Yes |
MarketplaceKit |
Yes (EU only) |
Yes (EU only) |
Not available |
Not available |
Not available |
The App Record
Before the first upload, an Account Holder, Admin or App Manager creates the app record in App Store Connect (Apps > + > New App). The record holds everything that is not in the binary: the platforms, the name, the primary language, the bundle ID it accepts builds for, an internal SKU, and which team members may access it. FieldNotes is one record with the iOS, macOS and visionOS platforms, so one purchase covers every device; watch-only apps count as part of the iOS platform. The Account Holder must have accepted the latest agreements, or the New App button does nothing useful.
| Field | FieldNotes |
|---|---|
Platforms |
iOS, macOS, visionOS (the watch app ships inside the iOS app) |
Name |
Field Notes (unique on the App Store per localization) |
Primary language |
English (U.S.); other localizations are added under App Information |
Bundle ID |
|
SKU |
|
User access |
Full access |
The product page itself (subtitle, description, keywords, screenshots, the privacy policy and support URLs) is localized per language in the same record, separately from the app’s String Catalogs (see Localization with String Catalogs). New in 2026, product pages gain header and search-result asset placements and a preview tool; see WWDC26 — Enhance your presence on the App Store.
Everything in App Store Connect is also available through the App Store Connect API, a REST API that CI and scripts use instead of the website. Each request carries a JSON Web Token signed with ES256 by a private key downloaded from Users and Access > Integrations. The token’s header and payload look like this, with placeholders for the team’s own values; tokens must expire within 20 minutes:
{ "alg": "ES256", "kid": "<KEY_ID>", "typ": "JWT" }
{
"iss": "<ISSUER_ID>",
"iat": 1790200000,
"exp": 1790201000,
"aud": "appstoreconnect-v1",
"scope": ["GET /v1/apps?filter[bundleId]=com.example.fieldnotes"]
}
# ASC_TOKEN holds a JWT signed with the .p8 key; the key file never enters the repository.
# -g (--globoff) stops curl from treating the brackets in filter[bundleId] as a URL glob.
curl -sS -g --fail-with-body -H "Authorization: Bearer $ASC_TOKEN" \
"https://api.appstoreconnect.apple.com/v1/apps?filter[bundleId]=com.example.fieldnotes" \
| jq '.data[0].id'
# "<APP_ID>"
See Apple Developer Documentation — Generating Tokens for API Requests and Apple Developer Documentation — List apps.
Needs a paid membership and an API key with a role that allows the call (App Manager or Admin for most of this
page). The optional scope claim limits what a leaked token can do, which is worth setting for CI.
Uploading a Build
Archive, Organizer and Distribute App
An archive is a Release build plus its debug symbols, stored as an .xcarchive. In Xcode, choose the FieldNotes
scheme and the Any iOS Device destination (or My Mac for the Mac build), then Product > Archive. The archive
opens in the Organizer (Window > Organizer), where:
-
Validate App runs the automated upload checks without uploading: signature, bundle ID, entitlements against the profile, icons, the privacy manifest’s required-reason declarations.
-
Distribute App re-signs the archive for the chosen method and uploads it. App Store Connect makes the build available for TestFlight and App Store submission; TestFlight Internal Only marks it so it can never go further than the internal group, which is useful for experimental branches.
-
The custom path lets you choose whether to upload symbols (keep it on: crash reports are symbolicated with them) and whether Xcode may increment the build number when it collides with an earlier upload.
Every upload of the same version needs a new CFBundleVersion. FieldNotes keeps MARKETING_VERSION = 2.2.0 for
the whole release and bumps CURRENT_PROJECT_VERSION (42, 43, …) for each TestFlight build.
xcodebuild -exportArchive
The Organizer’s choices can be written down in an export options property list, so a script produces the same
result. FieldNotes keeps its ExportOptions.plist next to the project:
<?xml version="1.0" encoding="UTF-8"?>
<!-- ExportOptions.plist: upload an App Store Connect build, signed automatically by the team -->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store-connect</string>
<key>destination</key>
<string>upload</string> <!-- "export" writes the package to disk instead -->
<key>teamID</key>
<string>ABCDE12345</string>
<key>signingStyle</key>
<string>automatic</string>
<key>uploadSymbols</key>
<true/>
<key>manageAppVersionAndBuildNumber</key>
<false/> <!-- the build number comes from CURRENT_PROJECT_VERSION -->
<key>testFlightInternalTestingOnly</key>
<false/>
</dict>
</plist>
# 1. Archive the iOS build (the watch app and extensions are embedded automatically).
xcodebuild -project FieldNotes.xcodeproj -scheme FieldNotes -configuration Release \
-destination 'generic/platform=iOS' -archivePath build/FieldNotes.xcarchive archive
# 2. Re-sign for App Store Connect and upload, authenticating with an API key.
xcodebuild -exportArchive -archivePath build/FieldNotes.xcarchive \
-exportOptionsPlist ExportOptions.plist -exportPath build/export \
-allowProvisioningUpdates \
-authenticationKeyPath "$RUNNER_TEMP/AuthKey_<KEY_ID>.p8" \
-authenticationKeyID "<KEY_ID>" -authenticationKeyIssuerID "<ISSUER_ID>"
See Apple Developer Documentation — Distributing your app for beta testing and releases and Apple Developer Documentation — Upload builds.
xcodebuild -help lists every export option. For a manually signed release, set signingStyle to manual and
add a provisioningProfiles dictionary mapping each bundle ID (the app, .watchkitapp, .widgets,
.watchkitapp.widgets, .share, .notificationservice) to its App Store Connect profile. The Mac build is archived
separately with -destination 'generic/platform=macOS'; the Developer ID export for distribution outside the store
is on Distributing Mac Apps with Developer ID and Notarization.
Besides Xcode and xcodebuild, App Store Connect accepts uploads from Transporter (a Mac app and a
command-line tool), from altool, and through the App Store Connect API; Xcode Cloud uploads as a workflow
post-action. Apple’s upload page lists which Xcode versions may build each kind of target; currently iOS, tvOS and
visionOS apps must be built with Xcode 26 or later (see the dated requirements below).
Processing
After the upload, App Store Connect processes the build: it checks it again, prepares thinned variants for each device, and symbolicates. That takes minutes to an hour. The build then appears under TestFlight with status VALID, or an email explains why it is INVALID. Scripts can wait for it:
# The newest builds of FieldNotes and their processing state.
curl -sS -G -H "Authorization: Bearer $ASC_TOKEN" \
"https://api.appstoreconnect.apple.com/v1/builds" \
--data-urlencode "filter[app]=<APP_ID>" \
--data-urlencode "sort=-uploadedDate" --data-urlencode "limit=3" \
| jq -r '.data[] | "\(.attributes.version) \(.attributes.processingState)"'
# 43 PROCESSING
# 42 VALID
# 41 VALID
Once processed, a build asks one question if the binary doesn’t answer it: export compliance. FieldNotes sets
ITSAppUsesNonExemptEncryption to NO in its Info.plist (see
Membership, Signing, Capabilities and Info.plist), so
its builds go straight to Ready to Submit.
TestFlight
TestFlight distributes beta builds through the TestFlight app and collects feedback. Builds stay installable for 90 days. Testers are organized in groups, and each group gets specific builds:
| Internal testing | External testing | |
|---|---|---|
Who |
Up to 100 App Store Connect users of the team (with a role that gives access to the app) |
Up to 10,000 people per app, invited by email or through a public link |
Review |
None: testers get the build as soon as it is processed |
TestFlight App Review: required for the first build of a version; later builds may skip a full review |
Groups |
FieldNotes Team |
Field Researchers (public link), Translators DE, Translators JA |
Limits |
Builds uploaded as TestFlight Internal Only can only go to internal groups |
An internal group must exist first; up to six builds submitted for beta review per 24 hours |
See Apple Developer Documentation — TestFlight overview and Apple Developer Documentation — Invite external testers.
Before inviting external testers, fill in the test information: a beta description, a feedback email, and, for each build, What to Test, which can be localized. A public link lets anyone join a group up to a tester limit you set, and tester criteria (device family, OS version) keep out testers whose devices the build doesn’t support. FieldNotes uses per-language groups for its translators: machine-translated languages go to native speakers in Translators DE and Translators JA before a release ships them, as Localization with String Catalogs recommends.
Adding a build to an external group and submitting it for beta review are two API calls:
API=https://api.appstoreconnect.apple.com/v1
# Add build 42 to the "Field Researchers" external group.
curl -sS -X POST -H "Authorization: Bearer $ASC_TOKEN" -H "Content-Type: application/json" \
"$API/betaGroups/<BETA_GROUP_ID>/relationships/builds" \
-d '{ "data": [ { "type": "builds", "id": "<BUILD_ID>" } ] }'
# Submit it for TestFlight App Review.
curl -sS -X POST -H "Authorization: Bearer $ASC_TOKEN" -H "Content-Type: application/json" \
"$API/betaAppReviewSubmissions" \
-d '{ "data": { "type": "betaAppReviewSubmissions",
"relationships": { "build": { "data": { "type": "builds", "id": "<BUILD_ID>" } } } } }'
See Apple Developer Documentation -- Add builds to a beta group and Apple Developer Documentation — Submit an app for beta review.
Feedback. On iOS, iPadOS, macOS and visionOS, testers with TestFlight 2.3 or later send feedback from the TestFlight app or by taking a screenshot inside the beta app; crashes are sent with the tester’s comment. Both appear under TestFlight > Feedback in App Store Connect and in Xcode’s Organizer (see Logging, MetricKit, Crash Reports and Organizer). Testers on older systems send email to the feedback address.
TestFlight builds behave slightly differently. In-App Purchases always run in the sandbox environment, so
testers are never charged (see
In-App Purchases and Subscriptions with StoreKit 2), and
requestReview does nothing. FieldNotes checks where it was installed from, both to show a "screenshot to send
feedback" hint to testers and, in the EU, to adapt to alternative distribution:
// FieldNotes/Support/FieldNotesInstallSource.swift
import StoreKit
import os
#if os(iOS) && canImport(MarketplaceKit)
import MarketplaceKit
#endif
/// Where this copy of FieldNotes came from.
enum FieldNotesInstallSource: Sendable, Equatable {
case appStore
case testFlightOrDevelopment // StoreKit's sandbox environment
case xcodeStoreKitTesting // a StoreKit configuration file in Xcode
case alternativeMarketplace(String)
case web
case unknown
private static let log = Logger(subsystem: "com.example.fieldnotes", category: "ui")
static func current() async -> FieldNotesInstallSource {
#if os(iOS) && canImport(MarketplaceKit)
// iOS and iPadOS 17.4 and later: the distributor, including EU alternative sources.
do {
switch try await AppDistributor.current {
case .appStore: return .appStore
case .testFlight: return .testFlightOrDevelopment
case .marketplace(let bundleID): return .alternativeMarketplace(bundleID)
case .web: return .web
case .other: break // development builds and other sources: ask StoreKit
@unknown default: break
}
} catch {
log.error("AppDistributor unavailable: \(error.localizedDescription, privacy: .public)")
}
#endif
do {
let result = try await AppTransaction.shared
let transaction: AppTransaction
switch result {
case .verified(let verified):
transaction = verified
case .unverified(let unverified, let error):
log.error("Unverified app transaction: \(error.localizedDescription, privacy: .public)")
transaction = unverified // good enough to pick UI; never to unlock content
}
switch transaction.environment {
case .production: return .appStore
case .sandbox: return .testFlightOrDevelopment
case .xcode: return .xcodeStoreKitTesting
default: return .unknown
}
} catch {
log.error("AppTransaction unavailable: \(error.localizedDescription, privacy: .public)")
return .unknown
}
}
}
AppTransaction.shared may need the network and throws when the person isn’t signed in to the App Store, so the
result is computed once in a .task and cached, and every failure path logs and falls back to .unknown rather
than blocking the UI. The distinction is for presentation only: whether the person owns Pro is still decided by
StoreManager's verified transactions.
Store Listing and Compliance
Three declarations in App Store Connect are shown on the product page and are required before submission (the accessibility one is voluntary for now). Each should be answered from the code, not from memory.
App Privacy details
The App Privacy section (the privacy "nutrition label") lists the data types the app and its third-party code
collect, whether each is linked to the person’s identity, whether it is used for tracking, and why. It is required
for new apps and updates, and it can be changed at any time without a new build. The input is the privacy report:
in the Organizer, Generate Privacy Report on an archive merges the PrivacyInfo.xcprivacy files of the app, its
extensions and its SDKs. FieldNotes answers:
| Data type | Linked to the user | Tracking | Purpose |
|---|---|---|---|
Precise Location (where a note was taken) |
Yes |
No |
App Functionality |
Photos or Videos, Other User Content (notes, attachments synced to the FieldNotes server) |
Yes |
No |
App Functionality |
Email Address (Sign in with Apple, possibly a relay address) |
Yes |
No |
App Functionality |
Crash Data, Performance Data (MetricKit payloads sent to the team) |
No |
No |
App Functionality, Analytics |
Data that stays on the device, or that only syncs through the person’s own iCloud account (the CloudKit private database), is not "collected" by the developer; what FieldNotes sends to its own server is. The manifest and its reason codes are on Security and Privacy.
Accessibility Nutrition Labels
The Accessibility Nutrition Labels tell people, before they download, which of nine accessibility features the app supports for its common tasks, per device type. They appear on product pages viewed on the 26 releases and later. As of 2026-09-24, Apple’s help page says providing them is voluntary to start and will become required for new apps and updates over time, with no date announced. An Account Holder, Admin or App Manager fills them in under App Accessibility, one device type at a time. FieldNotes declares:
| Label | iPhone | iPad | Mac | Apple Watch | Apple Vision Pro |
|---|---|---|---|---|---|
VoiceOver |
Yes |
Yes |
Yes |
Yes |
Yes |
Voice Control |
Yes |
Yes |
Yes |
Not offered |
Yes |
Larger Text |
Yes |
Yes |
Not offered |
Yes |
Yes |
Dark Interface |
Yes |
Yes |
Yes |
Yes |
Yes |
Differentiate Without Color Alone |
Yes |
Yes |
Yes |
Yes |
Yes |
Sufficient Contrast |
Yes |
Yes |
Yes |
Yes |
Yes |
Reduced Motion |
Yes |
Yes |
Yes |
Yes |
Yes |
Captions, Audio Descriptions |
No |
No |
No |
No |
No |
How each label is evaluated and tested in FieldNotes, and why it doesn’t claim Captions or Audio Descriptions for user-recorded videos, is on Accessibility. A device type left unanswered still shows the section, saying the developer hasn’t indicated support.
Age rating
Every app answers the age-rating questionnaire under App Information: content descriptors with their frequency, in-app controls (parental controls, age assurance) and capabilities. App Store Connect turns the answers into Apple’s age rating and any regional ratings. An unrated app can’t be published on the App Store. Two dated changes apply in 2026:
-
Updated questions, required since January 31, 2026. Apple moved to a new age-rating system shown on devices running the 26 releases, and developers had to answer the updated questions by January 31, 2026 to avoid an interruption when submitting updates. Source: Apple Developer — Upcoming Requirements ("Age Rating Updates").
-
Social media questions, added July 9, 2026 and required from September 2026. To support Time Allowances in iOS 27, iPadOS 27 and macOS 27, the questionnaire asks whether the app redistributes, amplifies or lets people interact with user-generated content through a feed. Responses are required for new apps and updates, and for notarization for alternative distribution, starting in September 2026. Source: Apple Developer News — Age rating questionnaire now includes social media questions.
FieldNotes has no objectionable content, no feed and no public profiles. People share notebooks only with invited collaborators, so it answers No to the social media capability, declares no user-generated content that strangers can see, and gets the lowest rating.
App Review
The App Review Guidelines
App Review checks every version against the App Review Guidelines, arranged in five sections. The page was last updated on June 8, 2026, together with the Developer Program License Agreement. That revision changed the introduction’s kid and teen safety guidance and guidelines 1.2, 4.3(a), 4.3(b) and 4.5.3 (Live Activities must not be used for spam, phishing or unsolicited messages). Source: App Review Guidelines ("Last Updated: June 8, 2026") and Apple Developer News — Updated Apple Developer Program License Agreement and App Review Guidelines now available.
| Section | What it covers | What it means for FieldNotes |
|---|---|---|
1. Safety |
Objectionable content, user-generated content (1.2), kids, physical harm, developer information |
Shared notebooks are invitation-only; a way to leave a shared notebook and report abuse is still provided. |
2. Performance |
App completeness (2.1), beta testing belongs in TestFlight (2.2), accurate metadata (2.3), hardware compatibility, public APIs only (2.5.1) |
A demo account for review, the backend switched on, screenshots that show the real app, and every new feature described in the review notes. |
3. Business |
Payments: digital unlocks use In-App Purchase (3.1.1), subscriptions, other purchase methods |
FieldNotes Pro is sold with StoreKit; see In-App Purchases and Subscriptions with StoreKit 2. |
4. Design |
Copycats, minimum functionality (4.2), spam (4.3), extensions, login services (4.8) |
Widgets, Live Activities and notifications follow 4.5.3; the only sign-in is Sign in with Apple, which satisfies 4.8. |
5. Legal |
Privacy (5.1): privacy policy (5.1.1(i)), account sign-in and deletion (5.1.1(v)), data use and sharing (5.1.2); intellectual property |
A privacy policy link in the app and in App Store Connect; accounts can be deleted inside the app. |
Guideline 4.8 is often misquoted: an app that uses a third-party or social login for the primary account must also offer an equivalent login option with the privacy properties the guideline lists; Sign in with Apple is one such option. Guideline 5.1.1(v) is not optional: if an app lets people create an account, it must let them delete it from within the app.
Submitting for review
To submit, create the App Store version (2.2.0) in the record, choose the build, fill in What’s New, and complete App Review Information: contact details, a demo account for anything behind sign-in, and notes that explain non-obvious features. For FieldNotes the notes explain how to create a shared notebook with the second demo account and where Pro features are. In-App Purchases, in-app events and custom product pages can travel in the same submission; each platform can have one app version under review at a time. The same flow through the API is a review submission with items:
API=https://api.appstoreconnect.apple.com/v1
# 1. Open a review submission for the iOS platform of FieldNotes.
curl -sS -X POST -H "Authorization: Bearer $ASC_TOKEN" -H "Content-Type: application/json" \
"$API/reviewSubmissions" \
-d '{ "data": { "type": "reviewSubmissions", "attributes": { "platform": "IOS" },
"relationships": { "app": { "data": { "type": "apps", "id": "<APP_ID>" } } } } }'
# 2. Add the App Store version (its build is already selected) as an item.
curl -sS -X POST -H "Authorization: Bearer $ASC_TOKEN" -H "Content-Type: application/json" \
"$API/reviewSubmissionItems" \
-d '{ "data": { "type": "reviewSubmissionItems", "relationships": {
"reviewSubmission": { "data": { "type": "reviewSubmissions", "id": "<SUBMISSION_ID>" } },
"appStoreVersion": { "data": { "type": "appStoreVersions", "id": "<VERSION_ID>" } } } } }'
# 3. Submit it.
curl -sS -X PATCH -H "Authorization: Bearer $ASC_TOKEN" -H "Content-Type: application/json" \
"$API/reviewSubmissions/<SUBMISSION_ID>" \
-d '{ "data": { "type": "reviewSubmissions", "id": "<SUBMISSION_ID>", "attributes": { "submitted": true } } }'
See Apple Developer Documentation — Overview of submitting for review and Apple Developer Documentation — Create a Review Submission.
If App Review finds a problem, the submission is rejected with a message citing the guideline. Reply in App Store Connect’s App Review section, fix and resubmit, or, if you disagree, appeal. Metadata-only problems can be fixed without a new build.
Releasing
Release options and phased release
Each version chooses how it goes live once approved: manually (the version waits in Pending Developer Release), automatically right after approval, or automatically after a date. For updates, phased release for automatic updates spreads the release over seven days to a random sample of people who have automatic updates on:
| Day 1 | Day 2 | Day 3 | Day 4 | Day 5 | Day 6 | Day 7 |
|---|---|---|---|---|---|---|
1% |
2% |
5% |
10% |
20% |
50% |
100% |
Anyone can still download the update manually from the App Store during the rollout. A phased release can be paused for up to 30 days in total, any number of times, and Release to All Users ends it early. Apple’s help page lists it for iOS, macOS and tvOS. FieldNotes uses it for every update and watches crash and hang rates in the Organizer during the first three days. Turning it on for a version through the API:
curl -sS -X POST -H "Authorization: Bearer $ASC_TOKEN" -H "Content-Type: application/json" \
"https://api.appstoreconnect.apple.com/v1/appStoreVersionPhasedReleases" \
-d '{ "data": { "type": "appStoreVersionPhasedReleases",
"attributes": { "phasedReleaseState": "INACTIVE" },
"relationships": { "appStoreVersion":
{ "data": { "type": "appStoreVersions", "id": "<VERSION_ID>" } } } } }'
See Apple Developer Documentation — Release a version update in phases and Apple Developer Documentation — Create an app store version phased release.
INACTIVE means phased release is scheduled and starts when the version is released; the state then moves through
ACTIVE (or PAUSED) to COMPLETE. If the app is removed from sale during a phased release, the rollout stops and
can’t be resumed for that version.
Asking for ratings
Ratings accumulate per version, and a release is a good moment to ask satisfied people for one. StoreKit decides
whether the prompt actually appears (at most three times in 365 days) and it never appears in TestFlight builds, so
the app only chooses the moment: never right after launch or on a button tap, but after a success. FieldNotes asks
once per version, after the person has created ten notes in that version. It counts notes the person creates, not
store.notes.count: loading the library at launch fills the list in one step, and a sync can add notes from other
devices, and neither is a moment of success:
// FieldNotes/Support/ReviewPromptModifier.swift
import SwiftUI
import StoreKit
import FieldNotesModel
import FieldNotesServices
#if !os(watchOS)
enum ReviewPrompt {
static let createdNotesKey = "notesCreatedSinceReviewPrompt"
static let threshold = 10
}
extension NoteStore {
/// The New Note button calls this instead of `add(_:)`; loading and syncing don't go through it.
func addCreatedByPerson(_ note: Note) {
add(note)
let defaults = UserDefaults.standard
defaults.set(defaults.integer(forKey: ReviewPrompt.createdNotesKey) + 1,
forKey: ReviewPrompt.createdNotesKey)
}
}
struct ReviewPromptModifier: ViewModifier {
@Environment(\.requestReview) private var requestReview
@AppStorage(ReviewPrompt.createdNotesKey) private var createdNotes = 0
@AppStorage("lastReviewPromptVersion") private var lastPromptedVersion = ""
private var currentVersion: String { BuildIdentity().version }
func body(content: Content) -> some View {
content.onChange(of: createdNotes) { _, newCount in
guard newCount >= ReviewPrompt.threshold,
lastPromptedVersion != currentVersion else { return }
lastPromptedVersion = currentVersion
createdNotes = 0 // the next version counts from zero again
requestReview()
}
}
}
extension View {
func promptsForReviewAfterTenNotes() -> some View { modifier(ReviewPromptModifier()) }
}
#Preview {
Text("Notes")
.promptsForReviewAfterTenNotes()
}
#endif
See Apple Developer Documentation — RequestReviewAction and Apple Developer Documentation — requestReview.
FieldNotes applies .promptsForReviewAfterTenNotes() to the split view in ContentView. The counter lives in
UserDefaults, so it survives relaunches, and @AppStorage observes the key, so the modifier sees each increment
that addCreatedByPerson(_:) makes. BuildIdentity is the helper from
Membership, Signing, Capabilities and Info.plist.
Upcoming Requirements and Deadlines
Apple announces upload requirements on its upcoming-requirements page and in developer news, usually months ahead. The ones that affect FieldNotes as of 2026-09-24:
| Applies from | Requirement | Official source |
|---|---|---|
May 1, 2024 |
Uploads must declare approved reasons for required-reason APIs, including those in third-party SDKs. |
Upcoming Requirements ("Approved reasons for APIs") |
January 31, 2026 |
Answers to the updated age-rating questions, to avoid interruptions when submitting updates. |
Upcoming Requirements ("Age Rating Updates") |
April 28, 2026 |
Uploads must be built with Xcode 26 or later and the 26 SDKs (iOS, iPadOS, tvOS, visionOS, watchOS). |
Upcoming Requirements ("SDK minimum requirements"), announced in Upcoming SDK minimum requirements (February 3, 2026) |
June 8, 2026 |
Revised App Review Guidelines and Developer Program License Agreement. |
|
September 2026 |
Social media questions in the age-rating questionnaire are required for new apps and updates. |
|
September 9, 2026 |
iOS and iPadOS apps uploaded to App Store Connect must target iOS 13 or later. |
Upcoming Requirements ("iOS and iPadOS minimum system requirements") |
October 1, 2026 |
New single set of EU business terms takes effect (see below). |
Changes for apps in the European Union (August 18, 2026) |
April 2027 |
iOS and iPadOS apps must be built with the iOS 27 and iPadOS 27 SDKs or later; tvOS, visionOS and watchOS apps with their 27 SDKs. |
App Store submissions now open for the latest OS releases (September 9, 2026); not yet on the Upcoming Requirements page on 2026-09-24 |
The same September 9, 2026 announcement notes that macOS 26 is the last release for Intel Macs and macOS 27 is
Apple silicon only, so a Mac app can be rebuilt for arm64 only. FieldNotes already builds with Xcode 27 and targets
26, so it meets every row; the check that proves it reads the SDK and minimum OS recorded in the archived app:
PLIST=build/FieldNotes.xcarchive/Products/Applications/FieldNotes.app/Info.plist
plutil -extract DTSDKName raw -o - "$PLIST" # iphoneos27.0
plutil -extract MinimumOSVersion raw -o - "$PLIST" # 26.0
EU Alternative Distribution
In the European Union, under the Digital Markets Act, iOS and iPadOS apps can also be distributed outside the App Store, in brief:
-
Alternative app marketplaces. Apps that install other developers' apps. Operating one needs Apple’s Alternative App Marketplace Entitlement, an organization membership and financial qualifications; marketplaces use the MarketplaceKit framework, and users need iOS 17.4 or iPadOS 18 or later.
-
Web distribution. Eligible developers distribute their own apps from their website.
-
Notarization for iOS and iPadOS apps. Apps distributed either way are still checked by Apple against a subset of the App Review Guidelines (the guidelines page can highlight exactly which), mostly about security and functionality. In App Store Connect, the developer chooses the distribution methods, submits the build for notarization and gets an alternative distribution package.
-
Business terms. Apple’s August 18, 2026 announcement moves every developer distributing in the EU to one set of business terms from October 1, 2026, replacing the Core Technology Fee with a 5% Core Technology Commission on digital transactions in apps distributed outside the App Store.
See Apple Developer — Operating an alternative app marketplace in the EU, Apple Developer Documentation — Submit for Notarization and Apple Developer News — Changes for apps in the European Union.
FieldNotes ships only on the App Store. Its one concession to alternative distribution is
FieldNotesInstallSource, which uses MarketplaceKit’s AppDistributor so that a future marketplace or web build
would not point people to App Store–only features such as the review prompt. A marketplace developer uses much more
of MarketplaceKit (installing apps, licenses, app search); that is out of scope here.
References
-
Apple Developer Documentation — Distributing your app for beta testing and releases
-
Apple Developer Documentation — Generating Tokens for API Requests
-
Apple Developer Documentation — Submit an app for beta review
-
Apple Developer Documentation — Create an app store version phased release
-
Apple Developer Documentation — Testing Apple In-App Purchases with sandbox
-
Apple Developer Documentation — Overview of Accessibility Nutrition Labels
-
Apple Developer Documentation — Age ratings values and definitions
-
Apple Developer Documentation — Overview of submitting for review
-
Apple Developer Documentation — Release a version update in phases
-
Apple Developer News — App Store submissions now open for the latest OS releases
-
Apple Developer News — Age rating questionnaire now includes social media questions
-
Apple Developer News — Changes for apps in the European Union
-
Apple Developer — Operating an alternative app marketplace in the EU