Skip to content

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/init

O 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:

  1. Pergunta o appId e a API key (ou use --app-id e --api-key).
  2. Instala o @hot-updater/react-native pelo npx expo install, para respeitar as versões do SDK do Expo, e o hot-updater, o @hot-updater/expo e o @pipanoar/hot-updater como dependências de desenvolvimento. Os pacotes do hot-updater vão na versão 1.0.0-rc.24.
  3. 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.
  4. Adiciona @hot-updater/expo aos plugins do app.json.
  5. Mostra o HotUpdater.wrap(...) com o appId e 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-updater

O 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 prebuild

O 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 android

Feche 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 10

O 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 Updatehot-updater com o Pipa no Ar
SDK no appexpo-updates@hot-updater/react-native com o plugin @hot-updater/expo
PublicarCLI do EASnpx hot-updater deploy
Builds nativasEAS Build ou localEAS Build ou local, igual
CobrançaDólar, por usuário ativoReal, 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 do npx expo prebuild com 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.