-
Notifications
You must be signed in to change notification settings - Fork 354
[2025-07] Add order filtering support to Customer Account API /account/orders route #3125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 2025-07-sfapi-caapi-update
Are you sure you want to change the base?
[2025-07] Add order filtering support to Customer Account API /account/orders route #3125
Conversation
Oxygen deployed a preview of your
Learn more about Hydrogen's GitHub integration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements order filtering functionality for the Customer Account API using the 2025-07 version. It allows customers to filter their order history by order number and confirmation number through a search form interface.
- Added GraphQL query parameter support for order filtering
- Implemented order filter utility functions for parsing and building search queries
- Created a responsive search form UI with order number and confirmation number inputs
Reviewed Changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.
File | Description |
---|---|
templates/skeleton/app/routes/account.orders._index.tsx | Added search form component and integrated filter functionality with URL parameter persistence |
templates/skeleton/app/lib/orderFilters.ts | Created utility functions for parsing URL parameters and building GraphQL query strings |
templates/skeleton/app/graphql/customer-account/CustomerOrdersQuery.ts | Added query parameter to the GraphQL orders query |
templates/skeleton/app/styles/app.css | Added CSS styles for the order search form with responsive grid layout |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
Implement new order filtering capabilities using the query parameter introduced in Customer Account API version 2025-07. Customers can now filter orders by name (order number) and confirmation number. Changes: - Add query parameter to CustomerOrdersQuery GraphQL fragments - Create orderFilters utility functions for building search queries - Add search form UI to orders page with inputs for filters - Update empty state to handle filtered vs unfiltered scenarios - Persist filter state in URL parameters for bookmarking The implementation follows the new filtering syntax: - name:1001 for order number filtering - confirmation_number:ABC123 for confirmation number filtering - Combined filters using AND operator
- Simplified HTML structure using semantic fieldset/legend elements - Removed inline styles in favor of CSS classes - Added responsive grid layout (single column mobile, two column desktop) - Improved accessibility with proper ARIA labels and search input types - Reduced visual complexity while maintaining full functionality - Minimal CSS that allows easy customization by developers
- Fix duplicate empty state logic by passing filters to OrdersTable - Add input sanitization to prevent injection attacks - Extract field name constants for maintainability - Remove hardcoded string repetition throughout component
5b89689
to
ed0c8b5
Compare
/** | ||
* Field name constants for order filtering | ||
*/ | ||
export const ORDER_FILTER_FIELDS = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI there are WAY more filter fields than just these 2 https://shopify.dev/docs/api/customer/latest/queries/customer#returns-Customer.fields.orders.arguments.query. We don't need to add support for all filters in this PR though (or for this release).
* @param value - The input string to sanitize | ||
* @returns The sanitized string | ||
*/ | ||
function sanitizeFilterValue(value: string): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Imo we should definitely have unit tests for all of this stuff as part of this PR.
In terms of the end state, this is something that's a pain to manually test and these will very objectively be "working" or "not working" with the API (which simpler tests can't guarantee), so I think we absolutely should add E2E tests for stuff like this. Doesn't need to happen as part of this PR/release though
function sanitizeFilterValue(value: string): string { | ||
// Only allow alphanumeric, underscore, and dash | ||
// Remove anything else to prevent injection | ||
return value.replace(/[^a-zA-Z0-9_\-]/g, ''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This specific regex for sanitation seems 👍🏻
Claude's verification
Verification Results: Current Sanitization is SAFE ✅
Based on my comprehensive research and testing:
Valid Characters in Shopify Fields:
-
Order Names:
#1001
,Store1001
- Uses: letters, numbers, hash (which is stripped separately)
-
Confirmation Numbers:
XPAV284CT
,R50KELTJP
,35PKUN0UJ
- Uses: uppercase letters and numbers only
-
Order Numbers: Pure integers like
1001
Current Sanitization (/[^a-zA-Z0-9_\-]/g
):
✅ Preserves all valid Shopify formats
- Keeps alphanumeric (covers all real examples)
- Keeps underscore and dash (for extended formats like
ORD-2024-001
) - Hash (
#
) is handled separately by stripping before sanitization
✅ Blocks ALL injection attempts
- Removes quotes, backslashes, brackets
- Removes SQL/GraphQL operators (
:
,>
,<
,=
) - Removes template syntax (
${}
,{{}}
) - Removes all XSS vectors
Key Insight:
The sanitization appears "too restrictive" because it removes colons, BUT that's actually correct! The colons in name:1001
are part of the query syntax, not the user input. The code correctly:
- Sanitizes user input:
"1001"
→"1001"
- Then builds query:
"name:" + "1001"
→"name:1001"
Conclusion:
The current implementation is production-ready and secure. It:
- ✅ Works with all documented Shopify formats
- ✅ Prevents GraphQL injection effectively
- ✅ Follows defense-in-depth principles
The only minor limitation is removing spaces from order names, but Shopify's actual order formats don't use spaces in the examples, so this is acceptable.
Filter Orders | ||
{hasFilters && ( | ||
<small className="order-search-active"> | ||
({[currentFilters.name, currentFilters.confirmationNumber].filter(Boolean).length} active) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
</div> | ||
); | ||
} | ||
|
||
function OrdersTable({orders}: Pick<CustomerOrdersFragment, 'orders'>) { | ||
function OrdersTable({orders, filters}: {orders: CustomerOrdersFragment['orders']; filters: OrderFilterParams}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should definitely have an aria-live: polite
on this for accessibility
<div className="order-search-buttons"> | ||
<button type="submit">Search</button> | ||
{hasFilters && ( | ||
<button |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
</div> | ||
|
||
<div className="order-search-buttons"> | ||
<button type="submit">Search</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to be too picky about the UX but I think very basic "loading" state functionality should be here as part of this PR. I like the loading UX for the "Profile" form where it simply disables the button + adds "ing" to it while loading. I think we should copy that here
/** Order name or number (e.g., "#1001" or "1001") */ | ||
name?: string; | ||
/** Order confirmation number */ | ||
confirmationNumber?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't seem to be querying or, therefore, displaying the order confirmation number. As a result, it's confusing that we are offering the option to filter by this.
Personally I think we should delete this option and just have name
as a filter param for now. I'd also be fine with replacing this with a different filter param that we're rendering (or start querying + rendering the order confirmation number and keep this filter param), but deleting this is the simplest option for now. Up to you.
WHY are these changes introduced?
Fixes #3079
The Customer Account API 2025-07 release added support for filtering orders using a
query
parameter. This allows merchants and customers to search through their order history more efficiently. Previously, customers had to manually scroll through paginated results to find specific orders.WHAT is this pull request doing?
This PR implements order filtering functionality in the skeleton template's customer account orders page:
query
parameter support to theCustomerOrdersQuery
GraphQL querybuildOrderSearchQuery
: Constructs the query string for the GraphQL parameterparseOrderFilters
: Parses URL search parameters into filter objectsHOW to test your changes?
Start the dev server with Customer Account Push enabled:
cd templates/skeleton SHOPIFY_HYDROGEN_FLAG_CUSTOMER_ACCOUNT_PUSH=true npm run dev
Use a
.tryhydrogen.dev
URL for OAuth to work properlyNavigate to the customer account orders page (
/account/orders
)Test filtering:
Verify:
Post-merge steps
None required.
Checklist
Additional Notes