app.json y app.config.js
Configura cómo Expo hace prebuild de proyectos nativos, cómo tu app se carga en Expo Go y development builds, y qué valores no-secretos llegan a JavaScript en tiempo de ejecución - sin filtrar credenciales al bundle.
Busca en todas las páginas de la documentación
Configura cómo Expo hace prebuild de proyectos nativos, cómo tu app se carga en Expo Go y development builds, y qué valores no-secretos llegan a JavaScript en tiempo de ejecución - sin filtrar credenciales al bundle.
Tarjeta de referencia rápida - lista para copiar y pegar.
// app.config.ts
import { ExpoConfig, ConfigContext } from "expo/config";
const APP_ENV = process.env.APP_ENV ?? "development";
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: APP_ENV === "production" ? "Acme" : "Acme (Dev)",
slug: "acme-mobile",
scheme: "acme",
extra: {
appEnv: APP_ENV,
apiUrl: process.env.EXPO_PUBLIC_API_URL ?? "https://staging.api.acme.test",
},
ios: { bundleIdentifier: "com.acme.mobile" },
android: { package: "com.acme.mobile" },
});# Inspecciona el manifest público resuelto (qué Constants.expoConfig expondrá)
APP_ENV=development EXPO_PUBLIC_API_URL=https://staging.api.acme.test npx expo config --type publicCuándo usarlo:
scheme de deep link, y configuración de EAS Update.extra y Constants.expoConfig.android/ e ios/ durante prebuild.// app.config.ts - staging vs production con patrones seguros para secretos
import { ExpoConfig, ConfigContext } from "expo/config";
type AppEnv = "development" | "staging" | "production";
function resolveEnv(): AppEnv {
const raw = process.env.APP_ENV ?? "development";
if (raw === "staging" || raw === "production") return raw;
return "development";
}
const env = resolveEnv();
const apiUrlByEnv: Record<AppEnv, string> = {
development: "http://localhost:4000",
staging: "https://staging.api.acme.test",
production: "https://api.acme.com",
};
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: env === "production" ? "Acme" : `Acme (${env})`,
slug: "acme-mobile",
scheme: "acme",
version: "1.4.0",
orientation: "portrait",
userInterfaceStyle: "automatic",
plugins: ["expo-router"],
extra: {
appEnv: env,
// Solo valores no-secretos - legibles en la app compilada
apiUrl: process.env.EXPO_PUBLIC_API_URL ?? apiUrlByEnv[env],
featureFlags: {
newCheckout: env !== "production",
},
},
ios: {
bundleIdentifier:
env === "production" ? "com.acme.mobile" : `com.acme.mobile.${env}`,
supportsTablet: true,
},
android: {
package: env === "production" ? "com.acme.mobile" : `com.acme.mobile.${env}`,
adaptiveIcon: {
foregroundImage: "./assets/adaptive-icon.png",
backgroundColor: "#ffffff",
},
},
});// src/config/runtime.ts - lee configuración resuelta en tiempo de ejecución (no app.config.ts crudo)
import Constants from "expo-constants";
type Extra = {
appEnv: "development" | "staging" | "production";
apiUrl: string;
featureFlags: { newCheckout: boolean };
};
const extra = Constants.expoConfig?.extra as Extra;
export const appEnv = extra.appEnv;
export const apiUrl = extra.apiUrl;
export const featureFlags = extra.featureFlags;# Desarrollo local (API de staging)
APP_ENV=staging EXPO_PUBLIC_API_URL=https://staging.api.acme.test npx expo start
# Build de producción EAS - los secretos viven en EAS, no en app.config
APP_ENV=production eas build --profile production --platform iosLo que esto demuestra:
app.config.ts fusiona valores estáticos de app.json usando el patrón middleware ({ config }).APP_ENV cambia identificadores nativos y nombre de pantalla por entorno.EXPO_PUBLIC_* se comen inline a JS en tiempo de compilación; extra expone configuración mediante Constants.expoConfig.npx expo config --type public muestra una vista previa del manifest visible en el cliente antes de que lo envíes.app.json o app.config.json) y configuración dinámica (app.config.js / app.config.ts). Si ambas existen, la dinámica gana después de fusionarse.{ config } y usa el valor retornado como el manifest final.Constants.expoConfig. Las keys sensibles (hooks, ios.config, android.config, campos de firma de update) se filtran.plugins se ejecutan durante npx expo prebuild y EAS Build para mutar proyectos nativos.| Paso | Fuente | Notas |
|---|---|---|
| 1 | app.config.json o app.json | Línea base estática; las herramientas CLI pueden auto-editar app.json |
| 2 | app.config.ts (preferido) o app.config.js | Si ambos TS y JS existen, TypeScript gana |
| 3 | Fusión de función | export default ({ config }) => ({ ...config, ... }) |
| 4 | Envoltura expo: {} | Si está presente en la raíz, solo el objeto expo anidado se usa |
# Configuración completa resuelta (incluye campos solo-nativos)
npx expo config
# Subset público - coincide con lo que JS puede leer en tiempo de ejecución
npx expo config --type public| Mecanismo | Se ejecuta cuando | Seguro para secretos | Leer en app mediante |
|---|---|---|---|
EXPO_PUBLIC_* en .env | expo start, expo export, bundling Metro de EAS | No - se come inline al bundle JS | process.env.EXPO_PUBLIC_API_URL |
APP_ENV / vars de shell personalizadas | Evaluación de app.config.ts | Sí (solo en tiempo de compilación, no auto-inline) | extra - Constants.expoConfig.extra |
| Variables / secretos de entorno EAS | Servidores EAS Build | Secretos sí (lado servidor) | Inyecta en extra en tiempo de build |
Campo extra | Horneado en manifest | No - se incluye en binario | Constants.expoConfig.extra |
// .env.development (comprometido - sin secretos)
EXPO_PUBLIC_API_URL=http://localhost:4000
APP_ENV=development// Código de función - EXPO_PUBLIC_ debe usar notación de punto (requerido para inline)
const url = process.env.EXPO_PUBLIC_API_URL; // ✓
// const url = process.env["EXPO_PUBLIC_API_URL"]; // ✗ no se come inlineEXPO_PUBLIC_*, extra, o app.json comprometido..env*.local a .gitignore para overrides específicos de máquina.app.json en código de función - usa Constants.expoConfig para que leas el manifest procesado.npx expo config --type public antes de cada release.| Campo | Propósito |
|---|---|
name / slug | Nombre de pantalla y segmento de URL de proyecto Expo |
scheme | Esquema de deep link (acme://) |
ios.bundleIdentifier / android.package | Identidad de store - debe diferir por entorno cuando se instala lado a lado |
plugins | Mutaciones nativas (permisos, derechos, cambios de Gradle/Pod) |
updates.url / runtimeVersion | Targeting de EAS Update |
extra | JSON arbitrario para runtime - tratar como público |
// types/expo-extra.ts - comparte forma de Extra entre app.config y runtime
export type AppExtra = {
appEnv: "development" | "staging" | "production";
apiUrl: string;
};
// app.config.ts
import { ExpoConfig, ConfigContext } from "expo/config";
import type { AppExtra } from "./types/expo-extra";
export default ({ config }: ConfigContext): ExpoConfig => {
const extra: AppExtra = {
appEnv: "development",
apiUrl: process.env.EXPO_PUBLIC_API_URL ?? "http://localhost:4000",
};
return { ...config, extra };
};Usa tsx para que app.config.ts pueda import módulos de TypeScript locales y config plugins.
EXPO_PUBLIC_ o extra - Ambos terminan en el bundle del cliente; cualquiera puede extraerlos. Fix: Mantén secretos en tu servidor; usa tokens de corta vida; almacena secretos de build solo en EAS Secrets.process.env - const { EXPO_PUBLIC_X } = process.env no se come inline por Metro. Fix: Siempre usa process.env.EXPO_PUBLIC_X notación de punto en código de app.app.config.ts desde componentes - Tira lógica solo-Node a Metro y puede filtrar manejo de env en tiempo de compilación. Fix: Lee Constants.expoConfig o process.env.EXPO_PUBLIC_* en código de app solo.app.config recargue como Fast Refresh - La configuración se re-evalúa cuando Metro reinicia, no en cada hot reload. Fix: Reinicia expo start después de cambiar app.config.ts; haz reload completo de la app para cambios de EXPO_PUBLIC_.com.acme.mobile.staging) por entorno.export default async () => ({}) es inválido; la configuración debe resolverse sincronizadamente. Fix: Lee vars de entorno sincronizadamente; obtén configuración remota desde tu API en tiempo de ejecución de app en su lugar.NODE_ENV para elegir archivos .env - expo export y eas update fuerzan NODE_ENV=production. Fix: Usa APP_ENV explícito o eas env:pull para cambio de entorno.| Alternativa | Usarlo cuando | No usarlo cuando |
|---|---|---|
Solo app.json | Apps simples; sin ramificación de entorno | Múltiples entornos o configuración dinámica tipada |
app.config.ts + extra | Nombres, IDs, feature flags específicos de entorno | Solo necesitas iconos estáticos y slug |
.env files de EXPO_PUBLIC_ | URLs de API seguras para cliente y toggles de función | Claves privadas o secretos por usuario |
| Variables de entorno EAS | Builds de CI/CD con secretos mantenidos en servidor | Prototipado puro de Expo Go sin EAS |
| Configuración remota (Firebase, LaunchDarkly) | Cambiar flags sin reconstruir | Constantes en tiempo de compilación como bundle identifier |
react-native-config (bare) | RN bare heredado sin soporte de env de Expo | Proyectos nuevos de Expo SDK 57 - usa EXPO_PUBLIC_ |
app.json es JSON estático que las herramientas pueden auto-actualizar. app.config.js / app.config.ts es dinámico - soporta variables, ramificación de entorno, y TypeScript. Expo fusiona estática en dinámica cuando exportas una función.
Mantén un app.json mínimo si las herramientas lo esperan, pero prefiere app.config.ts como fuente de verdad para cualquier cosa dependiente de entorno. Ejecuta npx expo config para ver el resultado fusionado.
Usa expo-constants:
import Constants from "expo-constants";
const apiUrl = Constants.expoConfig?.extra?.apiUrl;Para variables de EXPO_PUBLIC_, usa process.env.EXPO_PUBLIC_API_URL directamente en archivos fuente.
Las importaciones directas evitan el pipeline de procesamiento de configuración de Expo y pueden empacar contenidos de archivo crudo. Constants.expoConfig retorna el manifest resuelto, público-seguro usado en builds.
El subset de configuración incrustado en tu app y expuesto a JavaScript - la misma forma disponible mediante Constants.expoConfig. Úsalo para auditar fugas antes de release.
Expo CLI carga archivos .env e integra referencias de process.env.EXPO_PUBLIC_* a tu bundle JavaScript en tiempo de compilación. Son visibles a usuarios finales - nunca almacenes secretos allí.
extra es un objeto JSON arbitrario copiado al manifest público. Pasa feature flags, etiquetas de entorno, o endpoints no-sensibles. Léelo mediante Constants.expoConfig.extra.
Ramifica en app.config.ts en APP_ENV:
ios: {
bundleIdentifier:
env === "production" ? "com.acme.app" : "com.acme.app.staging",
},Configura APP_ENV cuando ejecutes eas build o localmente en tu shell.
No. La configuración dinámica debe retornar un objeto plano sincronizadamente - sin Promises. Obtén configuración remota después del lanzamiento de app en JavaScript en su lugar.
Causas comunes: falta el prefijo EXPO_PUBLIC_, usando notación de bracket (process.env['EXPO_PUBLIC_X']), u olvidaste reiniciar Metro después de cambiar .env. Arregla el prefijo, usa notación de punto, y ejecuta npx expo start --clear.
Lista nombres de plugin o rutas en el array de plugins. Se ejecutan durante prebuild/EAS Build para modificar proyectos nativos (permisos, derechos, Gradle). No se ejecutan en Expo Go para código nativo no ya empaquetado allí.
Compromete .env con defaults no-secretos si es útil. Nunca compretas .env.local o archivos con credenciales. Agrega .env*.local a .gitignore.
NODE_ENV es propiedad de Node y bundlers - expo export fuerza production. APP_ENV es una variable personalizada que controlas para cambio de staging/production en app.config.ts sin pelear con herramientas.
hooks, ios.config, android.config, y campos de firma de código de EAS Update se quitan de Constants.expoConfig. No confíes en leerlos desde JS.
Define un tipo AppExtra compartido y caste:
import Constants from "expo-constants";
import type { AppExtra } from "../types/expo-extra";
const extra = Constants.expoConfig?.extra as AppExtra;Mantén el tipo en un archivo importado por ambos app.config.ts y código de runtime.
Stack versions: Esta página fue escrita para React 19.2.3, React Native 0.86.0, y Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última actualización: 19 jul 2026