Budibase/budibase · error · HTTPError
Offline license has expired
Error message
Offline license has expired
What it means
After the token signature is verified, verifyOfflineLicenseToken calls verifyExpiry and converts any expiry failure (past expireAt or unparseable date) into HTTPError('Offline license has expired', 400). This is the user-facing expiry error for offline license activation and refresh (it also surfaces when getOfflineLicense refreshes the cached license).
Source
Thrown at packages/pro/src/sdk/licensing/licenses/offline/offline.ts:115
license.quotas = merge(license.quotas, _quotas)
return license
}
export async function verifyOfflineLicenseToken(
token: string
): Promise<OfflineLicense> {
let license: OfflineLicense
try {
license = await signing.verifyLicenseToken(token)
} catch {
throw new HTTPError("Invalid offline license token", 400)
}
try {
verifyExpiry(license)
} catch {
throw new HTTPError("Offline license has expired", 400)
}
try {
await verifyInstallation(license)
} catch (e) {
if (e instanceof OfflineLicenseMismatchError) {
throw new HTTPError(
"Offline license does not match this installation",
400
)
}
throw e
}
return license
}
export async function getOfflineLicense(): Promise<License | undefined> {View on GitHub (pinned to a81a902e9a)
Solutions
- Renew the subscription and activate a fresh offline license token from the account portal.
- Check the server clock (NTP) — correct time if drift caused the false expiry.
- Replace the stored token: activateOfflineLicenseToken(newToken), which saves and refreshes the cache.
- If the license should still be valid, decode the token and compare expireAt with the portal's records; re-export if they disagree.
Example fix
// before
await activateOfflineLicenseToken(savedOldToken)
// after
try {
await activateOfflineLicenseToken(savedOldToken)
} catch (e) {
if (e instanceof HTTPError && e.message === "Offline license has expired") {
const fresh = await promptUserForNewOfflineToken()
await activateOfflineLicenseToken(fresh)
} else {
throw e
}
} Defensive patterns
Strategy: try-catch
Validate before calling
function isExpired(token: string): boolean {
const license = /* decode token payload */ as { expireAt: string }
const expireAt = new Date(license.expireAt).getTime()
return !Number.isFinite(expireAt) || Date.now() > expireAt
}
if (isExpired(token)) throw new Error("Renew offline license before activation") Type guard
function isExpiryError(e: unknown): boolean {
return e instanceof HTTPError && e.message === "Offline license has expired"
} Try / catch
try {
await activateOfflineLicenseToken(token)
} catch (e) {
if (isExpiryError(e)) {
// begin renewal flow: obtain fresh token from account portal
} else throw e
} Prevention
- Automate reminders tied to the license expireAt date.
- Renew and re-export tokens before air-gapped maintenance windows.
- Verify server time before diagnosing expiry errors.
- Replace saved tokens promptly after renewal.
When it happens
Trigger: verifyOfflineLicenseToken(token) where the decoded license's expireAt is before the current time (or invalid), e.g. activating a stale offline token or the periodic cache refresh re-verifying an aged license.
Common situations: Annual offline license not renewed before the expiry date; air-gapped environments that can't auto-renew; server clock drift making a valid license appear expired; backups restored with an old saved offlineLicenseToken.
Related errors
- Offline license has expired. expireAt=${license.expireAt}
- Features do not exist for planType=${planType}
- Invalid offline license
- Invalid offline license token
- Offline license does not match this installation
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/da11f567e796031c.
Report an issue: GitHub.