Home › Guides › Stripe current_period_end is undefined
Stripe subscription current_period_end is undefined: what changed in 2025-03-31.basil
Matching pack: Subscription Billing Kit (for Stripe) (US$19), an Agent Skill for Claude Code and Cursor with a webhook-driven access model for SaaS subscriptions and version-tolerant readers for these fields. Read its test report (12 unit tests plus 3 request-schema checks against stripe-mock, not real Stripe test mode). Free: Stripe basil field checker, which runs in your browser and lists every moved field in a pasted event.
You upgraded the Stripe SDK or your account's API version, and now subscription.current_period_end is undefined, or TypeScript says the property doesn't exist on Subscription. Renewal dates and "access until" checks quietly break. The field wasn't lost: Stripe moved it in API version 2025-03-31.basil.
The short answer
current_period_startandcurrent_period_endwere removed from the Subscription object. Read them from each subscription item instead:subscription.items.data[i].current_period_end.- On invoices,
invoice.subscriptionwas removed too. Useinvoice.parent.subscription_details.subscription, after checking thatinvoice.parent.typeissubscription_details. - If your code may see both shapes (for example webhook events on an older version and API calls on a newer one), read the new location first and fall back to the old one.
Sources: Stripe changelog, 2025-03-31.basil, "Adds subscription item-level billing periods and removes subscription-level periods" (docs.stripe.com/changelog/basil/2025-03-31/deprecate-subscription-current-period-start-and-end); Stripe changelog, 2025-03-31.basil, "Invoicing resources now specify how they were generated" (docs.stripe.com/changelog/basil/2025-03-31/adds-new-parent-field-to-invoicing-objects). Checked 24 Oct 2026.
Why it shows up after an SDK upgrade
Stripe's typed SDKs pin an API version. Stripe's changelog lists the first SDK majors for basil, for example stripe-node v18.0.0, stripe-python v12.0.0, stripe-php v17.0.0, stripe-java v29.0.0, stripe-go v82.0.0 and stripe-dotnet v48.0.0. Upgrading to one of those (or later) means your API calls return the new shape, even if nothing else in your code changed.
Webhooks can still send the old shape
Webhook payloads follow the API version of the webhook endpoint, not your SDK. So after an SDK upgrade, stripe.subscriptions.retrieve() can return the new shape while customer.subscription.updated events still arrive in the old one, until you upgrade the endpoint's version too. Check which API version the endpoint uses in the Dashboard. Stripe's upgrade steps list upgrading webhook endpoints as its own step.
Code that reads both shapes
These are the same helpers our Subscription Billing Kit uses. They read the new location first and fall back to the old one:
// Reads both API shapes: before and after 2025-03-31.basil.
import type Stripe from "stripe";
/** Latest billing-period end (Unix seconds) across the subscription's items. */
export function periodEnd(sub: Stripe.Subscription): number | null {
const itemEnds = (sub.items?.data ?? [])
.map((i: any) => i.current_period_end) // basil and later
.filter((n: unknown): n is number => typeof n === "number");
if (itemEnds.length) return Math.max(...itemEnds);
const legacy = (sub as any).current_period_end; // before basil
return typeof legacy === "number" ? legacy : null;
}
/** The subscription id an invoice belongs to, or null for a one-off invoice. */
export function invoiceSubscriptionId(inv: Stripe.Invoice): string | null {
const v = (inv as any).parent?.subscription_details?.subscription // basil and later
?? (inv as any).subscription // before basil
?? null;
return typeof v === "string" ? v : v?.id ?? null;
}
Items on one subscription can have different billing periods (for example a monthly and a yearly price). periodEnd above takes the latest end, which suits an "access until" date. If you bill per item, read the period from the item you care about instead.
Other basil changes worth checking at the same time
- On invoices,
quote,subscription_detailsandsubscription_proration_datealso moved underparent. - On invoice line items,
subscription,subscription_item,invoice_item,proration,proration_detailsandtypewere removed; for example useline_item.parent.subscription_item_details.subscription_item. previous_attributesoncustomer.subscription.updatednow reports billing-period changes on the items. If you watch it for price or quantity changes, look atprevious_attributes.items.data[].price.- Searching your code for
current_period_end,.subscriptionon invoices andsubscription_detailsfinds most of the places to fix.
Sources: Stripe changelog, 2025-03-31.basil, "Adds subscription item-level billing periods and removes subscription-level periods" (docs.stripe.com/changelog/basil/2025-03-31/deprecate-subscription-current-period-start-and-end); Stripe changelog, 2025-03-31.basil, "Invoicing resources now specify how they were generated" (docs.stripe.com/changelog/basil/2025-03-31/adds-new-parent-field-to-invoicing-objects). Checked 24 Oct 2026.
Let your agent build it the safe way
Subscription Billing Kit (for Stripe) (US$19) is an Agent Skill for Claude Code and Cursor. It gives your agent a webhook-driven access model for SaaS: Checkout in subscription mode, the Customer Portal, an access policy for all 8 subscription statuses, protection against out-of-order events, trials, cancel-at-period-end, failed-payment hooks, and helpers for Stripe's 2025 "Basil" API changes. Its tests are 12 unit tests with basil-shaped fixtures plus 3 request-schema checks against stripe-mock; none run against real Stripe test mode. It assumes webhook delivery is already safe: Webhooks Done Right (for Stripe) (US$19) covers raw body, signatures and duplicate events.
See Subscription Billing Kit (for Stripe) · Read its test report