Skip to content

VueStripeEmbeddedCheckout ​

Embeds Stripe's prebuilt Checkout page in your site with stripe.createEmbeddedCheckoutPage(). Stripe renders the whole payment form (line items, totals, payment methods, address and tax fields) in an iframe on your page, driven by a Checkout Session. Choosing an integration compares it with the other Checkout options, and Embed Stripe Checkout shows the server and client code.

  • Use it inside VueStripeProvider. It does not need VueStripeElements.
  • Pass the client_secret of a Checkout Session created on your server with ui_mode: 'embedded_page' and a return_url.
  • Stripe allows one Embedded Checkout per page. A second VueStripeEmbeddedCheckout mounted at the same time reports an error instead of loading.

Usage ​

vue
<template>
  <VueStripeProvider :publishable-key="publishableKey">
    <VueStripeEmbeddedCheckout
      :fetch-client-secret="fetchClientSecret"
      @ready="onReady"
      @error="onError"
    />
  </VueStripeProvider>
</template>

<script setup lang="ts">
import { VueStripeProvider, VueStripeEmbeddedCheckout } from '@vue-stripe/vue-stripe'
import '@vue-stripe/vue-stripe/style.css' // once, e.g. in main.ts

const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY

// The session from the Embedded Checkout guide sets return_url, so Stripe
// redirects there after payment. For @complete, see "Redirect or @complete" below.
const fetchClientSecret = () =>
  fetch('/api/embedded-checkout-session', { method: 'POST' })
    .then((response) => response.json())
    .then((data: { clientSecret: string }) => data.clientSecret)

const onReady = () => console.log('Embedded Checkout is on the page')
const onError = (message: string) => console.error(message)
</script>

Props ​

PropTypeRequiredDescription
clientSecretstringOne of the twoClient secret of a Checkout Session created with ui_mode: 'embedded_page'
fetchClientSecret() => Promise<string>One of the twoFunction resolving to the client secret. The component calls it once on mount and passes the secret to Stripe
onLineItemsChange(event: StripeEmbeddedCheckoutLineItemsChangeEvent) => Promise<ResultAction>NoCalled when the customer adds, removes or changes a line item. Resolve { type: 'accept' } or { type: 'reject', errorMessage }. Passed to Stripe.js as is; Stripe does not call it on sessions the API creates today (see below)

The options are read once, when the page is created. To show another Checkout Session, re-mount the component, for example by changing its key:

vue
<VueStripeEmbeddedCheckout :key="clientSecret" :client-secret="clientSecret" />

onLineItemsChange ​

onLineItemsChange is a function prop, not an event, because Stripe waits for its result before it applies the change. The component passes it to stripe.createEmbeddedCheckoutPage() unchanged.

No session can enable it

Stripe.js 10.0.0 declares onLineItemsChange and says it is required when the session sets permissions.update.line_items: 'server_only'. The Checkout Sessions API has no such parameter: permissions[update][line_items] and permissions[update_line_items] are rejected with "Received unknown parameter" on every API version from 2025-03-31.basil to 2026-09-30.endive, and stripe-node 23 types permissions with update_shipping_details only. Stripe's Dynamically update line items guide says the full embedded page doesn't support dynamically updating line items.

To let customers change quantities on the embedded page, set adjustable_quantity on the session's line items; Stripe applies the change itself. To approve or change line items from your server, use the Checkout Form or your own checkout page, and call runServerUpdate() from useCheckoutForm or useCheckoutSession.

onShippingDetailsChange is not supported

Stripe deprecated dynamic shipping updates in Embedded Checkout (onShippingDetailsChange, deprecated since Stripe.js 9.14), so the component does not expose it.

Props Interface ​

ts
interface VueStripeEmbeddedCheckoutProps {
  clientSecret?: string
  fetchClientSecret?: () => Promise<string>
  onLineItemsChange?: (
    event: StripeEmbeddedCheckoutLineItemsChangeEvent
  ) => Promise<{ type: 'accept' } | { type: 'reject'; errorMessage?: string }>
}

Events ​

EventPayloadDescription
@readyStripeEmbeddedCheckoutEmitted after the page is created and mounted
@completenoneStripe's onComplete: the Checkout Session completed. Fires when Stripe does not redirect, see below. The component passes onComplete to Stripe only when you listen to @complete
@analyticsStripeEmbeddedCheckoutAnalyticsEventUnionStripe's onAnalyticsEvent: checkoutRendered, deviceData, promotionCodeApplied, lineItemChange, checkoutSubmitted, checkoutSubmitFailed. Passed to Stripe only when you listen to @analytics
@errorstringNo client secret was given, fetchClientSecret rejected or resolved no client secret, another Embedded Checkout is already mounted, createEmbeddedCheckoutPage failed (for example a session that is not ui_mode: 'embedded_page'), or, under the plugin without a VueStripeProvider, Stripe.js failed to load (the plugin logs that failure itself). @ready is not emitted then

Redirect or @complete

By default (redirect_on_completion: 'always') Stripe sends the customer to your return_url after payment. To stay on the page and react to @complete, create the session with redirect_on_completion: 'if_required' (redirect-based payment methods still use return_url) or 'never' (omit return_url). Listen to @complete only for such a session: with 'always', Stripe.js logs a warning that the onComplete handler will never be called. Stay on the page shows the session and the @complete handler.

Tracking analytics events ​

vue
<!-- Rendered inside VueStripeProvider -->
<script setup lang="ts">
import { VueStripeEmbeddedCheckout } from '@vue-stripe/vue-stripe'
import type { StripeEmbeddedCheckoutAnalyticsEventUnion } from '@vue-stripe/vue-stripe'

defineProps<{ clientSecret: string }>()

const onAnalytics = (event: StripeEmbeddedCheckoutAnalyticsEventUnion) => {
  if (event.eventType === 'checkoutSubmitted') {
    // Send it to your analytics tool
    console.log('checkout_submitted', event.details.amount)
  }
}
</script>

<template>
  <VueStripeEmbeddedCheckout :client-secret="clientSecret" @analytics="onAnalytics" />
</template>

Errors ​

ErrorCauseFix
VueStripeProviderError (thrown)Not inside VueStripeProviderWrap the component in VueStripeProvider
requires a clientSecret or fetchClientSecret propNeither prop givenPass one of them
The rejection's messagefetchClientSecret rejected (for example the server route failed)Fix the route, then re-mount the component
fetchClientSecret did not resolve to a client secretfetchClientSecret resolved an empty or non-string valueResolve the session's client_secret
Only one VueStripeEmbeddedCheckout can be mounted at a timeA second instance mounted while the first is on the pageUnmount the first one (v-if) before mounting another
Stripe error messageInvalid or expired client secret, session not ui_mode: 'embedded_page'Create a new session on your server

Slots ​

Loading Slot ​

Rendered while stripe.createEmbeddedCheckoutPage() is pending. Stripe then shows its own loader inside the iframe.

vue
<VueStripeEmbeddedCheckout :client-secret="clientSecret">
  <template #loading>
    <p>Preparing checkout...</p>
  </template>
</VueStripeEmbeddedCheckout>

Error Slot ​

Rendered when the page could not be created, including when fetchClientSecret rejects or resolves a session Stripe refuses:

vue
<VueStripeEmbeddedCheckout :client-secret="clientSecret">
  <template #error="{ error }">
    <p class="error">Checkout could not load: {{ error }}</p>
  </template>
</VueStripeEmbeddedCheckout>

Exposed Properties ​

PropertyTypeDescription
checkoutRef<StripeEmbeddedCheckout | null>The Embedded Checkout instance once created
loadingRef<boolean>true until the page is mounted
errorRef<string | null>Error message, if creating the page failed

The component calls checkout.destroy() when it unmounts, so you rarely need the instance yourself. Removing the component (for example with v-if) destroys the page, which takes the place of Stripe's checkout.unmount(); mount a new component to show Checkout again.

Styling ​

The component renders the same parts as the element components, so the shared vue-stripe-element-* styles apply:

html
<div class="vue-stripe-embedded-checkout vue-stripe-element">
  <!-- only when there is an error; an #error slot replaces the inner div -->
  <div class="vue-stripe-embedded-checkout-error vue-stripe-element-error">
    <div class="vue-stripe-error-message">The error message</div>
  </div>
  <div class="vue-stripe-embedded-checkout-mount vue-stripe-element-mount"><!-- Stripe iframe --></div>
  <!-- only while loading; a #loading slot replaces the inner div -->
  <div class="vue-stripe-embedded-checkout-loader vue-stripe-element-loader">
    <div class="vue-stripe-loading-message">Loading checkout...</div>
  </div>
</div>

The default styles for these classes ship in the package stylesheet. Import it once in your app:

ts
import '@vue-stripe/vue-stripe/style.css'

Style the Checkout page itself in the Stripe Dashboard branding settings or with the session's branding_settings.

TypeScript ​

The component's props, event payloads and slot props are typed on Vue 2.7 and Vue 3: vue-tsc checks a handler's parameter against the payload in Events, and a slot prop against Slots.

InstanceType<typeof VueStripeEmbeddedCheckout> also includes the exposed members. A template ref unwraps their refs, so checkoutRef.value?.checkout is a StripeEmbeddedCheckout | null.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VueStripeEmbeddedCheckout } from '@vue-stripe/vue-stripe'
import type {
  StripeEmbeddedCheckout,
  StripeEmbeddedCheckoutAnalyticsEventUnion
} from '@vue-stripe/vue-stripe'

const props = defineProps<{ clientSecret: string }>()
const checkoutRef = ref<InstanceType<typeof VueStripeEmbeddedCheckout> | null>(null)

const onReady = (checkout: StripeEmbeddedCheckout): void => {
  console.log('mounted', checkout)
}
const onAnalytics = (event: StripeEmbeddedCheckoutAnalyticsEventUnion): void => {
  console.log(event.eventType)
}
</script>

<template>
  <VueStripeEmbeddedCheckout
    ref="checkoutRef"
    :client-secret="props.clientSecret"
    @ready="onReady"
    @analytics="onAnalytics"
  />
  <p v-if="checkoutRef?.checkout">Checkout is mounted</p>
</template>

See Also ​