Skip to content
Create account
or
Sign in
The Stripe Docs logo
/
Ask AI
Create account
Sign in
Get started
Payments
Revenue
Platforms and marketplaces
Money management
Developer resources
Overview
About Stripe payments
Upgrade your integration
Payments analytics
Online payments
OverviewFind your use caseManaged Payments
Use Payment Links
Build a checkout page
    Overview
    Quickstarts
    Customize look and feel
    Collect additional information
    Collect taxes
    Dynamically update checkout
      Dynamically customize shipping options
      Dynamically update line items
    Manage your product catalog
    Subscriptions
    Manage payment methods
    Let customers pay in their local currency
    Add discounts, upsells, and optional items
    Set up future payments
    Save payment details during payment
    Manually approve payments on your server
    After the payment
    Elements with Checkout Sessions API beta changelog
    Migrate from legacy Checkout
    Migrate Checkout to use Prices
Build an advanced integration
Build an in-app integration
Payment methods
Add payment methods
Manage payment methods
Faster checkout with Link
Payment interfaces
Payment Links
Checkout
Web Elements
In-app Elements
Payment scenarios
Handle multiple currencies
Custom payment flows
Flexible acquiring
Orchestration
In-person payments
Terminal
Beyond payments
Incorporate your company
Crypto
Financial Connections
Climate
HomePaymentsBuild a checkout pageDynamically update checkout

Dynamically update line itemsPrivate preview

Update line items in response to changes made during checkout.

Private preview

This feature is in private preview. Request access to dynamic updates to checkout.

Learn how to dynamically add, remove, or update line items included in a Checkout Session.

Use cases

This guide demonstrates how to update line items to upsell a subscription, but you can also:

  • Check inventory: Run inventory checks and holds when customers attempt to change item quantities.
  • Add new products: Add a complimentary product if the order total exceeds a specific amount.
  • Update shipping rates: If the order total changes, update shipping rates by combining the method described in this guide with what’s out on Customize shipping options during checkout.
  • Update tax rates: If you’re not using Stripe Tax, you can dynamically update tax rates on line items based on the shipping address entered.

Set up SDK
Server-side

Use our official libraries to access the Stripe API from your application:

Command Line
Ruby
gem install stripe -v 15.1.0-beta.2

Update server SDK
Server-side

To use this beta, first update your SDK to use the checkout_server_update_beta=v1 beta version header.

Ruby
# Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys Stripe.api_key =
'sk_test_BQokikJOvBiI2HlWgH4olfQ2'
Stripe.api_version = '2025-03-31.basil; checkout_server_update_beta=v1;'

Configure Checkout Session update permissions
Server-side

Pass in permissions.update_line_items=server_only when you create the Checkout Session to enable updating line items from your server. Passing this option will also disable client-side updates to line items, such as updateLineItemQuantity, ensuring that all updates pass through your server.

Command Line
cURL
curl https://api.stripe.com/v1/checkout/sessions \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -H "Stripe-Version: 2025-03-31.basil; checkout_server_update_beta=v1;" \ -d ui_mode=custom \ -d "permissions[update_line_items]"=server_only \ -d "line_items[0][price]"=
{{PRICE_ID}}
\ -d "line_items[0][quantity]"=1 \ -d mode=subscription \ --data-urlencode return_url="https://example.com/return"

Dynamically update line items
Server-side

Create a new endpoint on your server to update the line items on the Checkout Session. You’ll call this from the frontend in a later step.

Security tip

It’s essential to understand that client-side code runs in an environment fully controlled by the user. A malicious user can bypass your client-side validation, intercept, and modify requests, or even craft entirely new requests to your server.

When designing the endpoint, we recommend the following:

  • Design endpoints for specific customer interactions instead of making them overly generic (for example, “add cross-sell item” rather than a general “update” action). Specific endpoints help keep the purpose clear and make validation logic easier to write and maintain.
  • Avoid passing Session data directly from the client to your endpoint. Malicious clients can modify request data, making it an unreliable source for determining the Checkout Session state. Rather, pass the session ID to your server and use it to securely retrieve the data from Stripe’s API.
Ruby
require 'sinatra' require 'json' require 'stripe' set :port, 4242 # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys Stripe.api_key =
'sk_test_BQokikJOvBiI2HlWgH4olfQ2'
Stripe.api_version = '2025-03-31.basil; checkout_server_update_beta=v1;' MONTHLY_PRICE_ID = '{{MONTHLY_PRICE}}' YEARLY_PRICE_ID = '{{YEARLY_PRICE}}' post '/change-subscription-interval' do content_type :json request.body.rewind request_data = JSON.parse(request.body.read) checkout_session_id = request_data['checkout_session_id'] interval = request_data['interval'] if checkout_session_id.nil? || !['yearly', 'monthly'].include?(interval) status 400 return { type: 'error', message: 'We could not process your request. Please try again later.' }.to_json end begin # 1. Create the new line items for the session new_price = interval == 'yearly' ? YEARLY_PRICE_ID : MONTHLY_PRICE_ID line_items = [{ price: new_price, quantity: 1, }] # 2. Update the Checkout Session with the new line items. Stripe::Checkout::Session.update(checkout_session_id, { line_items: line_items, }) # 3. Return success response. { type: 'success' }.to_json rescue Stripe::StripeError # Handle Stripe errors with a generic error message status 400 { type: 'error', message: 'We couldn't process your request. Please try again later.' }.to_json rescue StandardError # Handle unexpected errors status 500 { type: 'error', message: 'Something went wrong on our end. Please try again later.' }.to_json end end

When updating line items, you must retransmit the entire array of line items.

  • To keep an existing line item, specify its id.
  • To update an existing line item, specify its id along with the new values of the fields to update.
  • To add a new line item, specify a price and quantity without an id.
  • To remove an existing line item, omit the line item’s ID from the retransmitted array.
  • To reorder a line item, specify its id at the desired position in the retransmitted array.

Update client SDK
Client-side

Initialize stripe.js with the custom_checkout_server_updates_1 beta header.

checkout.js
const stripe = Stripe(
'pk_test_TYooMQauvdEDq54NiTphI7jx'
, { betas: ['custom_checkout_server_updates_1'], });

Invoke server updates
Client-side

Trigger the update from your front end by making a request to your server, and wrapping it in runServerUpdate. If the request is successful, the Session object updates with the new line items.

index.html
<button id="change-subscription-interval" role="switch" aria-checked="false"> Save with a yearly subscription </button>
checkout.js
document.getElementById('change-subscription-interval') .addEventListener("click", async (event) => { const button = event.target; const isCurrentSubscriptionMonthly = button.getAttribute("aria-checked") === "false"; const updateCheckout = () => { return fetch("/change-subscription-interval", { method: "POST", headers: { "Content-type": "application/json", }, body: JSON.stringify({ checkout_session_id: checkout.session().id, interval: isCurrentSubscriptionMonthly ? "yearly" : "monthly", }) }); }; const response = await checkout.runServerUpdate(updateCheckout); if (!response.ok) { // Handle error state return; } // Update toggle state on success const isNewSubscriptionMonthly = !isCurrentSubscriptionMonthly; button.setAttribute("aria-checked", !isNewSubscriptionMonthly); button.textContent = isNewSubscriptionMonthly ? "Save with a yearly subscription" : "Use monthly subscription"; });
Was this page helpful?
YesNo
Need help? Contact Support.
Join our early access program.
Check out our changelog.
Questions? Contact Sales.
LLM? Read llms.txt.
Powered by Markdoc