--- title: Add a survey to Thank you and Order status pages description: Learn how to build a post-checkout survey that appears on Thank you and Order status pages using checkout UI extensions. source_url: html: https://shopify.dev/docs/apps/build/checkout/thank-you-order-status/add-survey?extension=polaris md: https://shopify.dev/docs/apps/build/checkout/thank-you-order-status/add-survey.md?extension=polaris --- # Add a survey to Thank you and Order status pages The **Thank you** and **Order status** pages are shown to customers at the end of checkout, and can be customized using checkout UI extensions. In this tutorial, you'll create a single extension that displays two different surveys depending where an app user places the extension in the checkout editor. You'll use both the `purchase.thank-you.block.render` and `customer-account.order-status.block.render` extension targets to build two surveys: * A survey that asks the customer how they heard about the store. * A survey that asks how the customer is enjoying their purchase after the order has been delivered. Follow along with this tutorial to build a sample app, or clone the completed sample app. You can use this tutorial as an example to build other customizations, such as adding a form that customers can submit to subscribe to a newsletter. ![A GIF that shows a post-checkout customer survey extension rendering and responding to input.](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/apps/checkout/thank-you-order-status/survey-overview-BxIpaSKV.gif) ## What you'll learn In this tutorial, you'll learn how to do the following: * Create a checkout UI extension that renders on the **Thank you** and **Order status** pages. * Use multiple extension targets to display different interfaces to the customer. * Set up the UI components for the post-checkout survey. * Run the extension locally and test it on a development store. * Deploy your extension code to Shopify. ## Requirements * You're a [user with app development permissions](https://shopify.dev/docs/apps/build/dev-dashboard/user-permissions). * You've created a new [development store](https://shopify.dev/docs/api/development-stores) with the following: * [Generated test data](https://shopify.dev/docs/api/development-stores/generated-test-data) * [Checkout and Customer Accounts Extensibility](https://shopify.dev/docs/api/developer-previews#checkout-and-customer-accounts-extensibility-developer-preview) feature preview enabled * You've [created an app that uses Shopify CLI 3.85.1 or higher](https://shopify.dev/docs/apps/build/scaffold-app). ## Project Polaris [View on GitHub](https://github.com/Shopify/example-checkout--order-status--preact) ### Create a checkout UI extension To create a checkout UI extension, you'll use Shopify CLI, which generates starter code for building your extension. To create a checkout UI extension, you can use Shopify CLI, which generates starter code for building your extension and automates common development tasks. 1. Navigate to your app directory: ## Terminal ```terminal cd ``` 2. Run the following command to create a new checkout UI extension: ## Terminal ```terminal shopify app generate extension --template checkout_ui --name my-checkout-ui-extension ``` 1) Select `Checkout UI`. You should now have a new extension directory in your app's directory. The extension directory includes the extension script at `src/Checkout.jsx`. The following is an example directory structure: ## Checkout UI extension file structure ```text └── my-app └── extensions └── my-checkout-ui-extension ├── src │ └── Checkout.jsx OR Checkout.js // The index page of the checkout UI extension ├── locales │ ├── en.default.json // The default locale for the checkout UI extension │ └── fr.json // The locale file for non-regional French translations ├── shopify.extension.toml // The config file for the checkout UI extension └── package.json ``` 1. Start your development server to build and preview your app: ## Terminal ```terminal shopify app dev ``` To learn about the processes that are executed when you run `dev`, refer to the [Shopify CLI command reference](https://shopify.dev/docs/api/shopify-cli/app/app-dev). 2. Press `p` to open the developer console. In the developer console page, click on the preview link for your extension. ### Set up a target for your extension Set up a target for your checkout UI extension. [Targets](https://shopify.dev/docs/api/checkout-extensions/checkout#extension-targets) control where your extension renders in the checkout flow. #### Reference the extension targets in your configuration file You can define more than one target so that app users can add the extension to multiple locations in the checkout. In your checkout UI extension's configuration file, for each of your targets, create an `[[extensions.targeting]]` section with the following information: * `module`: The path to the file that contains the extension code. * `target`: An identifier that specifies where you're injecting code into Shopify. *** [`shopify.extension.toml`](https://shopify.dev/docs/apps/build/app-extensions/configure-app-extensions) is the configuration file for your extension. It contains basic information and settings. Note Whenever you edit your extension configuration file, you need to restart your server for the changes to take effect. ## Terminal ```bash shopify app dev ``` ## /extensions/typ-osp-survey/shopify.extension.toml ```toml api_version = "2025-10" [[extensions]] name = "typ-osp-survey" handle = "typ-osp-survey" type = "ui_extension" uid = "4e3e11dc-1fe2-8e29-485c-901abf804ea06596c846" [[extensions.targeting]] module = "./src/ThankYouPageSurvey.jsx" target = "purchase.thank-you.block.render" [[extensions.targeting]] module = "./src/OrderStatusPageSurvey.jsx" target = "customer-account.order-status.block.render" ``` ### Build the survey UI Build a user interface using components from the [Polaris web](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components) component library. #### Create a survey component Create a function to set up Polaris web components that the two surveys share, including a button to submit feedback and a message that appears after the feedback is submitted. *** Checkout UI extensions are limited to specific UI components exposed by the platform [for security reasons](https://shopify.dev/docs/api/checkout-ui-extensions#security). Polaris web components allow you to create a UI that feels seamless within the checkout experience, and that inherits a merchant's brand settings. [Box](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components/structure/box)[Stack](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components/structure/stack)[Heading](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components/titles-and-text/heading)[Text](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components/titles-and-text/text)[Button](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components/actions/button) ## /extensions/typ-osp-survey/src/shared.jsx ```jsx import { useCallback, useEffect, useState } from "preact/hooks"; /** * Returns a piece of state that is persisted in local storage, and a function to update it. * The state returned contains a `data` property with the value, and a `loading` property that is true while the value is being fetched from storage. * @param {string} key * @returns {[{data: any, loading: boolean}, (value: any) => void]} */ export function useStorageState(key) { const { storage } = shopify; const [data, setData] = useState(); const [loading, setLoading] = useState(true); useEffect(() => { async function queryStorage() { const value = await storage.read(key); setData(value); setLoading(false); } queryStorage(); }, [setData, setLoading, storage, key]); const setStorage = useCallback( (value) => { storage.write(key, value); }, [storage, key], ); return [{ data, loading }, setStorage]; } export function Survey({ title, description, onSubmit, children, loading }) { const [submitted, setSubmitted] = useState(false); async function handleSubmit() { await onSubmit(); setSubmitted(true); } if (submitted) { return ( Thanks for your feedback! Your response has been submitted ); } return ( {title} {description} {children} Submit feedback ); } ``` #### Create an attribution survey Use the `Survey` component that you created, along with Polaris web components, to build an attribution survey interface. This UI includes a `ChoiceList` to specify how the customer heard about the store. *** This sample code simulates storing the survey response by logging to your console. In a production-ready application, you should store the survey response in a database, or in [metafields](https://shopify.dev/docs/apps/custom-data/). This sample code also uses hard-coded survey questions and options. In a production-ready application, you should allow app users to customize the survey questions and answers with [checkout UI extension settings](https://shopify.dev/docs/api/checkout-ui-extensions/latest/configuration#settings-definition). Learn more about the [UX guidelines](https://shopify.dev/docs/apps/checkout/thank-you-order-status/ux-guidelines) for these pages. [Choice​List](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components/forms/choicelist)[Choice](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components/forms/choice) ## /extensions/typ-osp-survey/src/ThankyouPageSurvey.jsx ```jsx import { render } from "preact"; import { useState } from "preact/hooks"; import { Survey, useStorageState } from "./shared.jsx"; export default function () { render(, document.body); } function Attribution() { const [attribution, setAttribution] = useState(""); const [loading, setLoading] = useState(false); // Store into local storage if the attribution survey was completed by the customer. const [attributionSubmitted, setAttributionSubmitted] = useStorageState( "attribution-submitted", ); async function handleSubmit() { // Simulate a server request setLoading(true); return new Promise((resolve) => { setTimeout(() => { // Send the review to the server console.log("Submitted:", attribution); setLoading(false); setAttributionSubmitted(true); resolve(); }, 750); }); } // Hides the survey if the attribution has already been submitted if (attributionSubmitted.loading || attributionSubmitted.data === true) { return null; } return ( setAttribution(event.currentTarget.values[0])} > TV Podcast From a friend or family member Tiktok ); } ``` #### Create a product review survey Use the `Survey` component that you created, along with Polaris web components, to build a product review survey interface. This UI includes a `ChoiceList` to specify how the customer is enjoying their purchase. *** [Choice​List](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components/forms/choicelist)[Choice](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components/forms/choice) ## /extensions/typ-osp-survey/src/OrderStatusPageSurvey.jsx ```jsx import { render } from "preact"; import { useState } from "preact/hooks"; import { Survey, useStorageState } from "./shared.jsx"; export default function () { render(, document.body); } function ProductReview() { const [productReview, setProductReview] = useState(""); const [loading, setLoading] = useState(false); // Store into local storage if the product was reviewed by the customer. const [productReviewed, setProductReviewed] = useStorageState("product-reviewed"); async function handleSubmit() { // Simulate a server request setLoading(true); return new Promise((resolve) => { setTimeout(() => { // Send the review to the server console.log("Submitted:", productReview); setLoading(false); setProductReviewed(true); resolve(); }, 750); }); } // Hides the survey if the product has already been reviewed if (productReviewed.loading || productReviewed.data) { return null; } return ( { setProductReview(event.currentTarget.values[0]); }} > Amazing! Very happy with it. It's okay, I expected more. Eh. There are better options out there. I regret the purchase. ); } ``` ### Preview the extension Preview your extension to make sure that it works as expected. #### Start your server Run the Shopify CLI `dev` command to build your app and preview it on your development store. Make sure that you select a development store that has enabled the feature preview for [Checkout and Customer Accounts Extensibility](https://shopify.dev/docs/api/developer-previews#checkout-and-customer-accounts-extensibility-developer-preview). 1. In a terminal, navigate to your app directory. 2. Either start or restart your server to build and preview your app: ## Terminal ```bash shopify app dev ``` 3. Press `p` to open the developer console. 4. In the developer console page, click on the preview link for the custom Order status page extension. The checkout opens. *** This section describes how to solve some potential errors when you run `dev` for an app that contains a checkout UI extension. ### Property token error If you receive the error `ShopifyCLI:AdminAPI requires the property token to be set`, then you'll need to use the [`--checkout-cart-url` flag](https://shopify.dev/docs/api/shopify-cli/app/app-dev#flags) to direct Shopify CLI to open a checkout session for you. ## Terminal ```terminal shopify app dev --checkout-cart-url cart/{product_variant_id}:{quantity} ``` ### Missing checkout link If you don't receive the test checkout URL when you run `dev`, then verify the following: * You have a development store populated with products. * You're logged in to the correct Partners organization and development store. To verify, check your app info using the following command: ## Terminal ```terminal shopify app info ``` Otherwise, you can manually create a checkout with the following steps: 1. From your development store's storefront, add some products to your cart. 2. From the cart, click **Checkout**. 3. From directory of the app that contains your extension, run `dev` to preview your app: ## Terminal ```terminal shopify app dev ``` 4. On the checkout page for your store, change the URL by appending the `?dev=https://{tunnel_url}/extensions` query string and reload the page. The `tunnel_url` parameter allows your app to be accessed using a unique HTTPS URL. You should now see a rendered order note that corresponds to the code in your project template. #### Test the attribution survey 1. Complete the checkout by [placing a test order](https://help.shopify.com/manual/checkout-settings/test-orders). When you visit the **Thank you** page, the attribution survey displays. 1. Refresh the page to access the product review survey on the **Order status** page. 2. Optional: To preview the dynamic extension in a different supported location, add the [placement-reference URL](https://shopify.dev/docs/apps/build/checkout/test-checkout-ui-extensions#dynamic-extension-points) query parameter. *** To remain on the **Thank you** page through a page refresh, append the `?prevent_order_redirect=true` parameter to the URL. This parameter persists in the local storage of your browser and prevents redirection to the **Order status** page. To re-enable redirection, either clear your local storage or add the `?prevent_order_redirect=false` parameter to the URL and reload the page. When you're ready to release your changes to users, you can create and release an [app version](https://shopify.dev/docs/apps/launch/deployment/app-versions). An app version is a snapshot of your app configuration and all extensions. 1. Navigate to your app directory. 2. Run the following command. Optionally, you can provide a name or message for the version using the `--version` and `--message` flags. ## Terminal ```terminal shopify app deploy ``` Releasing an app version replaces the current active version that's served to stores that have your app installed. It might take several minutes for app users to be upgraded to the new version. Tip If you want to create a version, but avoid releasing it to users, then run the `deploy` command with a `--no-release` flag. You can release the unreleased app version using Shopify CLI's [`release`](https://shopify.dev/docs/api/shopify-cli/app/app-release) command, or through the Dev Dashboard. ## /extensions/typ-osp-survey/shopify.extension.toml ```toml api_version = "2025-10" [[extensions]] name = "typ-osp-survey" handle = "typ-osp-survey" type = "ui_extension" uid = "4e3e11dc-1fe2-8e29-485c-901abf804ea06596c846" [[extensions.targeting]] module = "./src/ThankYouPageSurvey.jsx" target = "purchase.thank-you.block.render" [[extensions.targeting]] module = "./src/OrderStatusPageSurvey.jsx" target = "customer-account.order-status.block.render" ``` ## /extensions/typ-osp-survey/src/shared.jsx ```jsx import { useCallback, useEffect, useState } from "preact/hooks"; /** * Returns a piece of state that is persisted in local storage, and a function to update it. * The state returned contains a `data` property with the value, and a `loading` property that is true while the value is being fetched from storage. * @param {string} key * @returns {[{data: any, loading: boolean}, (value: any) => void]} */ export function useStorageState(key) { const { storage } = shopify; const [data, setData] = useState(); const [loading, setLoading] = useState(true); useEffect(() => { async function queryStorage() { const value = await storage.read(key); setData(value); setLoading(false); } queryStorage(); }, [setData, setLoading, storage, key]); const setStorage = useCallback( (value) => { storage.write(key, value); }, [storage, key], ); return [{ data, loading }, setStorage]; } export function Survey({ title, description, onSubmit, children, loading }) { const [submitted, setSubmitted] = useState(false); async function handleSubmit() { await onSubmit(); setSubmitted(true); } if (submitted) { return ( Thanks for your feedback! Your response has been submitted ); } return ( {title} {description} {children} Submit feedback ); } ``` ## /extensions/typ-osp-survey/src/ThankyouPageSurvey.jsx ```jsx import { render } from "preact"; import { useState } from "preact/hooks"; import { Survey, useStorageState } from "./shared.jsx"; export default function () { render(, document.body); } function Attribution() { const [attribution, setAttribution] = useState(""); const [loading, setLoading] = useState(false); // Store into local storage if the attribution survey was completed by the customer. const [attributionSubmitted, setAttributionSubmitted] = useStorageState( "attribution-submitted", ); async function handleSubmit() { // Simulate a server request setLoading(true); return new Promise((resolve) => { setTimeout(() => { // Send the review to the server console.log("Submitted:", attribution); setLoading(false); setAttributionSubmitted(true); resolve(); }, 750); }); } // Hides the survey if the attribution has already been submitted if (attributionSubmitted.loading || attributionSubmitted.data === true) { return null; } return ( setAttribution(event.currentTarget.values[0])} > TV Podcast From a friend or family member Tiktok ); } ``` ## /extensions/typ-osp-survey/src/OrderStatusPageSurvey.jsx ```jsx import { render } from "preact"; import { useState } from "preact/hooks"; import { Survey, useStorageState } from "./shared.jsx"; export default function () { render(, document.body); } function ProductReview() { const [productReview, setProductReview] = useState(""); const [loading, setLoading] = useState(false); // Store into local storage if the product was reviewed by the customer. const [productReviewed, setProductReviewed] = useStorageState("product-reviewed"); async function handleSubmit() { // Simulate a server request setLoading(true); return new Promise((resolve) => { setTimeout(() => { // Send the review to the server console.log("Submitted:", productReview); setLoading(false); setProductReviewed(true); resolve(); }, 750); }); } // Hides the survey if the product has already been reviewed if (productReviewed.loading || productReviewed.data) { return null; } return ( { setProductReview(event.currentTarget.values[0]); }} > Amazing! Very happy with it. It's okay, I expected more. Eh. There are better options out there. I regret the purchase. ); } ``` ## Post-checkout survey UX guidelines To help merchants gain customer trust and to provide a great post-checkout experience, follow these guidelines when designing a post-checkout survey app extension. ### Display only one survey per session Limit content so that customers aren't overwhelmed with information after they've completed their order. ![The Order status page with only one placeholder survey included.](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/images/apps/checkout/thank-you-order-status/display-one-survey-DFXMkWkj.png) ### Give merchants control over survey content Merchants know their customers best and should have the flexibility to create surveys that relate to the shopping journey of their customers. ![A GIF that shows a rendered post-checkout survey extension cycling through different custom content that a merchant could choose to add.](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/images/apps/checkout/thank-you-order-status/survey-variations-ClpQiwvj.gif) ## Tutorial complete! Nice work - what you just built could be used by Shopify merchants around the world! Keep the momentum going with these related tutorials and resources. ### Next steps [![](https://shopify.dev/images/icons/32/information.png)![](https://shopify.dev/images/icons/32/information-dark.png)](https://shopify.dev/docs/apps/checkout/thank-you-order-status/ux-guidelines) [Review order status and Thank you page UX guidelines](https://shopify.dev/docs/apps/checkout/thank-you-order-status/ux-guidelines) [Optimize your user experience by following our UX guidelines.](https://shopify.dev/docs/apps/checkout/thank-you-order-status/ux-guidelines) [![](https://shopify.dev/images/icons/32/globe.png)![](https://shopify.dev/images/icons/32/globe-dark.png)](https://shopify.dev/docs/apps/checkout/localizing-ui-extensions) [Localize your extension](https://shopify.dev/docs/apps/checkout/localizing-ui-extensions) [Learn how to localize the text and number formats in your extension.](https://shopify.dev/docs/apps/checkout/localizing-ui-extensions) [![](https://shopify.dev/images/icons/32/blocks.png)![](https://shopify.dev/images/icons/32/blocks-dark.png)](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components) [Explore the Polaris web component reference](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components) [Learn about all of the components that you can use in your checkout UI extension.](https://shopify.dev/docs/api/checkout-ui-extensions/latest/polaris-web-components) [![](https://shopify.dev/images/icons/32/blocks.png)![](https://shopify.dev/images/icons/32/blocks-dark.png)](https://shopify.dev/docs/api/checkout-ui-extensions/latest/apis/extensiontargets) [Explore the checkout UI extension targets API reference](https://shopify.dev/docs/api/checkout-ui-extensions/latest/apis/extensiontargets) [Learn about the extension targets offered in the checkout.](https://shopify.dev/docs/api/checkout-ui-extensions/latest/apis/extensiontargets)