Architecting Custom Order Pages in Shopify Using Customer Account UI Extensions

Introduction
Shopify continues to expand what’s possible for post-purchase experiences through Customer Account UI Extensions. These extensions allow partners to build deeply customized, secure customer interfaces, directly inside Shopify’s authenticated account environment.
But with that power comes important architectural boundaries. Partners cannot override native account routes, inject global JavaScript, or freely redirect customers using traditional browser techniques. For merchants who rely on external order management systems (OMS) as their operational source of truth, this raises a critical question:
How can you build a fully custom customer order experience, powered by external systems, while staying entirely within Shopify’s extension framework?
This article outlines a production-proven architecture that does exactly that. It replaces Shopify’s default order list and order detail views with a custom, OMS-driven experience using three coordinated UI extensions, a shared API layer, and an adapter-based data normalization strategy.
Working Within Shopify’s Customer Account Constraints
Customer Account UI Extensions are intentionally sandboxed. This design protects store security, ensures performance consistency, and prevents fragile theme-level overrides. In practice, it introduces several constraints that directly affect solution design:
No direct redirects from routes like
/account/ordersNo global scripts or DOM manipulation
Strict rendering targets only
No external network access by default
Explicit permission required for Shopify API access
You can only render UI inside approved targets such as:
customer-account.order-index.block.rendercustomer-account.page.render
Outbound API requests require network_access = true. Shopify GraphQL requests require api_access = true. These rules shape the entire architecture.
Rather than fighting these constraints, this solution embraces them as first-class design inputs.
A Purpose-Built, Three-Extension Architecture
Instead of trying to force a single extension to do everything, the solution is intentionally split into three focused extensions, each with one responsibility.
1. Redirect Extension – Replacing the Native Orders Page
The first extension mounts directly inside Shopify’s native Order Index page using:
customer-account.order-index.block.render
This extension does not render UI. Its sole purpose is to immediately navigate customers into the custom order list experience using Shopify’s internal navigation API.
import {
reactExtension,
useApi
} from "@shopify/ui-extensions-react/customer-account";
export default reactExtension(
"customer-account.order-index.block.render",
() => <Redirector />
);
function Redirector() {
const { navigation } = useApi();
navigation.navigate("extension:customer-order-list");
return null;
}
From the shopper’s perspective, Shopify’s native Orders page simply never appears. Navigation feels instantaneous and native.
2. Order List Extension – The OMS-Powered Hub
Once redirected, customers land on the custom Order List extension, mounted at:
customer-account.page.render
This extension displays:
Historical orders
Open quotes
Gallery and list views
Order status indicators
Cross-navigation into order details
Most importantly, Shopify is not the order data source. The extension retrieves real-time data directly from an external OMS.
3. Order Details Extension – Deep Fulfillment Visibility
The Order Details extension, also mounted at customer-account.page.render, renders:
Multi-shipment fulfillment
Delivery progress timelines
Pickup vs. shipping views
Billing and shipping details
Quote-specific layouts
Navigation between the list and details views uses Shopify’s internal navigation system with URL query parameters.
Why the Redirect Technique Is So Effective
At first glance, redirecting from inside Shopify’s Order page via an extension may feel unconventional. In practice, it is the cleanest and most reliable way to replace native pages without violating platform rules.
Because the extension mounts directly into Shopify’s own rendering pipeline, the call to:
navigation.navigate("extension:customer-order-list")
executes before the native UI becomes visible. There is no flicker, no race condition, and no need for DOM manipulation. The result is a seamless handoff from Shopify routing to your custom experience.
Shared API Layer: One Gateway for Shopify and OMS Data
Once the UI is decoupled from Shopify’s order data, identity becomes the new bridge between systems. The shared API layer handles this orchestration by:
Fetching the authenticated customer’s email from Shopify
Using that identity to request OMS orders and quotes
Managing authentication headers and security tokens
Centralizing error handling
export async function getShopifyCustomerEmail() {
const response = await fetch(
"shopify://customer-account/api/2025-07/graphql.json",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(CUSTOMER_EMAIL_QUERY),
}
);
}
export async function getOMSOrdersAndQuotesByEmail(email) {
const response = await fetch(
"https://<OMS_BACKEND_API_URL>/api/orders",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer [TOKEN]",
},
origin: "<OMS_BACKEND_API_URL>",
body: JSON.stringify({ email }),
}
);
}
This layer ensures that:
UI components remain free of security concerns
OMS integrations can evolve independently
Shopify GraphQL access remains tightly scoped
Adapter Pattern: Normalizing Third-Party Order Data
Third-party OMS platforms are optimized for operations, not customer interfaces. Their schemas often differ significantly across orders, quotes, fulfillments, locations, and payments.
To keep the UI simple and predictable, the solution uses a strict adapter pattern:
export function transformOrderList(orders) {
return orders.map(mapToOrderListItem);
}
export function transformOrderDetails(order) {
return mapToOrderDetails(order);
}
Adapters normalize:
Item counts
Image extraction
Fulfillment completion status
Location formatting
Financial totals
This separation ensures the UI always receives a clean, standardized data shape, regardless of how the OMS evolves.
Managing Data Lifecycle Inside the Order List
The Order List extension fetches identity and OMS data on mount while guarding against common async pitfalls:
useEffect(() => {
let isMounted = true;
async function fetchData() {
const email = await getShopifyCustomerEmail();
const data = await getOMSOrdersAndQuotesByEmail(email);
if (!isMounted) return;
setOrders(transformOrderList(data.orders));
setQuotes(transformOrderList(data.quotes));
}
fetchData();
return () => { isMounted = false; };
}, []);
During loading, the UI displays skeleton components instead of blocking spinners. This preserves responsiveness and creates a smoother perceived experience for shoppers.
Deep-Link Navigation into Order Details
Navigation between order lists and order details is handled using Shopify’s internal extension routing. The Order Details extension validates incoming parameters before loading:
function OrderStatusPage() {
const entry = useNavigationCurrentEntry();
const { navigation } = useApi();
useEffect(() => {
const orderId = entry.url.split("orderId=")[1];
if (!orderId) {
navigation.navigate("extension:customer-order-list");
return;
}
// Fetch and render order details
}, []);
}
This ensures:
Safe fallback routing
Bookmarkable order detail views
Clean separation between list and detail logic
Design Principles That Guided the System
This architecture is driven by three core principles:
1. Separation of concerns at every layer UI renders. APIs fetch. Adapters transform. Utilities format.
2. User experience as the primary constraint Redirects must be instant. Loading states must feel intentional. Errors must never trap shoppers.
3. Long-term maintainability over short-term shortcuts OMS schemas evolve. Fulfillment logic grows. This architecture absorbs change without forcing repeated UI rewrites.
Conclusion
Shopify’s Customer Account UI Extensions create a powerful, but deliberately structured, environment for post-purchase customization. While direct route overrides and traditional redirects are not allowed, this article demonstrates how partners can still deliver:
Fully custom order lists
Deep OMS-driven fulfillment detail views
Seamless native-feeling navigation
Secure, compliant integrations
By combining:
A redirect interception extension
An OMS-powered order list
A deep order details interface
A shared API orchestration layer
A strict adapter normalization pattern
brands can deliver a best-in-class customer account experience while staying fully aligned with Shopify’s platform standards.
