# Guideline 2.3.1: tính năng ẩn và cập nhật OTA trên iOS

> Guideline 2.3.1: Apple từ chối mọi tính năng ẩn, ngủ hoặc thiếu trong ghi chú review. Điều bạn đổi từ xa được và điều buộc phải build lại.

Trang gốc: [Guideline 2.3.1: tính năng ẩn và cập nhật OTA trên iOS](https://devoria.fr/vi/blog/guideline-2-3-1-tinh-nang-an-cap-nhat-ota)
Tác giả: [Mehdi Azizi](https://devoria.fr/vi/mehdi-azizi)
Ngày đăng: 2026-11-04
Lần sửa nội dung gần nhất: 2026-11-04


Guideline 2.3.1 cấm các tính năng ẩn, ngủ (dormant) hoặc không được mô tả: mọi thứ app của bạn làm phải hiển thị rõ với người dùng và với reviewer. Mỗi điểm mới phải được mô tả cụ thể trong ghi chú review. **Một bản cập nhật từ xa có thể sửa những gì Apple đã thấy, nhưng không bao giờ được thêm những gì Apple chưa thấy.**

Mình phát hành app iOS bằng Expo, và app nào cũng tích hợp EAS Update ngay từ build đầu tiên để đẩy bản sửa lỗi. Quy tắc của mình: không đẩy bất kỳ bản cập nhật từ xa nào khi một phiên bản đang được review. Hướng dẫn này dựa trên văn bản của Apple, giấy phép của Apple, tài liệu của Expo và các trường hợp bị từ chối đã được công bố.

## Guideline 2.3.1 nói gì?

2.3.1 mở đầu phần 2.3 "Accurate Metadata" của [App Review Guidelines](https://developer.apple.com/app-store/review/guidelines/#2.3.1), trong mục 2 "Performance". Văn bản bắt đầu thế này: "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." Nói đơn giản: không có gì bị giấu, bị ngủ hay không được mô tả.

Phần tiếp theo áp dụng cho mọi lần nộp: "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." **Mỗi điểm mới phải được mô tả cụ thể trong ghi chú review và reviewer phải truy cập được.** Vậy nên ghi "Sửa lỗi và cải thiện" là không đủ.

Cùng đoạn này còn nhắm tới marketing gây hiểu lầm: hứa hẹn nội dung hay dịch vụ không có, hoặc hiển thị giá sai, trên App Store hay ở nơi khác.

Phần này từng giúp mình với Nolera. Khi đọc lại trang app trước khi ra mắt, mình phát hiện hai câu không thể kiểm chứng: "giao diện chỉ có tiếng Pháp", câu này đã sai, và "gói được chọn nhiều nhất", cho một app chưa từng phát hành. Ghi chú gửi reviewer cũng mô tả một trạng thái cũ của sản phẩm. Đó chính xác là điều phần này nhắm tới. Còn [2.3.10](https://devoria.fr/vi/blog/guideline-2-3-10-app-bi-tu-choi-metadata) thì chủ yếu nhắm vào trang app.

## Thông báo từ chối 2.3.1 nghĩa là gì?

Lời từ chối có tiêu đề "Guideline 2.3.1 - Performance", đôi khi kèm "Hidden Features". Đây là hai thông báo được các developer React Native công bố:

- **Tháng 3/2024**, trong một [ticket GitHub của CodePush](https://github.com/microsoft/react-native-code-push/issues/2680): "The app contains hidden features.", không giải thích gì thêm. Developer hỏi lại và nhận thêm một đoạn theo 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." Ticket không nói app sau đó có được duyệt hay không.
- **Tháng 2/2023**, trong một [ticket khác](https://github.com/microsoft/react-native-code-push/issues/2434), Apple nhắm vào "CodePush-related remote code downloading/updating support" trong binary.

Cách mình hiểu: **reviewer không nhìn thấy các bản cập nhật của bạn, họ nhìn thấy đoạn code có khả năng nhận chúng.** Hai trường hợp này đều nhắm vào CodePush. Mình chưa tìm thấy vụ từ chối công khai gần đây nào nhắm vào EAS Update, nhưng điều đó chẳng chứng minh được gì.

## Cập nhật từ xa: điều 2.5.2 và giấy phép cho phép những gì

[Quy tắc 2.5.2](https://developer.apple.com/app-store/review/guidelines/#2.5.2), mà mình cũng nhắc đến trong hướng dẫn về [chế độ chạy nền](https://devoria.fr/vi/blog/guideline-2-5-4-apple-tu-choi-che-do-chay-nen), cấm app "download, install, or execute code which introduces or changes features or functionality of the app".

Điều 3.3.1(B) của [Apple Developer Program License Agreement](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/#b331) cấm code thực thi được tải về, nhưng nói rõ: "Interpreted code may be downloaded to an Application". Với ba điều kiện: code đó không thay đổi mục đích chính của app, không lách qua chữ ký, sandbox hay các cơ chế bảo mật của hệ thống, và không tạo ra cửa hàng cho các app khác.

JavaScript của một app Expo là code thông dịch: theo mình, EAS Update nằm trong khuôn khổ này. [Tài liệu](https://docs.expo.dev/eas-update/introduction/) của nó nhắc rằng các bản cập nhật phải tuân theo quy định của App Store, và "changes to your app's behavior need to be reviewed". Với lỗi nghiêm trọng, Expo đề xuất sửa từ xa, rồi nộp một phiên bản mới có chứa bản sửa đó.

**Ranh giới của mình: sửa và tinh chỉnh những gì đã có thì được. Thay đổi bản chất app hay thứ nó bán thì không.**

## Điều bạn đổi từ xa được, điều cần build mới

Bảng mình áp dụng, rút ra từ các trang của Apple và Expo được trích ở đây. Dòng "miễn phí chuyển sang trả phí" và hai dòng cuối là cách hiểu của mình.

| Thay đổi                      | Cách làm           | Lý do                           |
| ----------------------------- | ------------------ | ------------------------------- |
| Lỗi JavaScript                | Cập nhật từ xa     | Đúng mục đích Expo đề ra        |
| Văn bản, bản dịch, giao diện  | Cập nhật từ xa     | Đúng mục đích Expo đề ra        |
| Giá gói đăng ký               | App Store Connect  | Không đổi code                  |
| Promotional text              | App Store Connect  | Không cần nộp lại               |
| Gói mua thuộc loại đã duyệt   | Chỉ review gói mua | Quy tắc của App Store Connect   |
| Gói mua đầu tiên của một loại | Phiên bản mới      | Quy tắc của App Store Connect   |
| Tính năng mới                 | Phiên bản mới      | 2.3.1 và 2.5.2                  |
| Miễn phí chuyển sang trả phí  | Phiên bản mới      | Phải sửa trang app và ghi chú   |
| Quyền truy cập, Info.plist    | Build mới          | File nằm trong binary           |
| Module native, SDK Expo       | Build mới          | Code native thay đổi            |
| Công tắc để tắt               | Cấu hình từ xa     | Chấp nhận nếu tạm thời          |
| Công tắc để mở lộ             | Bị cấm             | Tính năng chưa từng được review |

Promotional text có thể đổi "without requiring an updated submission", theo trang của Apple về [thông tin phiên bản](https://developer.apple.com/help/app-store-connect/reference/app-information/platform-version-information). Gói mua đầu tiên của mỗi loại phải đi kèm một phiên bản mới, các gói sau thì nộp riêng, theo trang trợ giúp về [nộp gói mua trong app](https://developer.apple.com/help/app-store-connect/manage-submissions-to-app-review/submit-an-in-app-purchase). Expo xếp code native, quyền truy cập và phiên bản SDK vào nhóm cần binary mới, giống trường hợp [ITMS-90683](https://devoria.fr/vi/blog/itms-90683-missing-purpose-string-expo). Một công tắc tắt một tính năng đang lỗi không thêm gì mới, miễn là nó không lấy đi thứ người đăng ký đã trả tiền.

## Công tắc từ xa mà không bị từ chối

Một công tắc từ xa, hay feature flag, tự nó không bị cấm: mọi thứ phụ thuộc vào việc nó che giấu cái gì. Quy tắc của mình:

- **Mọi thứ mà một công tắc có thể hiển thị đều có sẵn và được bật cho reviewer**, và được nêu tên trong ghi chú review.
- Một bài test A/B so sánh hai biến thể mà reviewer đều xem được, không bao giờ là một biến thể đã review với một biến thể chưa ai thấy.
- **Không có đoạn code nào phát hiện quá trình review để hành xử khác đi.** Đó chính là định nghĩa của tính năng ẩn.
- Một paywall điều khiển từ xa chỉ hiển thị sản phẩm đã được duyệt, và mỗi biến thể đều tuân thủ [quy tắc 3.1.2](https://devoria.fr/vi/blog/guideline-3-1-2-app-bi-tu-choi-goi-dang-ky).

## Chế độ demo và tài khoản test

[Guideline 2.1](https://devoria.fr/vi/blog/guideline-2-1-app-store-tu-choi) yêu cầu một tài khoản demo nếu app của bạn có đăng nhập, với server đang chạy. Nếu nghĩa vụ pháp lý hoặc bảo mật khiến bạn không thể cung cấp, một chế độ demo tích hợp có thể thay thế, nhưng chỉ "with prior approval by Apple", và nó phải thể hiện đầy đủ mọi tính năng. Trang của Apple về thông tin phiên bản bổ sung: tài khoản không được hết hạn, và các tài khoản bổ sung ghi vào phần ghi chú, giới hạn 4.000 byte.

Liên hệ với 2.3.1: **một chế độ demo hay tài khoản test mở ra những màn hình mà bình thường không thấy được thì phải được mô tả.** Một cử chỉ bí mật, một mã hay một tài khoản đặc biệt không được khai báo, đó là tính năng ẩn. Hãy nêu tên mọi lối truy cập đặc biệt trong ghi chú, kèm theo những gì nó mở ra.

## Quy tắc của mình: không cập nhật từ xa khi đang review

Vì sao EAS Update có mặt ở khắp app của mình: trên Solys, app báo thức của mình, một lỗi JavaScript làm app im bặt chỉ cần một hiệu ứng âm thanh lỗi một lần. Bản sửa chỉ có ba dòng, nhưng phải build lại toàn bộ và qua review mới đến được người dùng. Từ đó, mọi app của mình đều tích hợp expo-updates ngay từ build đầu tiên: đây là module native, một binary build thiếu nó sẽ không bao giờ nhận được gì. Cấu hình của mình, trong `app.json`:

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

Với `fingerprint`, một bản cập nhật chỉ đến được các binary có code native khớp ([runtime version](https://docs.expo.dev/eas-update/runtime-versions/)). Nhưng khi một phiên bản đang chờ hoặc đang được review, mình không phát hành gì cả. Hai lý do.

1. **Reviewer sẽ không thấy bản sửa.** Với `fallbackToCacheTimeout` bằng 0, một lượt cài mới sẽ khởi động bằng code có sẵn trong binary và chỉ áp dụng bản cập nhật ở lần mở sau ([tài liệu expo-updates](https://docs.expo.dev/versions/latest/sdk/updates/)). Reviewer cài, mở một lần và test binary. Nolera từng bị từ chối dù bản sửa đã được đẩy từ xa: mình kể chuyện này trong hướng dẫn về 2.1.
2. **Thứ reviewer test phải giống thứ người dùng nhận được.** Thay đổi hành vi trong lúc review tạo ra khoảng cách giữa phiên bản được xem và phiên bản được phát hành.

## Ghi chú review cụ thể

2.3.1 từ chối các mô tả chung chung. Mẫu mình sẽ dùng, bằng tiếng Anh:

```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.
```

## Phản hồi lời từ chối 2.3.1 thế nào?

Trả lời trong App Store Connect, nơi bạn [phản hồi tin nhắn của App Review](https://developer.apple.com/help/app-store-connect/manage-submissions-to-app-review/reply-to-app-review-messages/).

- **Thông báo vẫn mơ hồ**: hỏi xem tính năng nào bị nhắm tới, như developer trong ticket năm 2024.
- **Một tính năng có thật bị mô tả chưa rõ**: đưa đường dẫn chính xác và một video ngắn, rồi bổ sung ghi chú.
- **Một công tắc mở ra thứ chưa được review**: gỡ nó đi, hoặc bật nó lên và mô tả, rồi build lại.
- **Code cập nhật bị nhắm tới**: giải thích rằng nó chỉ dùng để sửa lỗi, trong khuôn khổ điều 3.3.1(B). Trong ticket năm 2023, Apple yêu cầu gỡ bỏ nó.

## Checklist 2.3.1 trước khi nộp

- Mọi điểm mới của phiên bản đều được mô tả trong ghi chú, kèm đường dẫn.
- Mọi công tắc từ xa đều được bật cho review và nêu tên trong ghi chú.
- Không có đoạn code nào thay đổi hành vi với reviewer.
- Các lối truy cập đặc biệt đều được mô tả: chế độ demo, tài khoản test, cử chỉ.
- Trang app và paywall không hứa hẹn điều gì mà build không làm được.
- Không phát hành bản cập nhật từ xa nào trong lúc review.

## Kiểm tra hồ sơ trước khi gửi

[Review Kit](https://devoria.fr/vi/kits/app-review) gom checklist trước khi gửi của mình, các mẫu ghi chú cho reviewer và các skill Claude Code để rà soát dự án của bạn. Nếu cần người thật xem giúp, [đặt một giờ với mình](https://devoria.fr/vi/coaching): mình cùng bạn xem lại ghi chú review và cấu hình cập nhật qua video call. Quy trình đầy đủ có trên trang [app bị Apple từ chối](https://devoria.fr/vi/expertise/ung-dung-bi-apple-tu-choi).

Bạn đang chuẩn bị ra mắt app đầu tiên? [Làm bài chẩn đoán miễn phí](https://devoria.fr/vi/diagnostic): nó sắp xếp các bước đúng thứ tự và gửi bạn checklist chống bị Apple từ chối của mình.

## Câu hỏi thường gặp

### "Guideline 2.3.1 - Performance - Hidden Features" nghĩa là gì?

Apple cho rằng app của bạn chứa một tính năng ẩn, đang ngủ hoặc không có trong ghi chú review. Thông báo cũng có thể nhắm vào đoạn code có khả năng thay đổi app sau review, đôi khi kèm lời từ chối theo 2.5.2.

### Apple có cho phép EAS Update không?

Giấy phép cho phép tải code thông dịch miễn là nó không thay đổi mục đích chính của app. Sửa lỗi thì được. Thêm tính năng chưa được review thì không.

### Có dùng feature flag trong app iOS được không?

Được, nếu mọi thứ chúng có thể hiển thị đều đã được review. Hãy bật mọi công tắc cho review và mô tả chúng trong ghi chú.

### Có cần báo chế độ demo cho reviewer không?

Có. Một chế độ demo tích hợp cần được Apple chấp thuận trước và phải thể hiện đầy đủ mọi tính năng.

### Có được phát hành cập nhật từ xa trong lúc review không?

Không có quy định nào cấm rõ ràng. Nhưng reviewer sẽ không thấy nó ở lần mở đầu tiên, và nó tạo ra khoảng cách với phiên bản đã review. Mình thì luôn tránh.


## Dự án của bạn

[Review Kit](https://devoria.fr/vi/kits/app-review): Checklist duyệt App Store của mình, các lần Apple từ chối đã sửa, checklist Google Play và ba skill Claude Code. 1.399.000 ₫.

[iOS & Android Kit](https://devoria.fr/vi/kits/ios): Tạo app iPhone và Android với Claude Code: code khởi đầu từ các app của mình, lộ trình đến tận store và tất cả các kit. 2.799.000 ₫.

[Chẩn đoán: điều gì ngăn bạn có app iPhone](https://devoria.fr/vi/diagnostic): 7 câu hỏi, 60 giây: bạn biết điều gì thật sự cản bạn đăng app iPhone, lộ trình 4 bước và kit phù hợp với bạn.
