Durante años el patrón para invitar a un usuario a instalar tu PWA fue siempre el mismo y siempre torpe: esperar el evento beforeinstallprompt, guardarlo en una variable, pintar tu propio botón, decidir cuándo mostrarlo, traducirlo a mano, lidiar con que Safari y Firefox lo ignoren, y rezar para que el navegador no decida que tu invocación no fue “lo suficientemente intencional”. Chrome y Edge acaban de proponer una salida más simple: un elemento HTML llamado <install> que renderiza el botón por ti, en el idioma del usuario, y abre el flujo de instalación sin que toques una línea de JavaScript.
El elemento está en origin trial conjunto de Chrome y Edge desde la versión 148 y se mantiene hasta la 153 (Chrome for Developers). Es la pieza declarativa de la Web Install API, complementa — no reemplaza — a navigator.install() y promete dejar atrás el ceremonial alrededor de beforeinstallprompt. Esta nota repasa la sintaxis exacta, los cuatro casos que vas a encontrar en producción, cómo cablearlo en un proyecto Astro y dónde están las trampas reales en mayo de 2026.
Qué hace exactamente el elemento install
El elemento se escribe como cualquier otro tag, en cualquier parte del documento:
<install></install>
Sin atributos, instala la app del sitio actual (el que tiene el manifest.json enlazado en <head>). Con atributos, puede apuntar a otra PWA distinta, incluso de otro origen. El navegador decide la posición del botón y su tamaño dentro del flujo natural del DOM, pero se reserva el contenido: texto, ícono y estilos base son del navegador, no del autor.
El botón es del navegador, no tuyo
Esto es lo que cambia el modelo mental. En beforeinstallprompt tú pintas el botón, eliges el copy, manejas el evento y al final llamas prompt() sobre el BeforeInstallPromptEvent. Con <install> el botón se renderiza con texto e iconografía estandarizados que el navegador localiza al idioma del usuario. Esto le da al navegador suficiente confianza como para abrir el flujo nativo sin exigir una “user activation” rígida — el usuario está clickeando un control que el propio navegador dibujó.
La consecuencia práctica: pierdes algo de control visual, ganas un montón de garantías. El botón nunca va a decir “Instala ya” en un sitio en alemán; nunca va a parecer un anuncio; nunca va a aparecer en sistemas donde la instalación de PWAs no está soportada — el elemento simplemente no renderiza nada y tu fallback queda visible.
Por qué importa esto
Hay tres razones técnicas por las que esto no es solo azúcar sintáctica:
- Sin handshake JS: no hay que esperar un evento, no hay que decidir cuándo mostrar el botón, no hay que guardar referencias en variables globales sucias.
- Cross-origin: una página puede ofrecer instalar una app de otro dominio, lo que abre la puerta a catálogos tipo app store hechos en HTML plano.
- Localización gratis: cero traducciones que mantener para “Install”, “Instalar”, “Installer”, “Установить”.
Sintaxis: cuatro casos en cinco líneas
Toda la API se resume en dos atributos. El resto son combinaciones.
Instalar la app actual
El caso más común: tu sitio tiene un manifest.json con id definido y quieres ofrecer el botón.
<install>Instalar Azirgo</install>
El contenido entre las etiquetas se usa como fallback si el navegador no soporta <install> — más sobre esto abajo. Si el navegador sí lo soporta, lo ignora y renderiza su propio botón.
Instalar una app de otro origen
Aquí está la parte interesante. Una página puede ofrecer instalar una PWA distinta apuntando con installurl:
<install installurl="https://app.azirgo.com/"></install>
El navegador va a fetchear la URL, descubrir su manifest y, si encuentra un campo id, está listo. Esto desbloquea páginas-catálogo donde una sola URL lista decenas de apps instalables, cada una con su propio <install>. La demo oficial del equipo de Edge muestra exactamente este patrón.
Cuando el manifest no tiene id
Si la app que quieres instalar no define id en su manifest, tienes que dárselo explícito como manifestid:
<install
installurl="https://app.azirgo.com/"
manifestid="https://app.azirgo.com/?source=pwa"
></install>
El manifestid se calcula como start_url resuelto contra el manifest, no es un identificador arbitrario. Para obtenerlo sin equivocarse, abrí DevTools en la app destino, vas a Application → Manifest → Identity → Computed App ID y copiás el valor. Si lo escribes a mano, una barra de más y la instalación falla con install_data_invalid.
Fallback para navegadores sin soporte
Safari y Firefox ignoran el elemento — lo tratan como un custom element vacío. Chrome y Edge antes de 148 también. La solución es pintar fallback dentro del tag:
<install installurl="https://app.azirgo.com/">
<a href="https://app.azirgo.com/" class="btn-instalar">
Abrir Azirgo
</a>
</install>
Los navegadores que soportan <install> esconden ese contenido y dibujan su botón. Los que no, lo muestran como cualquier otro hijo del DOM. Es la misma técnica de degradación que <picture> o <video> con <source>.
Eventos JavaScript que dispara
El elemento es declarativo, pero sigue siendo programable. Dispara tres eventos sobre el propio HTMLInstallElement:
const install = document.querySelector("install")
install.addEventListener("promptaction", () => {
// El usuario aceptó. La app quedó instalada.
gtag("event", "pwa_installed")
})
install.addEventListener("promptdismiss", () => {
// El usuario cerró el prompt sin instalar.
gtag("event", "pwa_dismissed")
})
install.addEventListener("validationstatuschanged", (e) => {
// El manifest no es válido, el id no coincide, etc.
console.error("Install inválido:", e.target.invalidReason)
})
invalidReason toma valores como install_data_invalid, manifest_id_mismatch, not_installable. Lo que vas a usar en producción es promptaction para medir conversiones de instalación — si lo único que mides es el click sobre el botón, te quedas sin saber si el usuario realmente terminó el flujo.
Para detectar soporte antes de hacer cualquier cosa:
if ("HTMLInstallElement" in window) {
// El elemento existe en este navegador.
}
Cómo activarlo hoy
Hay dos rutas, según si estás en local o en producción.
Vía flag local (desarrollo)
Para probar sin tokens, abrís Chrome o Edge y vas al flag interno:
chrome://flags/#web-app-install-element
Lo pones en Enabled, reiniciás el navegador y listo — el elemento queda activo en todas las páginas. Es la ruta que vas a usar mientras prototipas.
Vía origin trial (producción)
Para que funcione en tus usuarios reales sin pedirles que toquen flags, te registrás en el origin trial:
- Entrás a chromestatus.com → origin trials y buscás “Web app HTML install element”.
- Pegás el origin de tu sitio (
https://azirgo.com). - Aceptás los términos y te devuelven un token largo.
- Lo inyectás en cada página donde uses
<install>con un meta:
<meta http-equiv="origin-trial" content="TU_TOKEN_AQUI" />
O, si preferís evitar el meta, lo mandás como header HTTP:
Origin-Trial: TU_TOKEN_AQUI
El mismo token funciona en Chrome y Edge — no necesitás dos registros separados. El trial expira con la versión 153, así que tarde o temprano vas a tener que migrar al feature definitivo o volver al fallback.
Implementación en un proyecto Astro
Astro renderiza HTML estático, así que <install> encaja perfecto: el server emite el tag, el cliente lo interpreta. No hace falta hydration, no hace falta una isla. Aún así conviene encapsularlo en un componente para inyectar el token de origin trial una sola vez y tener fallback consistente.
Componente reutilizable Install.astro
Esta es la versión mínima utilizable que puedes copiar a src/components/Install.astro:
---
interface Props {
installurl?: string
manifestid?: string
label?: string
}
const {
installurl,
manifestid,
label = "Instalar la app",
} = Astro.props
---
<install installurl={installurl} manifestid={manifestid}>
<a
href={installurl ?? "/"}
class="inline-flex items-center gap-2 rounded-full bg-[#005BFC] px-5 py-2.5 text-sm font-medium text-white hover:bg-[#0034a8]"
>
{label}
</a>
</install>
Y lo usas desde cualquier página:
---
import Install from "@/components/Install.astro"
---
<section class="py-12">
<h2>Llévate Azirgo al inicio</h2>
<p>Una experiencia más rápida, offline-first.</p>
<Install label="Instalar Azirgo" />
</section>
El atributo class del <a> solo se ve en navegadores sin soporte. En Chrome 148+ con la flag activa, el botón del navegador aparece encima y oculta el fallback.
Manifest desde Astro
Para que el elemento funcione, la app necesita un manifest.json accesible y enlazado desde <head>. En Astro lo más limpio es servirlo desde public/ y referenciarlo en el layout base:
{
"id": "/",
"name": "Azirgo",
"short_name": "Azirgo",
"start_url": "/?source=pwa",
"display": "standalone",
"background_color": "#0a0a0a",
"theme_color": "#005BFC",
"icons": [
{ "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" }
]
}
Y en src/layouts/Base.astro:
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#005BFC" />
El campo id es lo que permite usar <install> sin tener que pasar manifestid desde otros sitios. Si tu manifest ya estaba en producción sin id, agregárselo está bien — Chrome lo trata como retro-compatible.
Cómo se compara con lo que ya tenemos
Hay tres formas hoy de gatillar la instalación de una PWA. Conviene tener clara la diferencia.
| Enfoque | Modelo | Código | Confianza navegador | Cross-origin | Localización |
|---|---|---|---|---|---|
<install> | Declarativo (HTML) | 1 tag | Alta | Sí (installurl) | Automática |
navigator.install() | Imperativo (JS) | Promesa + user activation | Media | Sí (parámetro URL) | Manual |
beforeinstallprompt | Evento (JS) | Listener + estado | Baja | No (solo app actual) | Manual |
navigator.install() y <install> viven en la misma especificación — la Web Install API — y comparten el origin trial. La diferencia es que navigator.install() lo controlas vos (cuándo, dónde, con qué condición), pero pagás en complejidad y en que el navegador exige una user activation reciente. <install> la confianza la pone el navegador, pero perdés el control fino sobre el momento.
beforeinstallprompt no se va a ningún lado: sigue siendo el camino para apps que quieran un control total del momento del prompt, o que necesiten soporte fuera de Chrome/Edge. La elección no es excluyente — podés tener un <install> visible siempre y, en paralelo, escuchar beforeinstallprompt para mostrar un banner contextual.
Para el caso típico de un sitio marketing-first que quiere ofrecer “instalar como app” sin ceremonia, <install> gana por simplicidad. Para una app compleja que ya tiene su propio onboarding y mide cada paso del funnel, navigator.install() te da más palancas.
Limitaciones reales en mayo 2026
Antes de que esto llegue a un sitio de producción serio, hay tres cosas que duelen.
- Solo Chrome y Edge. Safari ignora el tag, Firefox también. En la práctica significa que el fallback siempre se va a renderizar para una parte real de tu audiencia. No es opcional, es la mayoría del tráfico móvil.
- No funciona en incógnito. En ventanas privadas la Web Install API entera (tanto el elemento como
navigator.install()) está deshabilitada. Si tu CTA principal de instalación está envuelto en<install>, en incógnito el usuario ve el<a>de fallback. Probalo. - Cross-origin requiere participación del destino. El sitio que querés instalar tiene que tener un manifest con
id(o vas a tener que darmanifestidcorrecto), y va a aparecer en tu catálogo solo si el navegador puede fetchear ese manifest sin redirecciones raras. Una página catálogo apuntando a apps mal configuradas se ve mal — no muestra nada.
Hay un cuarto detalle que no es limitación pero sí trampa: el elemento no implementa “instalar y abrir”. Después de instalar, el usuario queda en tu página. Si tu UX espera que termine en la app standalone, mostralo explícito en el copy o escuchá promptaction para redirigir.
Tabla resumen
| Pregunta | Respuesta corta |
|---|---|
| ¿En qué navegadores funciona hoy? | Chrome y Edge 148 a 153 (origin trial) o detrás de flag |
| ¿Qué necesita la app destino? | Un manifest.json válido, idealmente con id definido |
| ¿Se puede personalizar el botón? | No: el navegador controla texto, ícono y estilos base |
| ¿Funciona en Safari/Firefox? | No — usa fallback dentro del tag |
¿Reemplaza a beforeinstallprompt? | No, convive — <install> es declarativo, el evento es imperativo |
| ¿Cómo mido conversiones? | Escuchá los eventos promptaction y promptdismiss |
Preguntas frecuentes
¿Necesito JavaScript para usar el elemento install?
¿Qué pasa en Safari y Firefox si uso <install>?
¿Cuál es la diferencia entre installurl y manifestid?
¿Puedo instalar una PWA de otro dominio desde mi sitio?
¿Hasta cuándo dura el origin trial?
¿Cómo lo pruebo en local sin registrarme al origin trial?
¿Cómo lo integro en un proyecto Astro?
Azirgo
¿Listo para construir tu Producto Digital?
Sitios web, apps móviles, software a medida y soluciones blockchain. Cuéntanos qué tienes en mente y armamos un plan claro contigo.
- Cotización clara en 48 horas
- Equipo en Ecuador, atención en español
- Desde un MVP hasta un producto en producción