Botón install renderizado por el navegador sobre fondo oscuro con código HTML
Volver al blog

El nuevo HTML install: instala PWAs sin JavaScript

Chrome y Edge probaron install, un elemento HTML que renderiza un botón para instalar PWAs con texto traducido por el navegador y dispara el flujo sin una línea de JavaScript. Repasamos sintaxis, los atributos installurl y manifestid, eventos, el origin trial 148-153 y cómo cablearlo en Astro.

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:

  1. 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.
  2. 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.
  3. 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:

  1. Entrás a chromestatus.com → origin trials y buscás “Web app HTML install element”.
  2. Pegás el origin de tu sitio (https://azirgo.com).
  3. Aceptás los términos y te devuelven un token largo.
  4. 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.

EnfoqueModeloCódigoConfianza navegadorCross-originLocalización
<install>Declarativo (HTML)1 tagAltaSí (installurl)Automática
navigator.install()Imperativo (JS)Promesa + user activationMediaSí (parámetro URL)Manual
beforeinstallpromptEvento (JS)Listener + estadoBajaNo (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.

  1. 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.
  2. 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.
  3. 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 dar manifestid correcto), 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

PreguntaRespuesta 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?
No. La forma básica del elemento, <install></install>, renderiza un botón funcional sin una sola línea de JavaScript. El navegador maneja el flujo de instalación, la traducción del texto y el ícono. Solo necesitas JavaScript si quieres escuchar los eventos promptaction o promptdismiss para medir conversiones.
¿Qué pasa en Safari y Firefox si uso <install>?
Safari y Firefox ignoran el elemento — lo tratan como un custom element vacío. Por eso conviene poner siempre un fallback dentro del tag, típicamente un <a> que abra la app en una pestaña nueva. Los navegadores con soporte ocultan ese fallback y dibujan su botón nativo encima.
¿Cuál es la diferencia entre installurl y manifestid?
installurl apunta a la URL de la PWA que quieres instalar (su start_url o cualquier página dentro del scope). manifestid es necesario solo si el manifest.json de esa app no define un campo id propio: en ese caso le pasas tú el ID computado para que el navegador identifique inequívocamente la app. Si el manifest ya define id, manifestid es opcional.
¿Puedo instalar una PWA de otro dominio desde mi sitio?
Sí. Esa es justamente la novedad del atributo installurl: tu página puede ofrecer un botón que instala una app alojada en otro origen. Esto habilita patrones tipo catálogo o app store hechos en HTML plano. Es el caso de uso que más diferencia <install> del antiguo beforeinstallprompt, que solo permitía instalar la app del sitio actual.
¿Hasta cuándo dura el origin trial?
El origin trial conjunto de Chrome y Edge va de la versión 148 (lanzada a finales de 2025) hasta la versión 153. Después de esa fecha, el elemento queda detrás de una bandera hasta que se promueva a feature estable, o vuelve a desactivarse si la propuesta cambia. Es prudente mantener el fallback siempre presente, no como solo redundancia.
¿Cómo lo pruebo en local sin registrarme al origin trial?
Abrí chrome://flags/#web-app-install-element en Chrome o Edge, ponelo en Enabled y reiniciás el navegador. A partir de ese momento el elemento <install> funciona en cualquier página que visites — sin necesidad de token, sin necesidad de servidor con headers especiales. Es la ruta recomendada para desarrollo y demos internas.
¿Cómo lo integro en un proyecto Astro?
Astro genera HTML estático, así que el elemento encaja sin hydration ni islas. La forma limpia es envolver el tag en un componente .astro que reciba installurl, manifestid y un label como props, e incluya un <a> de fallback. Después referenciás un manifest.webmanifest válido desde el layout base con <link rel='manifest'> y listo.

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