# Build number App Store Connect: version và số build

> Version là số khách hàng nhìn thấy, build number định danh từng lần gửi. Cặp này phải là duy nhất: với Expo, EAS tự tăng số build cho bạn.

Trang gốc: [Build number App Store Connect: version và số build](https://devoria.fr/vi/blog/build-number-app-store-connect-version)
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


Build number của App Store Connect chính là số build: khóa `CFBundleVersion`, dùng để định danh từng lần gửi. Version, tức `CFBundleShortVersionString`, là con số khách hàng của bạn nhìn thấy trên App Store. **Apple yêu cầu cặp version và số build phải là duy nhất: mỗi số build chỉ dùng được một lần cho cùng một version.**

Với Expo, bạn tự đặt version, còn EAS có thể tăng số build ở mỗi lần build production. Bài này giải thích hai con số, các quy tắc của Apple, lỗi ITMS-90062 và ITMS-90189, và những gì cần kiểm tra khi build của bạn không xuất hiện.

Mình phát hành các app iOS bằng Expo và EAS, trong đó có Solys, TradaZen và Nolera. Các thiết lập bên dưới lấy từ chính file `eas.json` của chúng.

## Version và số build: khác nhau thế nào?

Hai khóa trong Info.plist định danh app của bạn. [Tài liệu của Apple về CFBundleShortVersionString](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleshortversionstring) mô tả nó là version người dùng nhìn thấy: ba số nguyên cách nhau bằng dấu chấm, ví dụ 10.14.1. [Tài liệu về CFBundleVersion](https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleversion) nói về version của build: từ một đến ba số nguyên, chỉ gồm chữ số và dấu chấm.

| Thành phần | Khóa iOS                   | Ai nhìn thấy       | Trường Expo       |
| ---------- | -------------------------- | ------------------ | ----------------- |
| Version    | CFBundleShortVersionString | Khách hàng của bạn | `version`         |
| Số build   | CFBundleVersion            | Bạn và Apple       | `ios.buildNumber` |

Ví dụ giả định: với version 1.4.0, bạn gửi các build 12, 13 và 14 trong lúc test, rồi chọn một build để gửi duyệt. [Trang trợ giúp App Store Connect](https://developer.apple.com/help/app-store-connect/manage-builds/upload-builds) tóm gọn: version gắn build với trang app của nó, còn số build định danh build đó ở mọi nơi trong hệ thống.

## Quy tắc tăng số của Apple

1. **Cặp số phải là duy nhất.** Theo [tài liệu Xcode Cloud](https://developer.apple.com/documentation/xcode/setting-the-next-build-number-for-xcode-cloud-builds), App Store Connect yêu cầu tổ hợp version và số build không trùng lặp. Vì vậy app iPhone có thể bắt đầu lại từ 1 cho version mới. App Mac thì không: số của nó luôn phải tăng.
2. **Version đã được duyệt là đóng lại.** Khi Apple đã duyệt version 1.4.0, bạn không thể thêm build vào nó nữa. Version tiếp theo phải mang số cao hơn.
3. **Mỗi số được so sánh như số nguyên.** 1.10 đứng sau 1.9, vì 10 lớn hơn 9. Một version 1.9 gửi sau khi 1.20 đã được duyệt sẽ bị từ chối.

Lời khuyên của mình: **giữ số build là số nguyên luôn tăng, đừng đặt lại về 1.** Trên iPhone thì được phép bắt đầu lại từ 1, nhưng một số duy nhất suốt vòng đời app giúp bạn không nhầm hai build trong TestFlight. Đó là điều mình thấy trên các app dùng chế độ remote: bộ đếm cứ chạy tiếp từ version này sang version khác.

## Quản lý version và build với Expo và EAS

[Trang của Expo về version](https://docs.expo.dev/build-reference/app-versions/), cập nhật ngày 25 tháng 9 năm 2026, tách rõ vai trò. Version (`version` trong app.json) do bạn quản lý: bạn đổi nó khi chuẩn bị một bản phát hành mới. Số build (`ios.buildNumber`) có thể giao cho EAS. Expo còn nêu việc quên tăng số này là nguyên nhân phổ biến khiến lần gửi bị từ chối.

Mọi thứ được thiết lập trong eas.json:

```json
{
  "cli": {
    "appVersionSource": "remote"
  },
  "build": {
    "production": {
      "autoIncrement": true
    }
  }
}
```

- **`"remote"`**: server của EAS lưu số build. Đây là cách được khuyến nghị từ EAS CLI 12. Bộ đếm bắt đầu từ giá trị trong cấu hình của bạn, hoặc từ 1, sau đó `ios.buildNumber` bị bỏ qua.
- **`"local"`**: cấu hình của bạn là chuẩn. Với `autoIncrement`, EAS sửa `ios.buildNumber` trong file, và bạn phải commit thay đổi đó sau mỗi lần build.
- **`autoIncrement`**: [tài liệu tham chiếu eas.json](https://docs.expo.dev/eas/json/) chấp nhận `true` hoặc `"buildNumber"` cho số build, và `"version"` để tăng chữ số cuối của version. Ở chế độ remote, Expo nói rõ `"version"` không được hỗ trợ.

Với mình, TradaZen và Nolera dùng `remote`. Solys vẫn để `local`: số build nằm trong app.json và mỗi lần build production lại tăng nó trong file. **Cả hai đều chạy được, nhưng chế độ remote loại bỏ rủi ro quên commit.**

Hai lệnh hoàn thiện hệ thống này. `eas build:version:set` khởi tạo bộ đếm remote: nhập số build cuối cùng bạn đã gửi cho Apple. `eas build:version:sync` chép giá trị remote về project của bạn, hữu ích nếu một ngày bạn build bằng Xcode. Với EAS Update, chế độ remote không chạy với chính sách `nativeVersion`: Expo khuyên dùng `appVersion`.

## ITMS-90062: version đã được duyệt

Thông báo trông như sau (số giả định):

```text
ERROR ITMS-90062: This bundle is invalid. The value for key
CFBundleShortVersionString [1.4.0] in the Info.plist file must
contain a higher version than that of the previously approved
version [1.4.0].
```

Apple đã duyệt version 1.4.0: bạn không thể thêm build vào đó nữa. **Hãy tăng version trong app.json, ví dụ lên 1.4.1, rồi build lại.** Chỉ đổi số build là không đủ.

Nếu lỗi vẫn quay lại dù `version` đã cập nhật, hãy tìm trường `ios.version`. Theo [tài liệu tham chiếu cấu hình Expo](https://docs.expo.dev/versions/latest/config/app/), nó được ưu tiên hơn `version` trên iOS. Cách xử lý tương tự cho lỗi họ hàng ITMS-90186, "Invalid Pre-Release Train", báo rằng version đó đã đóng với build mới.

## ITMS-90189: Redundant Binary Upload

Lần này, số build là thứ gây kẹt:

```text
ERROR ITMS-90189: Redundant Binary Upload. You've already uploaded
a build with build number '12' for version number '1.4.0'.
```

Cặp 1.4.0 (12) đã tồn tại bên Apple. Với Expo, có bốn nguyên nhân hay gặp:

- **Profile đang dùng không có `autoIncrement`.** Nếu bạn tạo một profile riêng cho TestFlight, như của Nolera, hãy thêm lại tùy chọn này.
- **Bộ đếm remote bị tụt lại.** Bạn chuyển sang `remote` sau vài lần gửi thủ công: chạy `eas build:version:set` với số cuối cùng Apple đã biết.
- **Quên commit ở chế độ local.** Lần build sau lại bắt đầu từ số cũ.
- **Bạn gửi lại cùng một binary.** Với `eas submit`, hãy chọn build mới, không phải build cũ.

**Cách sửa luôn giống nhau: một build mới với số cao hơn.**

## "No builds available": build của bạn không xuất hiện

Build đã được gửi đi, nhưng trang app không hiện nó. Kiểm tra theo thứ tự sau.

1. **Nó vẫn đang được xử lý.** Apple xử lý từng build trước khi hiển thị và gửi email cho bạn khi xong. [Expo](https://docs.expo.dev/submit/ios/) cho biết thường mất 10 đến 15 phút. [Theo Apple](https://developer.apple.com/help/app-store-connect/manage-builds/view-builds-and-metadata), nếu xử lý quá 24 giờ, bạn cần mở ticket Feedback Assistant.
2. **Nó đang chờ bạn khai báo mã hóa.** Đó là trạng thái Missing Compliance, chi tiết ở dưới.
3. **Version không khớp.** Một build 1.4.1 không nằm dưới trang app đã chuẩn bị cho 1.5.0 trong App Store Connect. Hãy cho hai bên khớp nhau.
4. **Có một version vừa phát hành trong lúc đó.** Theo [trang trợ giúp về chọn build](https://developer.apple.com/help/app-store-connect/manage-builds/choose-a-build-to-submit), danh sách khi đó chỉ hiện các build gửi sau lần phát hành ấy.
5. **Người test mới là người không thấy.** Trong app TestFlight, "no builds available" thường có nghĩa build không nằm trong nhóm nào, đang chờ duyệt beta hoặc đã hết hạn. Chi tiết có trong [TestFlight để test app iPhone của bạn](https://devoria.fr/vi/blog/testflight-kiem-thu-app-iphone).

### Các trạng thái cần biết

Những trạng thái thường giải thích việc build không xuất hiện, theo [danh sách của Apple](https://developer.apple.com/help/app-store-connect/reference/app-build-statuses). Processing và Failed không có trong đó: đây là trạng thái gửi được mô tả ở [trang theo dõi build](https://developer.apple.com/help/app-store-connect/manage-builds/view-builds-and-metadata).

| Trạng thái         | Ý nghĩa                     | Cách xử lý              |
| ------------------ | --------------------------- | ----------------------- |
| Processing         | Apple đang xử lý build      | Chờ email               |
| Failed             | Lỗi khi xử lý               | Đọc lỗi, gửi lại        |
| Missing Compliance | Chưa khai báo mã hóa        | Trả lời hoặc Info.plist |
| Invalid Binary     | Bản gửi không hợp lệ        | Sửa, build mới          |
| Expired            | Quá 90 ngày                 | Build mới               |
| Ready to Submit    | Sẵn sàng để test hoặc duyệt | Thêm vào một nhóm       |

### Khai báo mã hóa một lần cho xong

Nếu thiếu khóa `ITSAppUsesNonExemptEncryption`, [Apple](https://developer.apple.com/documentation/bundleresources/information-property-list/itsappusesnonexemptencryption) sẽ hỏi bạn các câu hỏi tuân thủ ở mỗi version mới. Với Expo, trường này là `ios.config.usesNonExemptEncryption`:

```json
{
  "expo": {
    "ios": {
      "config": {
        "usesNonExemptEncryption": false
      }
    }
  }
}
```

`false` nghĩa là: không có mã hóa, hoặc chỉ có mã hóa được miễn trừ. [Apple nói rõ](https://developer.apple.com/documentation/security/complying-with-encryption-export-regulations) rằng mã hóa tích hợp sẵn trong hệ thống, như kết nối HTTPS, thường được miễn trừ, và câu trả lời cũng bao gồm các thư viện của bạn. Cả ba app của mình đều khai báo `false`. **Chỉ đặt `false` nếu điều đó đúng với app của bạn: khai báo này ràng buộc trách nhiệm của bạn.**

## Có đổi được số của build đã gửi không?

Không. Số này được ghi vào binary đã ký: muốn sửa, bạn cần một build mới.

Thứ bạn đổi được là build gắn với version. Apple chỉ cho phép một build mỗi version, nhưng [bạn có thể thay nó bao nhiêu lần tùy thích](https://developer.apple.com/help/app-store-connect/manage-builds/choose-a-build-to-submit) cho đến khi gửi duyệt:

1. Mở version trong App Store Connect, phần Build.
2. Di chuột lên build hiện tại và bấm nút xóa, dấu trừ.
3. Bấm nút thêm, dấu cộng, chọn đúng build, rồi lưu.

Gỡ build khỏi version không xóa nó khỏi tài khoản của bạn. Nếu version đã đang được duyệt, [hãy rút bản gửi trước](https://developer.apple.com/help/app-store-connect/manage-submissions-to-app-review/remove-a-submission-from-review): quá trình duyệt sẽ bắt đầu lại từ đầu.

## Trước lần gửi tiếp theo

Số build chỉ là một trong các bước kiểm tra khi gửi. Image build có thể gây ra [ITMS-90725](https://devoria.fr/vi/blog/itms-90725-sdk-version-issue-expo), còn việc ký có những cái bẫy riêng, chi tiết trong [chứng chỉ developer iOS và provisioning profile](https://devoria.fr/vi/blog/ios-developer-certificate-provisioning-profile-expo). Toàn bộ chuỗi, từ build đến TestFlight, có trong [phát hành app iOS không cần Mac](https://devoria.fr/vi/blog/phat-hanh-app-ios-khong-can-mac-expo-eas), và thời gian duyệt có trong [thời gian duyệt App Store](https://devoria.fr/vi/blog/thoi-gian-duyet-app-app-store). Mọi bước được tập hợp ở trang [phát hành ứng dụng lên App Store](https://devoria.fr/vi/expertise/dang-ung-dung-len-app-store).

Nếu bạn đang chuẩn bị bản phát hành đầu tiên, [làm bài chẩn đoán miễn phí](https://devoria.fr/vi/diagnostic): nó cho bạn biết điều gì đang chặn project của bạn và gửi bạn checklist chống bị Apple từ chối của mình. Để xây app, tích hợp gói thuê bao và phát hành, [iOS & Android Kit](https://devoria.fr/vi/kits/ios) đi theo lộ trình bốn bước với Starter Code từ các app của mình, theo nhịp của bạn.

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

### Version và build number trên App Store Connect khác nhau thế nào?

Version là số công khai của bản phát hành, như 1.4.0. Build number định danh một binary cụ thể gửi cho Apple cho version đó. Bạn có thể gửi nhiều build cho cùng một version, mỗi build một số riêng.

### Có cần đổi số build mỗi lần gửi không?

Có, với cùng một version. Apple từ chối lần gửi thứ hai có cùng cặp version và số build, đó là lỗi ITMS-90189. Với EAS, `autoIncrement` lo việc này ở mỗi lần build production.

### Số build có phải bắt đầu lại từ 1 ở mỗi version không?

Không bắt buộc. Trên iPhone, Apple chấp nhận số thấp hơn cho version mới, vì chỉ cặp số cần là duy nhất. Một bộ đếm luôn tăng vẫn dễ theo dõi hơn.

### Làm sao đổi build number trong App Store Connect?

Bạn không thể sửa số của build đã gửi. Nhưng bạn có thể thay build gắn với version, từ phần Build của trang app, miễn là nó chưa được gửi duyệt. Muốn có số mới, hãy build lại.

### Tại sao build của tôi không xuất hiện trong App Store Connect?

Thường là nó vẫn đang được xử lý hoặc đang chờ bạn khai báo mã hóa. Hãy kiểm tra cả việc version của nó khớp với version trên trang app. Nếu xử lý quá 24 giờ, Apple yêu cầu mở ticket.


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