Tema
Updates OTA com Expo sem EAS
Atualizado em outubro de 2026.
Resposta curta: dá para ter updates OTA num app Expo sem o EAS Update. O caminho é usar o SDK aberto do hot-updater com o plugin @hot-updater/expo, gerar a parte nativa com npx expo prebuild e publicar pela CLI do hot-updater num servidor como o Pipa no Ar. O npx @pipanoar/init faz quase tudo: detecta o Expo, instala os pacotes, adiciona o plugin no app.json e cria o hot-updater.config.ts. O que não funciona é o Expo Go, que não carrega bundles de fora.
Antes de começar: o que precisa ser verdade
- O projeto usa prebuild. Development build ou build de release, gerada por você ou pelo EAS Build. O EAS Build continua valendo: só os updates saem por outro caminho.
- Você testa em build de release. Updates OTA não rodam em builds de debug, que carregam o JavaScript do Metro.
- Um sistema de OTA por app. Se o app hoje usa o EAS Update, a troca exige uma build nova na loja com o SDK novo, como em qualquer migração de OTA.
E você precisa de uma conta no painel, com uma organização e um app criados. O appId (começa com app_) fica na página Setup do app, e a API key, na página API keys.
1. Rode o init
Na raiz do projeto:
sh
npx @pipanoar/initO comando decide que o projeto é Expo se ele depende do pacote expo, se o app.json tem a chave expo ou se existe um app.config.*. Depois:
- Pergunta o
appIde a API key (ou use--app-ide--api-key). - Instala o
@hot-updater/react-nativepelonpx expo install, para respeitar as versões do SDK do Expo, e ohot-updater, o@hot-updater/expoe o@pipanoar/hot-updatercomo dependências de desenvolvimento. Os pacotes do hot-updater vão na versão1.0.0-rc.24. - Cria o
hot-updater.config.ts. Se já existir um, só substitui com a sua confirmação e guarda o anterior numa cópia.bak. - Adiciona
@hot-updater/expoaos plugins doapp.json. - Mostra o
HotUpdater.wrap(...)com oappIde a API key preenchidos. Com Expo Router, ele aponta o layout raiz como o lugar de colar.
Todas as opções estão na referência do init.
Projeto com app.config.ts
O init edita o app.json. Se o seu projeto tem também um app.config.ts (ou .js), ele avisa: confira se o arquivo repassa os plugins do app.json, ou adicione @hot-updater/expo em plugins direto nele.
2. Ou faça à mão
Se preferir não rodar o init, são três mudanças. Os pacotes:
sh
npx expo install @hot-updater/react-native@1.0.0-rc.24
npm install -D hot-updater@1.0.0-rc.24 @hot-updater/expo@1.0.0-rc.24 @pipanoar/hot-updaterO plugin no app.json:
json
{
"expo": {
"plugins": ["@hot-updater/expo"]
}
}E o hot-updater.config.ts, na raiz:
ts
import { expo } from "@hot-updater/expo";
import { pipanoar } from "@pipanoar/hot-updater";
import { defineConfig } from "hot-updater";
export default defineConfig({
build: expo(),
...pipanoar({ appId: "app_xxxxxxxxxxxxxxxxxxxx" }),
updateStrategy: "appVersion",
});O build: expo() é o que muda em relação ao React Native bare. O pipanoar() configura o banco e o armazenamento da CLI para o seu app.
3. Envolva o app
No componente raiz (o App.tsx, ou o app/_layout.tsx com Expo Router):
tsx
import { HotUpdater, insights } from "@hot-updater/react-native";
import { pipanoarClient } from "@pipanoar/hot-updater/client";
function App() {
// ...
}
export default HotUpdater.wrap({
...pipanoarClient({
appId: "app_xxxxxxxxxxxxxxxxxxxx",
apiKey: "COLE_A_API_KEY_AQUI",
}),
updateStrategy: "appVersion",
// Insights: instalações ativas, adoção e falhas no painel.
plugins: [insights()],
})(App);A API key só lê os updates deste app, então pode ir no bundle. Use o mesmo updateStrategy aqui e no config.
4. Gere a parte nativa e a build
sh
npx expo prebuildO plugin @hot-updater/expo ajusta o iOS e o Android no prebuild para carregar o bundle baixado. No React Native bare essa parte é manual; no Expo, o plugin cuida. Depois gere uma build de release, local ou no EAS Build, e instale no aparelho.
Essa build é a base. Daqui em diante, cada mudança de JavaScript vai pelo ar.
5. Publique o primeiro update
Crie um token de deploy na organização, em Tokens de deploy, com escopo Deploy. Ele é secreto: fica no seu terminal ou nos segredos do CI, nunca no app.
sh
export PIPANOAR_TOKEN=pn_...
npx hot-updater deploy -p ios
npx hot-updater deploy -p androidFeche e abra o app duas vezes: na primeira ele baixa o update, na segunda abre com ele. A release aparece em Releases no painel, e os números começam a chegar em Insights.
Para liberar só para uma parte dos aparelhos, use -r:
sh
npx hot-updater deploy -p android -r 10O rollout gradual está nos planos pagos, a partir do Starter. No Free, cada release vai para 100% dos aparelhos do canal.
Assinatura no Expo
Com a assinatura de bundles, o app só aplica updates assinados com a chave do seu app. No Expo, ative em Assinatura no painel, ligue signing: true no pipanoar(), baixe a chave pública na mesma página e aponte a opção publicKeyPath do plugin @hot-updater/expo para o arquivo. Depois rode o prebuild e gere a build de novo. O porquê de cada passo está em Como assinar bundles OTA.
O que muda em relação ao EAS Update
| EAS Update | hot-updater com o Pipa no Ar | |
|---|---|---|
| SDK no app | expo-updates | @hot-updater/react-native com o plugin @hot-updater/expo |
| Publicar | CLI do EAS | npx hot-updater deploy |
| Builds nativas | EAS Build ou local | EAS Build ou local, igual |
| Cobrança | Dólar, por usuário ativo | Real, por entrega |
Para entender a diferença de custo, veja EAS Update é caro?.
Problemas comuns
- O update não chega no Expo Go. É esperado: use uma development build ou uma build de release.
- O update não chega na build de release. Confira se o
updateStrategyé o mesmo no app e no config, e se a build foi gerada depois donpx expo prebuildcom o plugin. Mais casos em Dúvidas e problemas comuns. - A CLI responde 401. O token está errado, revogado, é de outra organização ou está limitado a outro app. Confira a variável
PIPANOAR_TOKEN.
O guia de referência, mais curto, está em Expo.