Darstellung
ADR 0006 — Installation identity
Status: accepted Date: 2026-08-21
Context
The platform needs to count installations against a plan limit and to recognise the same site across a domain change. The plugin needs a stable identifier that survives normal WordPress operations.
The candidates all have problems. site_url changes on an SSL migration, a www redirect or a domain move. A hash of the database prefix changes on migration. home_url plus the admin email changes when either does.
The site is also copyable: staging refreshes, backups and clones are routine, and none of them are abuse.
Decision
A UUID v4, generated once, plus a server-issued credential.
installation_uuidfromSupport\Uuid, stored inad_form_license_identity, seeded inActivator::activate_site().- Never regenerated on a normal request. Generated lazily only if the option is missing, which is the repair path for installs predating licensing.
- A malformed stored value is replaced rather than trusted.
- Scope is per site, including per blog on multisite. See ADR 0007.
- After activation the platform issues an
installation_token. The licence key is never transmitted again. site_urlandhome_urlare reported verbatim on every request. There is no local URL comparison anywhere. Reconciliation is the API's job.forget_credentials()drops the token, installation id, licence id and masked key but keeps the UUID, so a reconnect maps to the same installation rather than consuming another slot.
The UUID is an identifier, not a secret: safe in logs and support tickets. The token is confidential and never appears in HTML, JavaScript, REST responses or logs, not even partially.
Consequences
Good:
- Stable across SSL migration,
wwwchanges, domain moves and prefix changes. - Reconnecting does not consume an activation.
- No PII in the identifier.
- The plugin cannot mistakenly deactivate a working production site by comparing strings, which is the classic way licensing breaks a customer's site.
Costs:
- A cloned site carries the same UUID, so two installations report the same identity. Intentional: the plugin reports the collision alongside the differing URLs and the API decides. A local clone check would misfire on legitimate restores.
- Wiping the option looks like a new installation. Bounded by the platform's activation limit, and visible server-side as repeated activations of one licence.
Lifecycle
| Event | Behaviour |
|---|---|
| First activation | Generate and store |
| Normal request | Read only |
| Backup restore | UUID returns with the backup, which is correct |
| Domain migration | Same UUID, new URL, both reported |
| Clone or staging refresh | Same UUID, different URL, both reported. API decides |
| Plugin deactivated | Untouched |
| Plugin deleted, data deletion enabled | Removed with the other options |
Rejected
Derive identity from site_url. Breaks on every URL change, which is the most common support case in licensing systems.
Hash the database prefix or table names. Breaks on migration.
Use the admin email. PII, and it changes.
Detect clones locally and self-deactivate. The plugin cannot distinguish a clone from a restore, and getting it wrong disables a paying customer's production site. Reporting and letting the API decide is strictly better.
Regenerate the UUID when the URL changes. Turns every migration into a new activation.