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 needVueStripeElements. - Pass the
client_secretof a Checkout Session created on your server withui_mode: 'embedded_page'and areturn_url. - Stripe allows one Embedded Checkout per page. A second
VueStripeEmbeddedCheckoutmounted at the same time reports an error instead of loading.
Usage
<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
| Prop | Type | Required | Description |
|---|---|---|---|
clientSecret | string | One of the two | Client secret of a Checkout Session created with ui_mode: 'embedded_page' |
fetchClientSecret | () => Promise<string> | One of the two | Function resolving to the client secret. The component calls it once on mount and passes the secret to Stripe |
onLineItemsChange | (event: StripeEmbeddedCheckoutLineItemsChangeEvent) => Promise<ResultAction> | No | Called 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:
<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
interface VueStripeEmbeddedCheckoutProps {
clientSecret?: string
fetchClientSecret?: () => Promise<string>
onLineItemsChange?: (
event: StripeEmbeddedCheckoutLineItemsChangeEvent
) => Promise<{ type: 'accept' } | { type: 'reject'; errorMessage?: string }>
}Events
| Event | Payload | Description |
|---|---|---|
@ready | StripeEmbeddedCheckout | Emitted after the page is created and mounted |
@complete | none | Stripe'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 |
@analytics | StripeEmbeddedCheckoutAnalyticsEventUnion | Stripe's onAnalyticsEvent: checkoutRendered, deviceData, promotionCodeApplied, lineItemChange, checkoutSubmitted, checkoutSubmitFailed. Passed to Stripe only when you listen to @analytics |
@error | string | No 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
<!-- 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
| Error | Cause | Fix |
|---|---|---|
VueStripeProviderError (thrown) | Not inside VueStripeProvider | Wrap the component in VueStripeProvider |
requires a clientSecret or fetchClientSecret prop | Neither prop given | Pass one of them |
| The rejection's message | fetchClientSecret rejected (for example the server route failed) | Fix the route, then re-mount the component |
fetchClientSecret did not resolve to a client secret | fetchClientSecret resolved an empty or non-string value | Resolve the session's client_secret |
Only one VueStripeEmbeddedCheckout can be mounted at a time | A second instance mounted while the first is on the page | Unmount the first one (v-if) before mounting another |
| Stripe error message | Invalid 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.
<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:
<VueStripeEmbeddedCheckout :client-secret="clientSecret">
<template #error="{ error }">
<p class="error">Checkout could not load: {{ error }}</p>
</template>
</VueStripeEmbeddedCheckout>Exposed Properties
| Property | Type | Description |
|---|---|---|
checkout | Ref<StripeEmbeddedCheckout | null> | The Embedded Checkout instance once created |
loading | Ref<boolean> | true until the page is mounted |
error | Ref<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:
<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:
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.
<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
- Embed Stripe Checkout - How-to guide with the server code
- Embedded Checkout Live Demo - Pay with a test card
- Fulfil orders with webhooks - Fulfil when the session completes
- VueStripeCheckout - Redirect to hosted Checkout instead
- VueStripeCheckoutElementsProvider - Your own layout on a Checkout Session
- Stripe Embedded Checkout docs
