Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silly-rooms-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/backend': minor
---

[Billing Beta] Update subscription item `plan` and `planId` properties to be `null`.
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ export class BillingSubscriptionItem {
/**
* The plan associated with this subscription item.
*/
readonly plan: BillingPlan,
readonly plan: BillingPlan | null,
/**
* The plan ID.
*/
readonly planId: string,
readonly planId: string | null,
/**
* Unix timestamp (milliseconds) of when the subscription item was created.
*/
Expand Down Expand Up @@ -112,8 +112,8 @@ export class BillingSubscriptionItem {
data.period_start,
data.next_payment,
formatAmountJSON(data.amount),
BillingPlan.fromJSON(data.plan),
data.plan_id,
data.plan ? BillingPlan.fromJSON(data.plan) : null,
data.plan_id ?? null,
data.created_at,
data.updated_at,
data.period_end,
Expand Down
10 changes: 5 additions & 5 deletions packages/backend/src/api/resources/JSON.ts
Original file line number Diff line number Diff line change
Expand Up @@ -880,8 +880,8 @@ export interface BillingSubscriptionItemJSON extends ClerkResourceJSON {
date: number;
} | null;
amount: BillingMoneyAmountJSON | null;
plan: BillingPlanJSON;
plan_id: string;
plan?: BillingPlanJSON | null;
plan_id?: string | null;
}
Comment on lines +883 to 885
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Correct optional+nullable modeling for “omitted or null”; add brief field docs.

Types now reflect the API contract. Please document the semantics so SDK users know these may be absent or explicitly null.

   amount: BillingMoneyAmountJSON | null;
-  plan?: BillingPlanJSON | null;
-  plan_id?: string | null;
+  /**
+   * May be omitted or null when the instance is configured to allow planless items.
+   */
+  plan?: BillingPlanJSON | null;
+  /**
+   * May be omitted or null. Can be null even when `plan` is omitted.
+   */
+  plan_id?: string | null;

To catch lingering unsafe accesses, run:


🏁 Script executed:

#!/bin/bash
# Find places that still assume `plan` is always present.
rg -nP --type=ts -C2 '\b(plan|plan_id)\b(?!\s*\?:)' packages | sed -n '1,120p'
rg -nP --type=ts -C2 '\bplan\.(id|name|slug)\b' packages | sed -n '1,120p'

Length of output: 12199


Guard direct accesses to plan / plan_id (now optional|null)

JSDoc additions are fine; there are many direct property accesses that will throw when plan is omitted/null — fix these call sites:

  • packages/shared/src/react/commerce.tsx:258 — checkout.plan.name
  • packages/clerk-js/src/ui/contexts/components/Plans.tsx:145,153 — subscription.plan.id (find/filter)
  • packages/clerk-js/src/ui/components/Subscriptions/SubscriptionsList.tsx:182 — subscription.plan.name
  • packages/clerk-js/src/ui/components/SubscriptionDetails/index.tsx:263,280,283,293,301,421,433,510,526 — multiple selectedSubscription/subscription.plan.name/id/avatarUrl usages
  • packages/clerk-js/src/ui/components/Statements/StatementPage.tsx:108,119 — item.subscriptionItem.plan.name
  • packages/clerk-js/src/ui/components/SubscriptionDetails/tests/SubscriptionDetails.test.tsx:925 — test expects subscription.plan.id (update mocks)

Replace direct member access with optional chaining or explicit null checks (e.g., subscription.plan?.name or guard early), update affected tests/mocks, then re-run the grep to confirm no remaining unsafe accesses.

🤖 Prompt for AI Agents
packages/backend/src/api/resources/JSON.ts around lines 883-885: the properties
plan and plan_id are now optional|null, so any direct member access will throw
when absent; update all listed call sites to guard against null by using
optional chaining or explicit null checks (e.g., subscription.plan?.name or if
(!subscription.plan) handle early), update tests/mocks that expect plan.id
(e.g., SubscriptionDetails.test.tsx) to include nullable cases or adjust
assertions, and re-run a grep across the repo for "plan." and "plan_id" to
ensure no remaining unsafe direct accesses remain.


/**
Expand All @@ -906,7 +906,7 @@ export interface BillingSubscriptionItemWebhookEventJSON extends ClerkResourceJS
next_payment_amount: number;
next_payment_date: number;
amount: BillingMoneyAmountJSON;
plan: {
plan?: {
id: string;
instance_id: string;
product_id: string;
Expand All @@ -922,8 +922,8 @@ export interface BillingSubscriptionItemWebhookEventJSON extends ClerkResourceJS
currency: string;
annual_monthly_amount: number;
publicly_visible: boolean;
};
plan_id: string;
} | null;
plan_id?: string | null;
}

/**
Expand Down
Loading