iOS 27 introduces a Product Page Header on the App Store โ a dedicated visual area above screenshots where developers can place custom marketing images or videos. A new Asset Library in App Store Connect centralizes all creative assets and allows real-time updates to Product Page Headers and Search Result visuals without a new app submission.
โข A dedicated Product Page Header lets you showcase brand identity and aspirational visuals the moment someone lands on your app โ before they scroll to screenshots.
โข The Asset Library enables real-time swaps of approved assets (e.g., seasonal campaigns) without submitting a new app version or going through App Review again.
โข Search Result images/videos can now be customized per Custom Product Page and keyword, dramatically improving discoverability and ad conversion through Apple Ads integration.
Demonstrates how to upload a creative asset to the App Store Connect Asset Library using the App Store Connect REST API from a Swift command-line tool, then polls for its status.
import Foundation
// MARK: - App Store Connect API: Upload Creative Asset to Asset Library
// Requires: App Store Connect API key with Apps + App Store access
// Replace placeholders with your real values from App Store Connect
let apiKeyId = "YOUR_API_KEY_ID"
let issuerId = "YOUR_ISSUER_ID"
let privateKeyPem = """ // paste your .p8 content here (no header/footer lines)
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg...
"""
// MARK: - JWT Token Builder (ES256)
import CryptoKit
struct AppStoreConnectJWT {
static func token(keyId: String, issuerId: String, pemKey: String) throws -> String {
let header = #"{"alg":"ES256","kid":"\#(keyId)","typ":"JWT"}"#
let now = Int(Date().timeIntervalSince1970)
let payload = """
{"iss":"\(issuerId)","iat":\(now),"exp":\(now + 1200),\
"aud":"appstoreconnect-v1"}
"""
func b64(_ s: String) -> String {
Data(s.utf8).base64EncodedString()
.replacingOccurrences(of: "=", with: "")
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
}
let unsigned = "\(b64(header)).\(b64(payload))"
let keyData = Data(base64Encoded: pemKey
.components(separatedBy: .newlines)
.filter { !$0.hasPrefix("---") }
.joined()) ?? Data()
let privateKey = try P256.Signing.PrivateKey(derRepresentation: keyData)
let signature = try privateKey.signature(for: Data(unsigned.utf8))
let sigB64 = signature.derRepresentation
.base64EncodedString()
.replacingOccurrences(of: "=", with: "")
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
return "\(unsigned).\(sigB64)"
}
}
// MARK: - Upload creative asset metadata (Step 1 of 2: reserve the asset)
async func reserveCreativeAsset(appId: String, jwt: String) async throws -> String {
let url = URL(string: "https://api.appstoreconnect.apple.com/v1/appStoreVersionLocalizations")!
// In practice you POST to the Asset Library endpoint:
// POST /v1/apps/{appId}/creativeAssets (replace with GA endpoint name)
var req = URLRequest(url: url)
req.httpMethod = "POST"
req.setValue("Bearer \(jwt)", forHTTPHeaderField: "Authorization")
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"data": [
"type": "creativeAssets",
"attributes": [
"fileName": "summer_header.png",
"fileSize": 512_000,
"assetType": "PRODUCT_PAGE_HEADER"
],
"relationships": [
"app": ["data": ["type": "apps", "id": appId]]
]
]
]
req.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, _) = try await URLSession.shared.data(for: req)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let assetId = (json?["data"] as? [String: Any])?["id"] as? String ?? ""
print("Reserved creative asset ID:", assetId)
return assetId
}
// MARK: - Entry point
Task {
do {
let jwt = try AppStoreConnectJWT.token(keyId: apiKeyId,
issuerId: issuerId,
pemKey: privateKeyPem)
let assetId = try await reserveCreativeAsset(appId: "YOUR_APP_ID", jwt: jwt)
print("Next: upload binary to the upload URL, then PATCH to commit asset:", assetId)
} catch {
print("Error:", error)
}
}iOS 27 enables developers to sell auto-renewable subscriptions to multiple people at once via in-app group purchases or volume purchasing through Apple Business/School Manager. Purchasers buy a set number of seats and share an invite link; StoreKit 2 handles the full seat-assignment lifecycle.
Retention Messaging lets developers configure custom messages, images, and promotional offers that appear on the App Store cancellation page when a subscriber is about to cancel. It supports both static configuration via App Store Connect and real-time server-driven responses through the Retention Messaging API.
Creative assets must be approved via App Review before they can be swapped in real-time through Asset Library โ plan ahead for seasonal or campaign assets. Uploading via the App Store Connect API requires appropriate API key permissions. Assets submitted standalone through Asset Library are approved independently of app version submissions, but the app version still needs to be approved for a first release.
Assets must meet App Store Connect specifications; header and search result visuals are displayed on iOS 27 and iPadOS 27 or later.