hcengineering/platform · error
Invalid tier id: ${tierId}
Error message
Invalid tier id: ${tierId} What it means
getTypeAndPlan in Subscriptions.svelte parses a tier id expected in the form '<prefix>:<type>:<plan>' (three colon-separated parts). If tierId.split(':') does not yield exactly 3 parts it throws `Invalid tier id: ${tierId}`. The tier id came from data that doesn't match the expected naming convention.
Source
Thrown at plugins/billing-resources/src/components/Subscriptions.svelte:384
} else {
// Subscription already exists and matches this checkout, just clean up the URL
const cleanedLoc = { ...loc, query: {} }
navigate(cleanedLoc)
}
}
}
$: isCheckoutPolling = pollingCheckoutId !== null
function formatEndDate (endDate: number): string {
const date = new Date(endDate)
return date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
}
function getTypeAndPlan (tierId: Ref<Tier>): { type: SubscriptionType, plan: string } {
const parts = tierId.split(':')
if (parts.length !== 3) {
throw new Error(`Invalid tier id: ${tierId}`)
}
return {
type: parts[1] as SubscriptionType,
plan: parts[2].toLowerCase()
}
}
onMount(() => {
void (async () => {
// First, load current subscriptions
await fetchSubscriptions()
// Then fetch usage stats
await fetchUsageStats()
// Then check if we need to poll for a new subscription from checkout
checkForCheckoutParam()View on GitHub (pinned to 63e28dc964)
Solutions
- Inspect the offending tierId string and re-create the tier using the current '<prefix>:<type>:<plan>' naming convention.
- Add a defensive branch returning a sensible default (e.g. { type: 'subscription', plan: 'free' }) for ids that don't match.
- Migrate legacy tier ids in the database to the 3-part format.
- Align plugin versions so tier creation and tier parsing use the same id format.
Example fix
// before
const parts = tierId.split(':')
if (parts.length !== 3) {
throw new Error(`Invalid tier id: ${tierId}`)
}
// after
const parts = tierId.split(':')
if (parts.length !== 3) {
console.warn('Non-standard tier id, using defaults:', tierId)
return { type: 'subscription' as SubscriptionType, plan: 'free' }
} Defensive patterns
Strategy: type-guard
Validate before calling
function isCanonicalTierId(tierId: string): boolean {
return tierId.split(':').length === 3
}
// before rendering:
if (!isCanonicalTierId(subscription.tier)) renderFallbackTier(subscription) Type guard
function parseTierId(tierId: string): { type: SubscriptionType, plan: string } | null {
const parts = tierId.split(':')
return parts.length === 3
? { type: parts[1] as SubscriptionType, plan: parts[2].toLowerCase() }
: null
} Try / catch
try {
const { type, plan } = getTypeAndPlan(tierId)
renderSubscription(type, plan)
} catch (err) {
if (err instanceof Error && err.message.startsWith('Invalid tier id')) {
renderFallbackSubscription(tierId) // show raw id or 'unknown plan'
} else throw err
} Prevention
- Create tiers only through the billing plugin so ids follow the '<prefix>:<type>:<plan>' format.
- Migrate legacy tier ids after upgrading the plugin.
- Add a parsing helper returning null instead of throwing for display code.
When it happens
Trigger: Rendering a subscription whose tier Ref<Tier> is not of the canonical 'xxx:type:plan' form — e.g. a default/free tier, a manually created tier, or a tier id produced by a different/older version of the billing plugin.
Common situations: Environments where tiers were created before the tier-id naming convention was introduced, custom tiers made by admins, or test/staging data copied between environments with mismatched tier formats.
Related errors
- Unable to determine the required version of Rush from ${RUSH
- Invalid package specifier: ${rawPackageSpecifier}
- Failed to parse response for part ${partNumber}
- Invalid endpoint reference
- Invalid package specifier: ${rawPackageSpecifier}
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/bef3609cadd3aa31.
Report an issue: GitHub.