Skip to main content

Migrate TopBar from Polaris React

Polaris React TopBar has no like-for-like replacement. Embedded apps already render inside the Shopify admin, which owns the global top bar, account menu, search, and responsive navigation controls.

Remove the in-iframe TopBar, then migrate only the app-specific responsibilities that it contained. Use s-app-nav for primary app destinations, s-page for the current page heading and actions, and page content for search that filters your app's resources.


Anchor to Migrate the app shellMigrate the app shell

The following example moves product search into the Products page, promotes the create action into the page title area, and makes Settings and Support primary app destinations. It removes the duplicate user identity and mobile-navigation controls.

Migrating an embedded app top bar

function ProductsApp({products, query, onQueryChange, onCreateProduct}) {
const homeLink = {href: '/app', rel: 'home'} as const;

return (
<>
<s-app-nav>
<s-link {...homeLink}>Home</s-link>
<s-link href="/app/products">Products</s-link>
<s-link href="/app/settings">Settings</s-link>
<s-link href="/app/support">Support</s-link>
</s-app-nav>

<s-page heading="Products">
<s-button
slot="primary-action"
variant="primary"
onClick={onCreateProduct}
>
Create product
</s-button>

<s-section heading="All products">
<s-stack gap="base">
<s-search-field
label="Search products"
value={query}
onInput={(event) => onQueryChange(event.currentTarget.value)}
/>

{products.length > 0 ? (
<s-unordered-list>
{products.map((product) => (
<s-list-item key={product.id}>
<s-link href={`/app/products/${product.id}`}>
{product.title}
</s-link>
</s-list-item>
))}
</s-unordered-list>
) : (
<s-paragraph>No products match this search.</s-paragraph>
)}
</s-stack>
</s-section>
</s-page>
</>
);
}
import {Frame, Page, TopBar} from '@shopify/polaris';

export function ProductsApp({
query,
onQueryChange,
onCreateProduct,
onContactSupport,
onNavigationToggle,
}) {
const searchField = (
<TopBar.SearchField
value={query}
onChange={onQueryChange}
placeholder="Search products"
/>
);

const userMenu = (
<TopBar.UserMenu
name="Jaded Pixel"
detail="store@example.com"
initials="JP"
actions={[
{
items: [
{content: 'Account settings', url: '/app/settings'},
{content: 'Contact support', onAction: onContactSupport},
],
},
]}
/>
);

return (
<Frame
topBar={
<TopBar
searchField={searchField}
userMenu={userMenu}
showNavigationToggle
onNavigationToggle={onNavigationToggle}
/>
}
>
<Page
title="Products"
primaryAction={{content: 'Create product', onAction: onCreateProduct}}
/>
</Frame>
);
}

s-app-nav renders only in the Shopify admin shell, so this admin navigation example isn't available as an isolated live preview.

Keep s-app-nav mounted near the app root and let each route render its own s-page. Don't mount a separate app nav or page shell for each search result.


Anchor to Move each responsibilityMove each responsibility

Polaris React TopBar responsibilityPolaris web componentsMigration notes
App name and surrounding chromeRemoveThe Shopify admin identifies the embedded app and owns the global shell.
showNavigationToggle and onNavigationToggleRemoveThe Shopify admin owns responsive navigation controls. Remove state used only to open an in-iframe menu.
searchFields-search-field in the relevant pageKeep app-resource search near the results it changes. Use route or URL state when the query should survive navigation.
searchResults and searchResultsVisibleA page table, list, or search-results sectionRender normal page content from the same query state. Include loading, empty, and error states.
onSearchResultsDismissClear the page search queryReset associated filters and pagination only when that matches the existing behavior.
secondaryMenuSlotted s-page actions or an s-menuKeep frequent actions visible and put only related, secondary actions in a menu.
userMenu identity fieldsRemoveDon't reproduce the merchant or shop identity that the Shopify admin already displays.
App-specific userMenu.actionss-app-nav, page links, or page actionsMove destinations such as app settings and support to a discoverable location based on their scope.

Anchor to Keep app search in contextKeep app search in context

First decide what the old search queried:

  • If it searched resources owned by your app, place s-search-field with the table, list, or index page that displays those results.
  • If it filtered only the current page, keep the query in that route and clear it when leaving the page.
  • If it was intended to search the entire Shopify admin, remove it. Embedded apps can't replace or customize the admin's global search.

Connect the field to the backend query rather than filtering only the visible page when results are paginated. Reset pagination when the query changes, and use the debounce and cancellation pattern in the Filters migration so an older response can't replace newer results. Render loading and retryable error states next to the results.

Don't move page-specific search into every route just to preserve the old top-bar position. Pages that don't search app resources shouldn't render an empty search control.


Anchor to Rehome account and menu actionsRehome account and menu actions

Classify every old TopBar.UserMenu and TopBar.Menu action by purpose:

  • Put primary app destinations such as Settings in s-app-nav when they apply across the app.
  • Put resource or workflow actions in the relevant s-page action slot.
  • Put secondary, related page actions in s-menu, opened by a slotted page button.
  • Put help links in the page or app navigation when merchants need persistent access to them.
  • Remove sign-out, store-switching, merchant-profile, and other Shopify-account controls. The Shopify admin owns those actions.

Preserve authorization checks and pending, success, and error handling when moving an action. Changing its placement doesn't make its backend operation safe to run more than once.


Anchor to Remove TopBar and Frame stateRemove TopBar and Frame state

Remove TopBar, TopBar.SearchField, TopBar.UserMenu, and TopBar.Menu after their app-specific responsibilities have moved. Also remove state and callbacks used only for these behaviors:

  • Opening or closing the old user and secondary menus.
  • Showing or dismissing overlaid search results.
  • Opening in-iframe mobile navigation.
  • Repeating the current shop's name, email, initials, or avatar.

If Frame still hosts Navigation, Toast, Loading, or ContextualSaveBar, migrate those responsibilities before removing it. Follow the Frame migration for the complete shell teardown.


  • Open every s-app-nav destination directly and through browser navigation.
  • Run page search through loading, results, empty, failed, cleared, and out-of-order response states.
  • Verify search and pagination restore correctly after reload, back, and forward navigation.
  • Run every moved page or menu action through success and failure, including repeated-submission protection.
  • Test wide and narrow admin layouts and confirm that no app-owned top bar duplicates Shopify admin chrome.
  • Verify page headings, action order, search labels, result announcements, and keyboard focus.

Anchor to Remove Polaris ReactRemove Polaris React

After every TopBar call site is migrated, remove its imports, shell-only state, menu descriptor builders, and CSS targeting TopBar internals. Remove @shopify/polaris only after no other route in scope imports it.



Was this page helpful?