# Guideline 2.3.1：隱藏功能與 OTA 更新

> Apple 禁止隱藏、休眠或沒寫進審查備註的功能。哪些可以遠端更新、哪些必須重新建置，這裡一次說清楚。

原始頁面：[Guideline 2.3.1：隱藏功能與 OTA 更新](https://devoria.fr/zh-hant/blog/guideline-2-3-1-hidden-features-ota-updates)
作者：[Mehdi Azizi](https://devoria.fr/zh-hant/mehdi-azizi)
發布日期：2026-11-04
內容最後修訂：2026-11-04


Guideline 2.3.1 禁止隱藏、休眠或未說明的功能：你的 App 做的每件事，使用者和審查人員都必須看得到。每項新功能都要在審查備註裡具體描述。**遠端更新可以修正 Apple 看過的東西，但永遠不能加入 Apple 沒看過的東西。**

我用 Expo 發布 iOS App，每個 App 從第一個 build 就內建 EAS Update，用來推送修正。我的規則是：版本在審查期間，絕不發布任何遠端更新。這篇指南的依據是 Apple 的條文、授權協議、Expo 的文件，以及公開過的退件案例。

## Guideline 2.3.1 說了什麼？

2.3.1 是 [App Review Guidelines](https://developer.apple.com/app-store/review/guidelines/#2.3.1) 第 2 節「Performance」中 2.3「Accurate Metadata」的第一條。條文開頭是：「Don't include any hidden, dormant, or undocumented features in your app; your app's functionality should be clear to end users and App Review.」白話來說：不能有隱藏、沉睡或沒有說明的東西。

接下來針對每一次送審：「All new features, functionality, and product changes must be described with specificity in the Notes for Review section of App Store Connect (generic descriptions will be rejected) and accessible for review.」**每項新功能都必須在審查備註中具體描述，而且審查人員要能實際操作到。** 所以只寫「錯誤修正與效能改善」是不夠的。

同一段也針對誤導性行銷：在 App Store 或其他地方，承諾不存在的內容或服務，或是標示假的價格。

這部分在 Nolera 上幫了我。上架前重讀商店頁面時，我找到兩句無法驗證的話：「介面僅提供法文」，這已經不正確了；還有「最多人選擇的方案」，但這個 App 根本還沒上架。給審查人員的備註描述的也是產品的舊狀態。這正是這部分條文要抓的東西。至於 [2.3.10](https://devoria.fr/zh-hant/blog/guideline-2-3-10-app-rejected-metadata)，主要針對的是商店頁面。

## 2.3.1 退件訊息是什麼意思？

退件標題是「Guideline 2.3.1 - Performance」，有時後面會接「Hidden Features」。以下是 React Native 開發者公開的兩則訊息：

- **2024 年 3 月**，在一則 [CodePush 的 GitHub issue](https://github.com/microsoft/react-native-code-push/issues/2680) 中：「The app contains hidden features.」，沒有其他說明。開發者要求補充，收到一段依 2.5.2 提出的說明：「Your app, extension, or linked framework appears to contain code designed explicitly with the capability to change the app's behavior or functionality after App Review approval.」該 issue 沒有說 App 後來有沒有過審。
- **2023 年 2 月**，在[另一則 issue](https://github.com/microsoft/react-native-code-push/issues/2434) 中，Apple 點名的是二進位檔裡的「CodePush-related remote code downloading/updating support」。

我的解讀是：**審查人員看不到你的更新，他看到的是能接收更新的程式碼。** 這兩個案例針對的都是 CodePush。我沒有找到近期針對 EAS Update 的公開退件案例，但這不代表什麼。

## 遠端更新：2.5.2 和授權協議允許什麼

[2.5.2 條](https://developer.apple.com/app-store/review/guidelines/#2.5.2)（我在[背景模式](https://devoria.fr/zh-hant/blog/guideline-2-5-4-background-mode-rejected)的指南裡也談過）禁止 App「download, install, or execute code which introduces or changes features or functionality of the app」。

[Apple Developer Program License Agreement](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/#b331) 第 3.3.1(B) 條禁止下載可執行程式碼，但也明確寫道：「Interpreted code may be downloaded to an Application」。條件有三個：這段程式碼不改變 App 的主要用途、不繞過簽章、沙盒或系統安全機制，也不建立其他 App 的商店。

Expo App 的 JavaScript 屬於直譯程式碼：我認為 EAS Update 符合這個框架。它的[文件](https://docs.expo.dev/eas-update/introduction/)提醒，更新必須遵守 App Store 的規則，而且「changes to your app's behavior need to be reviewed」。遇到嚴重 bug 時，Expo 建議先遠端推送修正，再送審一個包含該修正的新版本。

**我的界線：修正和調整既有的東西，可以。改變 App 的本質或它販售的東西，不行。**

## 哪些可以遠端更新，哪些需要新的 build

這是我用的對照表，整理自本文引用的 Apple 與 Expo 頁面。「免費變付費」那一列和最後兩列是我的解讀。

| 變更                   | 途徑              | 原因                   |
| ---------------------- | ----------------- | ---------------------- |
| JavaScript bug         | 遠端更新          | Expo 設計的用途        |
| 文字、翻譯、樣式       | 遠端更新          | Expo 設計的用途        |
| 訂閱價格               | App Store Connect | 沒有改動程式碼         |
| 宣傳文字               | App Store Connect | 不需重新送審           |
| 已核准類型的購買項目   | 只審該購買項目    | App Store Connect 規則 |
| 某類型的第一個購買項目 | 新版本            | App Store Connect 規則 |
| 新功能                 | 新版本            | 2.3.1 與 2.5.2         |
| 免費變付費             | 新版本            | 商店頁面與備註需更新   |
| 權限、Info.plist       | 新的 build        | 屬於二進位檔的檔案     |
| 原生模組、Expo SDK     | 新的 build        | 原生程式碼有變動       |
| 用來關閉功能的開關     | 遠端設定          | 暫時性的話可接受       |
| 用來揭露功能的開關     | 禁止              | 功能從未被審查         |

依照 Apple 關於[版本資訊](https://developer.apple.com/help/app-store-connect/reference/app-information/platform-version-information)的頁面，宣傳文字可以「without requiring an updated submission」修改。依照[提交購買項目](https://developer.apple.com/help/app-store-connect/manage-submissions-to-app-review/submit-an-in-app-purchase)的說明，每種類型的第一個購買項目要跟著新版本送審，之後的可以單獨送。Expo 把原生程式碼、權限和 SDK 版本列為需要新二進位檔的項目，跟 [ITMS-90683](https://devoria.fr/zh-hant/blog/itms-90683-missing-purpose-string-expo) 的情況一樣。用來關閉故障功能的開關不會加入任何新東西，只要它不拿走訂閱者已經付費的內容。

## 不會被退件的遠端開關

遠端開關（feature flag）本身並沒有被禁止：關鍵在於它藏了什麼。我的規則：

- **開關能顯示的所有東西，在審查時都已存在並開啟**，而且寫在審查備註裡。
- A/B 測試比較的是審查人員看得到的兩個版本，絕不是一個審過、一個沒審過的版本。
- **沒有任何程式碼會偵測審查狀態來改變行為。** 這正是隱藏功能的定義。
- 遠端控制的付費牆只顯示已核准的商品，而且每個版本都遵守 [3.1.2 條](https://devoria.fr/zh-hant/blog/guideline-3-1-2-app-subscription-rejected)。

## 示範模式與測試帳號

如果你的 App 需要登入，[guideline 2.1](https://devoria.fr/zh-hant/blog/app-store-guideline-2-1-rejection-fix) 要求提供示範帳號，而且伺服器要是開著的。如果法律或安全上的義務讓你無法提供帳號，可以用內建的示範模式代替，但必須「with prior approval by Apple」，而且要展示所有功能。Apple 的版本資訊頁面還補充：帳號不能過期，額外的帳號寫在備註裡，備註上限為 4,000 位元組。

跟 2.3.1 的關聯是：**如果示範模式或測試帳號會開啟平常看不到的畫面，就必須說明。** 沒有告知的秘密手勢、代碼或特殊帳號，就是隱藏功能。在備註裡列出每個特殊入口，以及它會開啟什麼。

## 我的規則：審查期間不發布任何遠端更新

我為什麼每個 App 都用 EAS Update：在我的鬧鐘 App Solys 上，有個 JavaScript bug 會讓音效只要失敗一次，App 就完全沒聲音。修正只有三行，但要推出去卻得跑完整的 build 和審查。從那之後，我所有的 App 從第一個 build 就內建 expo-updates：它是原生模組，沒有它編譯出來的二進位檔永遠收不到任何更新。我在 `app.json` 裡的設定：

```json
{
  "expo": {
    "runtimeVersion": { "policy": "fingerprint" },
    "updates": { "fallbackToCacheTimeout": 0, "checkAutomatically": "ON_LOAD" }
  }
}
```

使用 `fingerprint`，更新只會送到原生程式碼相符的二進位檔（[執行階段版本](https://docs.expo.dev/eas-update/runtime-versions/)）。但當某個版本在等待審查或審查中時，我什麼都不發布。原因有兩個。

1. **審查人員看不到修正。** `fallbackToCacheTimeout` 設為 0 時，全新安裝會先用內建的程式碼啟動，下一次啟動才套用更新（[expo-updates 文件](https://docs.expo.dev/versions/latest/sdk/updates/)）。審查人員安裝、啟動一次，測試的就是二進位檔本身。Nolera 就是在修正已經遠端推送的情況下被退件：我在 2.1 的指南裡有寫這段經過。
2. **審查人員測試的內容，必須就是使用者收到的內容。** 在審查期間改變行為，會讓審查看到的版本和實際推出的版本出現落差。

## 具體的審查備註

2.3.1 不接受籠統的描述。這是我會用的範本，以英文撰寫：

```text
What's new in this version:
- [Feature]: [where to find it, how to test it].

Remote configuration:
- "[flag]" controls [feature]. It is ON for review.

Over-the-air updates:
- The app uses expo-updates for bug fixes only.
  No update will be published during this review.

Demo account: see Sign-in information. It never expires.
```

## 如何回覆 2.3.1 退件？

在 App Store Connect 裡回覆，也就是你[回覆 App Review 訊息](https://developer.apple.com/help/app-store-connect/manage-submissions-to-app-review/reply-to-app-review-messages/)的地方。

- **訊息很模糊**：詢問是指哪個功能，就像 2024 年那則 issue 的開發者一樣。
- **某個真實功能描述不清**：提供確切的操作路徑和一段短影片，然後補齊備註。
- **某個開關揭露了未審查的內容**：移除它，或是開啟並說明它，然後重新 build。
- **問題出在更新程式碼**：說明它只用於修正，符合第 3.3.1(B) 條的範圍。在 2023 年那則 issue 中，Apple 要求的是把它移除。

## 送審前的 2.3.1 檢查清單

- 這個版本的每項新功能都寫在備註裡，附上操作路徑。
- 每個遠端開關在審查時都已開啟，並寫在備註裡。
- 沒有任何程式碼會針對審查人員改變行為。
- 特殊入口都有說明：示範模式、測試帳號、手勢。
- 商店頁面和付費牆沒有承諾 build 做不到的事。
- 審查期間沒有發布任何遠端更新。

## 送審前檢查你的資料

[Review Kit](https://devoria.fr/zh-hant/kits/app-review) 收錄了我的送審前檢查清單、給審查人員的備註範本，以及會幫你檢查專案的 Claude Code skills。如果想要真人幫你看，[跟我預約一小時](https://devoria.fr/zh-hant/coaching)：我們透過視訊一起檢查你的審查備註和更新設定。完整流程請看 [App 被 Apple 退件](https://devoria.fr/zh-hant/expertise/app-rejected-by-apple)這一頁。

正在準備第一次上架嗎？[做免費診斷](https://devoria.fr/zh-hant/diagnostic)：它會幫你把步驟排好順序，並寄給你我的 Apple 防退件檢查清單。

## 常見問題

### 「Guideline 2.3.1 - Performance - Hidden Features」是什麼意思？

Apple 認為你的 App 含有隱藏、休眠或沒寫進審查備註的功能。這則訊息也可能是針對能在審查後改變 App 的程式碼，有時會伴隨 2.5.2 退件。

### Apple 允許 EAS Update 嗎？

授權協議允許下載直譯程式碼，只要它不改變 App 的主要用途。修正，可以。加入未經審查的功能，不行。

### iOS App 可以使用 feature flag 嗎？

可以，前提是它們能顯示的所有內容都經過審查。審查時把每個開關打開，並寫在備註裡。

### 示範模式需要告知審查人員嗎？

需要。內建示範模式必須事先取得 Apple 同意，而且要展示所有功能。

### 審查期間可以發布遠端更新嗎？

條文沒有明文禁止。但審查人員第一次啟動時看不到它，而且它會跟審查過的版本產生落差。我一律不這麼做。


## 你的專案

[Review Kit](https://devoria.fr/zh-hant/kits/app-review)：我的 App Store 審查檢查清單、我被 Apple 退件後的修正、Google Play 檢查清單，以及三個 Claude Code skill。$1,749。

[iOS & Android Kit](https://devoria.fr/zh-hant/kits/ios)：用 Claude Code 打造你的 iPhone 和 Android App：我自己 App 的起始程式碼、上架商店的完整流程，以及所有套件。$3,599。

[診斷：你離自己的 iPhone App 還差什麼](https://devoria.fr/zh-hant/diagnostic)：7 個問題，60 秒：找出真正卡住你發布 iPhone App 的原因，拿到你的 4 步驟路線，以及最適合你的套件。
