RyzeDeskRyzeDesk

Stripe

4105 articles

Dynamically update line items


Dynamically update line items

Learn how to modify pricing and the contents of a basket during 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 Customise 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.

Payment Intents API

If you use the Payment Intents API, you must manually track line item updates and modify the payment amount or by creating a new PaymentIntent with adjusted amounts.

Set up the SDK Server-side

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

Command Line

Select a language

Ruby

Python

PHP

Node.js

.NET

Go

Java

No results

gem install stripe -v 15.1.0

Update the server SDK Server-side

To use this feature, ensure your SDK version is 2025-03-31.basil or later.

Select a language

Ruby

Python

PHP

Node

.NET

Go

Java

No results

# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
# Find your keys at https://dashboard.stripe.com/apikeys.
client = Stripe::StripeClient.new(
 'sk_test_Ou1w6LVt3zmVipDVJsvMeQsc',
 stripe_version: '2025-03-31.basil',
)

Create a Checkout Session Server-side

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

Dynamically update line items Server-side

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

Security tip

Client-side code runs in an environment that’s controlled by the user. A malicious user can bypass your client-side validation, intercept and modify requests or create new requests to your server.

When creating an endpoint, we recommend the following:

  • Create endpoints for specific customer interactions instead of making them generic. For example, “add cross-sell items” instead of a general “update” action. Specific endpoints can help with writing and maintaining validation logic.
  • Don’t pass 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. Instead, pass the session ID to your server and use it to securely retrieve the data from the Stripe API.

Select a language

Ruby

Python

PHP

Node

.NET

Go

Java

No results

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 the client SDK Client-side

Initialise Stripe.js.

checkout.js

const stripe = Stripe('pk_test_GvF3BSyx8RSXMK5yAFhqEd3H');

Request server updates Client-side

From your front end, create a function to send an update request to your server and wrap it in runServerUpdate. A successful request updates the Session object with the new line items.

runServerUpdate enforces a 20-second timeout for your update function. If your function doesn’t resolve within 20 seconds, runServerUpdate returns an error. Wrap runServerUpdate calls in try / catch blocks to handle any errors, and record metrics to diagnose timeouts and other failures.

Wrap runServerUpdate calls in try / catch blocks to handle errors from your server and from runServerUpdate itself (for example, timeouts). response.type === 'error' covers failures in Stripe’s internal retrieval of the updated session. Errors returned by your own server (such as 4xx or 5xx responses) aren’t reflected in response because the Fetch API resolves for any completed HTTP response regardless of status code. Check response.ok inside your update function and throw on failure so that the catch block is reached.

index.html

<button id="update-line-items" role="switch" aria-checked="false">
 Save with a yearly subscription
</button>

checkout.js

document.getElementById('update-line-items')
 .addEventListener("click", async (event) => {
 const button = event.target;
 const isCurrentSubscriptionMonthly =
 button.getAttribute("aria-checked") === "false";

 const updateCheckout = async () => {
 const response = await fetch("/update-line-items", {
 method: "POST",
 headers: {
 "Content-type": "application/json",
 },
 body: JSON.stringify({
 checkout_session_id: actions.getSession().id,
 interval: isCurrentSubscriptionMonthly ? "yearly" : "monthly",
 })
 });
 if (!response.ok) {
 const body = await response.json();
 throw new Error(body.message);
 }
 };

 try {
 const response = await checkout.runServerUpdate(updateCheckout);
 if (response.type === 'error') {
 // Handle Stripe API errors (for example, session retrieval failure)
 return;
 }
 } catch (error) {
 // Handle promise rejection from your server (4xx/5xx errors) or
 // from runServerUpdate itself (for example, timeouts).
 // error.message contains the message thrown from your update function.
 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";
 });
Last verified 2026-09-27

Is this helpful?