- Marijn Kruisselbrink (Google)
- Issue Tracker: WICG/manifest-incubations on GitHub (for spec-related issues)
- Chromium Implementation Bug: crbug.com/380486617 (for implementation-specific discussion)
When a user installs a Progressive Web App (PWA), its identity and security context are tightly bound to its web origin (e.g., app.example.com). This presents a significant challenge for developers who need to change their PWA's origin due to rebranding, domain restructuring, or technical re-architecture. Currently, such a change forces users to manually uninstall the old app and reinstall the new one, leading to a disruptive experience and potential user loss. This proposal introduces a mechanism for developers to seamlessly migrate an installed PWA to a new, same-site origin, preserving user trust and permissions.
Imagine you have the "SocialApp" app installed on your computer from www.example.com/social. One day, the company decides to move the app to its own dedicated home at social.example.com. Without a migration mechanism, the app you have installed would either break or redirect you to the new site in a generic browser window, losing its app-like feel. You would have to figure out that you need to uninstall the old app and install the new one from the new address. You might also lose your settings, like whether you've allowed the app to send notifications.
This feature aims to make that transition seamless. Instead of a broken experience, the app would notify you of an available update. With your approval, the app would relaunch from its new home at social.example.com.
Another common problem occurs when a developer accidentally creates multiple "apps" for the same service. Because a PWA's identity historically defaulted to its start_url, a developer who changed the starting path of their app (e.g., from /app to /launcher) without specifying a stable id would inadvertently create a second, separate app installation for their users.
In this scenario, a user might end up with two versions of the same app on their device. The older version becomes "stale"—it can no longer be updated because the site it pointed to has changed—but it remains in the user's app list, leading to confusion and a cluttered experience. This proposal provides a way for developers to merge these "split" apps by migrating the identity of the stale app into the new, correct one.
- Seamless User Experience: Users should be able to transition to a new PWA origin with a simple, non-disruptive update flow.
- Developer Enablement: Provide a clear mechanism for developers to migrate their PWAs for reasons like rebranding (e.g., "ConnectApp" to "ConnectX"), SEO optimization (e.g.,
photo-app.example.comtophotos.example.com/app), or backend architecture changes (e.g.,mail.example.comtoapp.example.com). - Resolve Duplicate Identities: Allow developers to fix "split apps" where multiple installations were created accidentally due to changing URLs without a stable manifest ID.
- Permission Persistence (Future): While not in the initial version, the design should allow for future secure migration of user-granted permissions.
- Security: The migration process must be secure, preventing malicious takeovers or phishing attacks.
- Cross-Site Migration: While we may explore solutions for cross-origin migration in the future, this initial proposal is limited to same-site migrations. Allowing migration between different sites (e.g., from
example.comtoanother-site.com) would introduce significantly more security risks. For example, if a site were compromised, an attacker could migrate its users to a malicious phishing site, potentially transferring user trust and any granted permissions. The same-site restriction ensures that both the old and new origins are controlled by the same entity, reducing the risk of this category of attack. - Data Migration: User data stored locally (e.g., in IndexedDB, Cache Storage) is not within the scope of this migration. Developers are responsible for migrating this data, typically via server-side logic.
The solution is a handshake between the old and new origins. A developer signals the migration by adding a migrate_from member to the web app manifest of the new application, and confirms this on the old origin using a .well-known association file. The migration can be discovered in two ways:
- By visiting the new app: The user navigates to the new site (for example because the old site redirects to the new site). The browser fetches its manifest, sees a
migrate_fromfield pointing to the old, installed app, and validates this against the.well-knownfile on the old origin. - By visiting the old app: The browser checks for an update to the old app's manifest and sees a
migrate_tofield. The browser fetches the manifest for the new site, verifies it has a matchingmigrate_fromfield, and validates this against the.well-knownfile on the old origin.
This proposal is designed to work with and depends on the Predictable App Updating proposal. The user-facing update prompts and the timing of migration checks will align with the mechanisms defined in that feature.
1. The "New" App Declares the Migration (Required):
The developer adds a migrate_from member to the manifest of the PWA at the new origin. This declares that it claims to be the successor to the old app. For the browser to process the migrate_from field, the manifest must also contain an id field.
Manifest on social.example.com:
{
"name": "SocialApp",
"id": "/",
"start_url": "/",
"migrate_from": [
"https://www.example.com/social/"
]
}2. The "Old" App Confirms the Migration (Required):
The old origin must confirm the migration by hosting a web-app-origin-association file in the /.well-known/ directory. This file must list the id of the new app, and explicitly authorize the migration by setting "allow_migration": true. This allows any PWA hosted on the old origin to migrate to the specific PWA identified by that ID on the new origin. This mechanism is shared with Scope Extensions.
File at https://www.example.com/.well-known/web-app-origin-association:
{
"https://social.example.com/": {
"allow_migration": true
}
}3. The "Old" App Proactively Signals the Migration (Optional):
To proactively trigger the update flow without waiting for the user to visit the new site, the developer can add a migrate_to member to the manifest of the PWA at the original origin.
Manifest on www.example.com:
{
"name": "SocialApp (Legacy)",
"id": "/social/",
"start_url": "/social/start",
"migrate_to": {
"id": "https://social.example.com/",
"install_url": "https://social.example.com/download?usp=migrate"
}
}Once the migration is discovered and validated, the browser will present the user with an update prompt. The developer can control the user experience with the optional behavior field in the migrate_from object:
"suggest": The user is (passively) notified of the update but can ignore it."force": The next time the user launches the app being migrated (after a manifest is parsed with this behavior specified), they are presented with a blocking dialog requiring them to either migrate or uninstall the app.
In the initial version of this proposal, permissions are not migrated. When an app migrates to a new origin, it is treated as a new entity with whatever permissions were previously denied or granted to the new origin. The user will be prompted to grant permissions (like Notifications or Location) again when the new app requests them.
We may explore migrating user-granted permissions in the future. If implemented, this would likely involve a "most restrictive" merge strategy (respecting the strictest setting between the old and new origin) and require explicit user confirmation. However, to reduce initial complexity and potential confusion, this is currently out of scope.
While this proposal primarily focuses on cross-origin, same-site migrations, the same manifest fields can also be used for same-origin migrations. For example, a developer might want to change the id of their PWA, consolidate multiple PWAs on the same origin, or fix a common issue where the PWA's identity accidentally changes. This can happen if a developer modifies the start_url of their app without having an explicit id set in the manifest, as the start_url is used as a fallback for the PWA's identity. A same-origin migration can be used to seamlessly move users from the old identity to a new, explicitly defined one.
In these cases, because the origin of the PWA is not changing, the security context and user-granted permissions remain tied to the same origin. As such, the .well-known association file is not required, and the user agent would not need to show any UI to the user and could perform a silent migration in the background. This provides a seamless way for developers to manage the identity of their PWAs on the same origin without disrupting the user.
To illustrate the user flow, here are some ASCII art mockups of what the UI could look like.
"Suggest" Behavior Dialog:
This dialog can be triggered when a migration is available and the developer has not marked it as "force".
+-----------------------------------------------------+
| A new version of this app is available. |
|-----------------------------------------------------|
| |
| Review the URL change to make sure you're |
| comfortable with it before updating. |
| |
| URL: https://www.example.com/social/ -> |
| https://social.example.com/ |
| |
| [ Ignore ] [ Uninstall ] [ Relaunch to update ] |
+-----------------------------------------------------+
For "force" update, a similar dialog would appear on next launch, with the major difference being that the "Ignore" button is absent.
A common migration strategy is to redirect all traffic from the old origin to the new one. This creates a problem: if the browser cannot access the old origin's pages, it can no longer fetch the old manifest to process important updates that might be needed before the user migrates, such as changes to scope_extensions.
To solve this, the migrate_from field in the new app's manifest can be an object that includes an optional install_url. This URL must not redirect to the new app, and point to the old app's manifest. This gives the browser a stable endpoint to keep the old, unmigrated app up-to-date.
Manifest on social.example.com with optional install_url:
{
"name": "SocialApp",
"id": "/",
"start_url": "/",
"migrate_from": [
{
"id": "https://www.example.com/social/",
"install_url": "https://www.example.com/social/install.html?noredirect=true"
}
]
}PWA Origin Migration is a specialized form of a PWA update. It is designed to work harmoniously with the broader Predictable App Updating proposal.
Developers can combine an origin migration with other manifest changes, such as updating the app's name or icon. When using the "suggest" behavior, the update dialog will present all these changes to the user for review, just like a standard update.
However, there is a critical security consideration for "force" migrations. To prevent a developer from forcing a user to accept a potentially deceptive name or icon change, the browser will enforce a separation of concerns. During a forced migration, the browser will only apply the URL change. If the new manifest also contains changes to security-sensitive fields like name or icon, the browser will defer these changes. After the migration is complete, these deferred changes will be processed as a standard, ignorable update, allowing the user to review them separately and preserving user control over the app's identity.
We considered using HTTP redirects (e.g., 301 Moved Permanently) as the primary mechanism for triggering a migration, rather than a manifest-based handshake.
-
Pros:
- Strong Signal: A 301 redirect is a standard, explicit signal that a resource has moved. It works without requiring the old site to host a specific manifest or verification file (like the origin association file).
-
Cons:
- Limited Discovery: Migration detection via redirect only works if the user navigates to the old URL. This is effective if the user launches the old installed app (which loads the old
start_url), but fails if the user accesses the new app directly (e.g., via a link shared by a colleague or a calendar invite). In those cases, the browser never hits the old origin and thus never sees the redirect signal to trigger the migration. - Conflation of Navigation and Identity: A redirect signals that a page has moved, but not necessarily that the app identity should change. Relying solely on redirects makes it difficult to distinguish between a simple URL restructuring (where the app identity remains the same) and a true migration.
- No "Soft" Migration: Redirects are immediate. It is difficult to support the "suggest" behavior (where the user is notified but can choose to migrate later) because the navigation has already happened. The user is effectively forced into the new experience immediately.
- Cannot Handle "Split" Apps: As described in the "User-Facing Problem", developers may have accidentally created multiple app installations for the same logical app. If these apps are on the same origin and don't involve a URL change (just a manifest/ID fix), a redirect cannot trigger the migration because the URL hasn't changed.
- Limited Discovery: Migration detection via redirect only works if the user navigates to the old URL. This is effective if the user launches the old installed app (which loads the old
While redirects are a useful trigger for discovery (and indeed, if an old app redirects to a new one, the browser will see the new manifest and can initiate the handshake), we believe the manifest-based approach is necessary to provide the metadata required for a controlled, secure, and predictable application lifecycle transition.
We considered removing the optional behavior field and having all migrations default to the "force" behavior (blocking the user until they migrate or uninstall).
- Pros:
- API Simplicity: Simplifies the manifest schema by removing a configuration member.
- Developer Certainty: Ensures that developers can reliably move their entire user base to the new origin without leaving "zombie" installations on the old, unsupported origin.
- Cons:
- Disruptive UX: Not all migrations require a hard cutover. Developers might prefer a "soft" transition period where the migration is suggested but the user can continue using the old app for a few weeks (e.g., while legacy backend APIs are still being phased out).
- Identity Change Friction: As described in the security section, "force" migrations defer name and icon changes to a second, separate update to prevent deceptive branding changes. If every migration were forced, users would always be required to undergo a two-step branding update, which could be more confusing than a single, ignorable "suggested" update that includes both the URL and branding changes.
By providing the behavior flag, we give developers the flexibility to manage their transition according to their specific technical and product requirements.
A mandatory waiting period (e.g., 2 weeks) after a migration is declared was considered to help prevent malicious activity if a site is compromised. However, this was deemed overly complex and potentially not useful, as there's no guarantee a developer would notice a compromise within that period. The same-site restriction is a much stronger and more reliable security guarantee, as it ensures both origins are controlled by the same entity.
Instead of relying on a .well-known file on the old origin, we considered requiring the migrate_from field in the new manifest to always specify an install_url. This URL would point to the old origin's manifest, allowing the browser to fetch and verify the old manifest's intent to migrate (via a migrate_to field). While this achieves a two-way handshake, it requires the old manifest to be updated and maintained, which can be challenging if the old site is already redirecting or is no longer actively developed. The .well-known file approach is preferred as it decouples the verification from the manifest update cycle and aligns with scope_extensions.
We previously considered only requiring a one-way reference from the new manifest to the old one. The main advantage of this approach was simplicity and resilience in scenarios where the old site might be difficult to update or no longer actively maintained.
However, security reviews highlighted that without a two-way reference, the security of an origin is effectively only as strong as the weakest same-site origin. A compromised same-site origin could unilaterally claim to be the successor to another app, potentially hijacking it. Therefore, a strict two-way handshake (via .well-known or install_url) is now required.
Another alternative considered was to make the id manifest member mandatory for not just the new, but also the old app. In this model, the browser would ignore the migrate_to and migrate_from fields entirely if an explicit id was not present in the manifest. This would enforce the best practice of using a stable identifier, preventing developers from accidentally creating new app identities by changing the start_url, which would break the migration. However, this was rejected as it would create significant friction for developers with existing PWAs that do not have an id set. They would be forced to update their old app first and wait for it to propagate before they could begin a migration, adding complexity and delay to the process. The current proposal is more flexible and accommodating to existing applications.
We also considered not requiring an id field in the manifest at all for migrations. The migration process itself does not technically depend on the id field. The primary motivation for requiring it is to encourage the adoption of a best practice that provides a stable identity for a PWA, preventing potential issues in the future. However, making this a requirement for an unrelated feature could be seen as using the migration mechanism to enforce an orthogonal best practice.
We considered that not all same-site migrations carry the same level of risk. For instance, migrating from a specific subdomain like maps.example.com to a broader one like www.example.com could unintentionally widen the scope of permissions in a way a user might not expect. We explored introducing stricter requirements or different UI treatments for these "scope-widening" migrations. Ultimately, we opted for a single, consistent rule for all same-site migrations to keep the feature simple and predictable for both developers and users, relying on the explicit, post-migration permission UI to ensure user clarity and consent.
The user-facing UI for the migration (update notifications, confirmation dialogs) will be built using existing, accessible browser components. Even when the site requests "force" behavior for the migration, the browser should not interrupt the user with what they are doing. Instead the migration UI should be triggered for example the next time the user launches the app being migrated.
The primary privacy consideration is the migration of identity. A user who installed an app from old.example.com might not want new.example.com to control that app. This is mitigated by the same-site restriction ensuring the same entity controls both origins. In the initial version of this proposal, permissions are NOT migrated, meaning the new origin starts with default permissions.
The primary security threat is a hostile takeover, where a malicious actor could try to migrate a legitimate PWA to a phishing site. The same-site restriction is the critical defense here. Since an attacker cannot control a different eTLD+1, they cannot hijack a PWA. For example, example.com could not be migrated to evil-phishing-site.com.
- Major App Developers: Positive. Several have expressed a strong need for this feature to support their product roadmaps. Their feedback has been crucial in defining the requirements, particularly the need for both "suggested" and "forced" migration behaviors.
- W3C TAG Review: Issue 1164
- Browser Standards Positions:
- Chrome: Prototyping/Implementing
- Mozilla: mozilla/standards-positions#1313
- WebKit: WebKit/standards-positions#568
This section contains answers to the W3C Security and Privacy Self-Review Questionnaire.
This feature does not directly expose user information to websites. It allows a website (new.example.com) to declare a relationship with another (old.example.com). The browser uses this information, combined with its own knowledge of whether the user has the old PWA installed, to initiate a migration UI. The new site does not receive a confirmation that the user has the old app installed; the entire process is mediated by the user through the browser's UI.
2.2 Do features in your specification expose the minimum amount of information necessary to implement the intended functionality?
Yes. The feature only exposes the developer-declared relationship between two origins. No user-specific information is exposed to the web.
2.3 Do the features in your specification expose personal information, personally-identifiable information (PII), or information derived from either?
No.
Not applicable. No sensitive information is exposed.
2.5 Does data exposed by your specification carry related but distinct information that may not be obvious to users?
No.
Yes, the result of a successful migration is persistent: the installed PWA's identity is permanently changed from the old origin to the new one. This is a change to existing persistent state (the app installation), not the introduction of a new type of storage.
2.7 Do the features in your specification expose information about the underlying platform to origins?
No.
No.
No. While future extensions might allow for permission migration, the initial version treats the new origin as a fresh install with default permissions. The user must grant access to device sensors again on the new origin.
No.
No.
2.12 Do features in this specification allow an origin some measure of control over a user agent’s native UI?
Yes. The core purpose of the feature is to change the origin associated with an installed PWA's window and identity. For cross-origin migrations, this is a significant change to the user's app experience, and it is explicitly communicated to the user for their approval before it occurs. For same-origin migrations, this change can happen without user interaction as the security context remains the same.
None.
2.14 How does this specification distinguish between behavior in first-party and third-party contexts?
This feature is only relevant in a first-party context, as a web app manifest is only applicable to a top-level browsing context.
2.15 How do the features in this specification work in the context of a browser’s Private Browsing or Incognito mode?
This feature does not apply to Private Browsing or Incognito mode, as PWA installation is generally not available in those modes.
2.16 Does this specification have both "Security Considerations" and "Privacy Considerations" sections?
Yes, they are present in this document.
No.
2.18 What happens when a document that uses your feature is kept alive in BFCache (instead of getting destroyed) after navigation, and potentially gets reused on future navigations back to the document?
The migration process is managed by the browser's systems, not by a specific document. The discovery of a migration might be triggered by fetching a manifest from a document, but the subsequent UI and state changes are not tied to that document's lifecycle. Therefore, a document involved in the discovery process being placed in BFCache will not affect the migration.
Similar to the BFCache case, the migration process is not tied to the lifecycle of any single document. If a document that triggered a migration check is disconnected (e.g., an iframe is removed), the browser-level migration process will proceed unaffected.
The specification will need to define error handling for cases such as a migrate_to field pointing to a URL that returns a 404 error, or a manifest that is invalid or does not contain the required reciprocal migrate_from field. These errors will be handled by the browser and will result in the migration not being offered to the user. They could be exposed to developers in the DevTools console but will not be exposed to the web.
No. The UI surfaces for this feature (dialogs, notifications) will be implemented using standard, accessible browser components and will not introduce any mechanism for detecting the use of assistive technology.
N/A