Displaying an order summary for the signed-in shopper
Extend the FastStore API with a query that summarizes the signed-in shopper's orders, authenticated with the shopper's own session.
9 min read
This guide describes how to extend your storefront with a section that shows signed-in shoppers how many orders they have placed and when the most recent one was. You can then place that section on any page managed in the Headless CMS, such as a landing page.
Because this data belongs to one specific shopper, the resolver must authenticate as that shopper rather than as the store, so each shopper only ever sees their own orders and never another shopper's.
For instructions on declaring type definitions and resolvers, see Extending API schemas. For the reasoning behind the credentials used here, see Authentication in API extensions.
Context
- You want to show a compact summary of the shopper's purchase history, without loading the full order list.
- The data comes from the Orders API, through the
/api/oms/user/ordersendpoint. - That endpoint returns only the orders belonging to whoever is signed in, so it requires no entity, schema, or app configuration of your own.
- The summary is personal data, so no shopper may read another shopper's orders.
The complete implementation is available in the playground.store repository, insrc/graphql/vtexandsrc/components/OrderSummary.
Before you begin
Make sure you have:
- A FastStore project running locally. See Getting started.
- A shopper account you can sign in with on your store, ideally with at least one placed order, so the summary shows a value other than zero.
No configuration in the VTEX Admin is required: the endpoint scopes the response by the shopper's own session.
Step 1: Creating the type definitions
Before the API can return the summary, your store's GraphQL schema needs to know that this data exists and what shape it takes. This step declares a new
orderSummary query and the OrderSummary type it returns. The resolver you will create in Step 2 is what actually fetches the data to fulfill this query.- Open your store's repository in a code editor of your preference.
- In the
src/graphql/vtex/typeDefsdirectory, create a file namedorderSummary.graphql. - In the
orderSummary.graphqlfile, add the following content:
In this file, the following is declared:
type OrderSummarydefines a new GraphQL object type with the two fields the section needs:totalOrders(required, henceInt!) andlastOrderDate(optional, since a shopper with no orders has no date to report). The triple-quoted comments above each field become that field's description in your API's schema.extend type Queryadds a new root-level query,orderSummary, to your store's existing GraphQL schema, without modifying any query that already exists.- Notice that the query takes no arguments. The shopper's identity comes from the request, not from the caller, so there is no email or user ID to pass. An argument like that would let any caller ask for someone else's summary.
@cacheControl(scope: "private", sMaxAge: 0)keeps the response out of shared caches, so one shopper's summary is never served to another visitor.
Step 2: Creating the resolver
The type definitions tell your API that an
orderSummary query exists, but GraphQL still needs a function that produces the data when that query runs. This step writes that function: a resolver that checks the shopper is signed in, then forwards the shopper's own cookies to the Orders API so the response is scoped to that one shopper.- In the
src/graphql/vtex/resolversdirectory, create a file namedorderSummary.ts. If theresolversdirectory doesn't exist yet, create it first. - In the
orderSummary.tsfile, add the following content:
In this file, the following is declared:
orderSummaryResolver.Query.orderSummaryis the function GraphQL calls whenever anorderSummaryquery runs. Its third parameter,ctx, is the request context, which carries the incoming headers, the account name, and the platform clients.requireAuthenticatedShopper(ctx)runs first and throws immediately for a signed-out visitor, so the rest of the function only ever runs for an authenticated shopper.UnauthorizedErrorcomes from@faststore/apiand results in a401response to the caller.- The
try/catcharound the validation matters. For a missing or expired token, VTEX ID answers401and the client throws rather than resolving with an unsuccessful status, so without thecatchthe caller would receive a500instead of a401. cookie: ctx.headers?.cookieforwards the shopper's own cookies to the Orders API. This is what makes the endpoint answer as that shopper. Sending anappKeyandappTokenpair instead would authenticate the request as the store, and the endpoint would no longer be scoped to one person.per_page: "1"keeps the response small. The count comes frompaging.total, so there is no reason to download the whole list.- If the response isn't successful, the resolver logs the status and throws. The upstream body is never returned, since an error body may carry headers or internal URLs.
Step 3: Registering the resolver
-
Open the
src/graphql/vtex/resolvers/index.tsfile. If your store doesn't have this file yet, create it in thesrc/graphql/vtex/resolversdirectory. -
Import the resolver you created in Step 2 and merge its
Queryroot into the exported object: -
Save the file.
Merge theQuery(andMutation, if applicable) keys from each resolver file, as above. Spreading two resolver objects that both defineQuerydirectly intoresolversoverwrites the root instead of merging it, and only the last one takes effect.
Step 4: Displaying the summary in a section
-
In the
src/componentsdirectory, create a directory namedOrderSummarywith a file namedOrderSummary.tsx. -
In the
OrderSummary.tsxfile, add the following content: -
Export the component from
src/components/index.tsx, so the storefront can render it: -
Declare the section in
cms/faststore/sections.json, so it becomes available in the CMS: -
Run
yarn cms-syncto send the section to the CMS, then add OrderSummary to a page in the VTEX Admin. See Creating a new section.
TheuseQuery_unstableanduseSession_unstablehooks are exported from@faststore/core/experimentaland their APIs may change between releases. See Experimental exports - Hooks and Components for more information.
The
doNotRun option matters here: without it, the query runs for signed-out visitors too, and every one of them receives a 401 the component has to discard.The component readspersonfromuseSession_unstableto tell whether someone is signed in.useAuth_unstableanswers the same question, but the property it returns was renamed between core versions, whilepersonis the same in both.
Expected behavior
The section reaches a "nothing to show" state through three different paths, and they mean different things:
| Situation | What happens |
|---|---|
| Visitor is not signed in | The query never runs, and the section invites the visitor to sign in. |
| Shopper is signed in with no orders | paging.total is 0, and the section shows 0 orders with no date. This is the expected, non-error case. |
| The request fails | The resolver throws, and the section shows the error message. An expired session is the most common cause. |
Result
On the page where you placed the section, a signed-in shopper sees how many orders they have placed and the date of the most recent one:

A signed-out visitor sees the sign-in message instead, and no request can return another shopper's summary, because the Orders API scopes the response to the session that made the request.