Shopify Custom Design Requests: Navigating Draft Orders & Customer Accounts

Hey everyone! As a Shopify migration expert, I often dive deep into the community forums to see what real store owners and developers are wrestling with. Recently, I stumbled upon a fantastic discussion about building a "Custom Design Requests" page within customer accounts using Draft Orders. This is a common need for businesses offering personalized products, like custom rugs, jewelry, or apparel. It's tricky to get right, so I wanted to share the collective wisdom from that thread.

The original poster, vedant_shopify_dev, laid out a clear workflow: customers submit design specs, a Draft Order is created, and then they wanted a dedicated page in the customer account area to display these requests, much like a standard order history. It's a brilliant idea to leverage Shopify's existing infrastructure, but as the community quickly highlighted, there are some important nuances.

Is Using Draft Orders for Custom Requests TOS-Compliant?

This was one of the first big questions, and thankfully, M.Rahman provided a crystal-clear answer: Yes, 100% compliant!

The key here is understanding Shopify's Terms of Service (TOS) context. Shopify prohibits bypassing their checkout for standard product sales using an external payment gateway. However, for an inquiry, quote, or custom request flow where the final payment will be handled via a custom invoice link that directs the customer back to Shopify Checkout (the invoice_url), you're completely in the clear. So, if your custom rug design eventually leads to an invoice paid through Shopify, you're good to go.

The Architectural Crossroads: Draft Orders vs. Metaobjects

While using Draft Orders for custom requests is TOS-compliant, the community had some excellent points about its suitability for the entire lifecycle of a custom request. lumine and LucasMao both raised a critical insight: Draft Order statuses (OPEN, INVOICE_SENT, COMPLETED) might not be granular enough to express the full journey of a custom design request, especially if it goes through multiple revisions, rejections, or approvals before a price is even agreed upon.

Imagine a custom rug request that needs two revisions, then gets rejected, then re-submitted. Trying to cram all that into Draft Order tags can get messy. The consensus emerged: it's often better to separate the design-request record from the payment object. This means:

  • Use a dedicated data store for the request itself: Shopify Metaobjects or your own app-owned database are excellent choices for holding the initial request, its specifications, and a more detailed status (e.g., "Pending Review," "Revision Requested," "Quote Sent," "Approved").
  • Create the Draft Order only when the quote is ready: Once the design is finalized and the price is agreed upon, then you generate the Draft Order. This keeps your merchant's Draft Order list clean, free of abandoned or pending requests, and uses Draft Orders for their intended purpose: invoicing.

Displaying Custom Requests in Customer Accounts: The Technical Deep Dive

This is where things get really technical, and the community provided invaluable guidance.

The Page Itself: Customer Account UI Extensions

For the frontend page in the customer account area, lumine confirmed that the target you want is customer-account.page.render. This renders an entirely new, full page within the customer account, allowing it to sit neatly alongside "Orders" in the navigation menu. Just remember: a full page target can't coexist with any other target in the same extension, so if you need other blocks elsewhere, they'll need separate [[extensions.targeting]] entries.

The Major Hurdle: Draft Orders are Admin-Only

Here's the critical piece of information from M.Rahman: Draft Orders are not exposed in the Storefront API. This means your Customer Account UI Extension, running client-side, cannot directly query for Draft Orders for security reasons. You can't just fetch them from the frontend.

The Solution: A Secure Backend and App Proxy

Both LucasMao and M.Rahman emphasized the need for a secure backend. This is the recommended approach:

  1. Build a Lightweight Backend Service: This could be a Node.js, Remix, or Next.js service, or part of a custom Shopify App using an App Proxy (e.g., /apps/custom-requests).

  2. Customer Account UI Extension Initiates Request: When a logged-in customer visits your "Requested Custom Designs" page, the UI Extension sends a request to your app's backend.

  3. Backend Validates Customer & Queries Admin API: Your backend service receives the request. It's crucial here to securely validate the logged-in customer. lumine pointed to the Session Token API for this. You'll get a signed JWT; your backend verifies it with your app's shared secret, and the dest claim confirms the shop. For the customer's identity, the sub claim carries the customer GID, but this is only present if your app has access to protected customer data. If not, you can verify the shop but not the specific customer via the JWT alone, so careful planning is needed around how your extension passes the customer ID securely to your backend.

  4. Query Draft Orders (or Metaobjects) Securely: Once validated, your backend uses the Shopify Admin GraphQL API to query for the relevant Draft Orders (or custom Metaobjects). The correct query syntax for Draft Orders, as corrected by lumine, is with customerId (the numeric legacy ID), not customer_id:

    query getCustomerDraftOrders {
      draftOrders(first: 10, query: "customerId:YOUR_CUSTOMER_ID") {
        edges {
          node {
            id
            name
            status
            invoiceUrl
            lineItems(first: 10) {
              edges {
                node {
                  title
                  customAttributes {
                    key
                    value
                  }
                }
              }
            }
          }
        }
      }
    }
    

    Remember, a wrong search field returns an empty connection, not an error, so precision matters!

  5. Return Structured Data to Frontend: Your backend processes the Admin API response and sends the structured data back to your Customer Account UI Extension for display.

So, while the idea of using Draft Orders for custom design requests is sound and TOS-compliant for the invoicing stage, the real power and flexibility come from pairing it with a robust backend. This backend will handle the secure authentication, the complex lifecycle of the request (perhaps using Metaobjects), and the necessary Admin API calls to fetch the data. It's a bit more involved than just a client-side extension, but it gives you the secure, scalable, and compliant solution your custom product business needs.

Share:

Use cases

Explore use cases

Agencies, store owners, enterprise — find the migration path that fits.

Explore use cases