Budibase/budibase · error · OfflineLicenseMismatchError
Invalid offline license
Error message
Invalid offline license
What it means
verifyInstallation compares the license's embedded identifier (installId + unique tenantId) against the current installation's identifier and throws OfflineLicenseMismatchError('Invalid offline license') when they differ. The message is intentionally vague for security reasons. It prevents an offline license issued for one installation/tenant from being used on another.
Source
Thrown at packages/pro/src/sdk/licensing/licenses/offline/offline.ts:85
export function verifyExpiry(license: OfflineLicense) {
const now = Date.now()
const expireAt = new Date(license.expireAt).getTime()
if (!Number.isFinite(expireAt) || now > expireAt) {
throw new Error(`Offline license has expired. expireAt=${license.expireAt}`)
}
}
export class OfflineLicenseMismatchError extends Error {}
export async function verifyInstallation(license: OfflineLicense) {
const identifier = await getIdentifier()
if (
license.identifier.installId !== identifier.installId ||
license.identifier.tenantId !== identifier.tenantId
) {
// be intentionally vague
throw new OfflineLicenseMismatchError("Invalid offline license")
}
}
export function enrichLicense(license: OfflineLicense) {
const planType = license.plan.type
const hosting = Hosting.SELF // offline only applicable in self host
const _features = features.getOfflineFeatures(planType)
const _quotas = quotas.getQuotas(hosting, planType)
// apply the latest features and quotas to license
license.features = union(license.features, _features)
license.quotas = merge(license.quotas, _quotas)
return license
}
export async function verifyOfflineLicenseToken(
token: stringView on GitHub (pinned to a81a902e9a)
Solutions
- Export the offline license token from the installation you intend to activate it on (matching installId and tenant).
- Regenerate the identifier file from the target installation and request a new token from the account portal using that identifier.
- If the installId legitimately changed (restore/migration), contact support to rebind the license to the new install.
- Ensure the tenant context (tenantId) when activating matches the tenant the token was issued for.
Example fix
// before
await activateOfflineLicenseToken(tokenFromOtherInstall)
// after
const identifier = await getIdentifier()
console.log("Request token for installId:", identifier.installId) // then activate the matching token
await activateOfflineLicenseToken(tokenForThisInstall) Defensive patterns
Strategy: validation
Validate before calling
const localIdentifier = await getIdentifier()
const tokenLicense = /* decode token payload */
if (tokenLicense.identifier.installId !== localIdentifier.installId ||
tokenLicense.identifier.tenantId !== localIdentifier.tenantId) {
throw new Error("Token was not issued for this installation")
} Type guard
function matchesInstallation(license: OfflineLicense, identifier: OfflineIdentifier): boolean {
return license.identifier.installId === identifier.installId &&
license.identifier.tenantId === identifier.tenantId
} Try / catch
try {
await activateOfflineLicenseToken(token)
} catch (e) {
if (e instanceof HTTPError && e.message.includes("does not match this installation")) {
// re-export identifier and request a token for THIS install
} else throw e
} Prevention
- Generate offline tokens per installation using that install's exported identifier.
- Never copy offline tokens between environments or cloned VMs.
- After restores/migrations, reissue tokens for the new installId.
- Keep tenant context consistent during activation.
When it happens
Trigger: verifyInstallation(license) called via verifyOfflineLicenseToken when the token was generated for a different installId or tenantId than the current environment — e.g. token exported from install A activated on install B.
Common situations: Copying an offline license token between dev/staging/prod environments; restoring a backup where the installId was regenerated; cloned VMs sharing one token; multi-tenant setups using a token issued for another tenant; re-issued install (installId changed) after a rebuild.
Related errors
- Offline license does not match this installation
- Features do not exist for planType=${planType}
- Offline license has expired. expireAt=${license.expireAt}
- Invalid offline license token
- Offline license has expired
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/f53643a73b3536d6.
Report an issue: GitHub.