Efficient way to get a list of store credit accounts with balance

Hi, is there an efficient way to get a list of store credit accounts with balance w/o looping through all customers one by one?

The only way that I am aware of is to loop through all customers w/o a way to narrow it down to customers with store credit account, which is very inefficient: customers - GraphQL Admin

Hey Andrew,

I asked the .dev assistant on shopify.dev and it suggested that it should be possible to “efficiently retrieve a list of store credit accounts with balances without looping through all customers, you can use the storeCreditAccounts field available on the Customer object. This field allows you to query store credit accounts directly, and you can use filters like query to narrow down the results to accounts with balances.”

Here’s an example query:

query GetStoreCreditAccountsWithBalance {
  customers(first: 10) {
    edges {
      node {
        id
        displayName
        storeCreditAccounts(query: "balance.amount:>0") {
          edges {
            node {
              id
              balance {
                amount
                currencyCode
              }
            }
          }
        }
      }
    }
  }
}

Can you test that out? I don’t have customers with store credit to test it on myself.

This will still need to loop through all customers with customers(first: 10) and pagination. It would work if shopify supports this instead: customers(first: 10,query: “balance.amount:>0”)

Hi again Andrew,

I’ve connected with the store credit team to see if there’s a more efficient method, or if they are considering investing in building support for other methods.

Hi Andrew,
Eileen here from the store credit team. There isn’t a better way to do this at the moment. We have an item in our backlog to create a top level query that will allow you to do this type of filtering much more efficiently. I don’t have a specific timeline for this to share, but we will post in the changelog when available.
Also worth noting, in admin you can now build customer segments based on store credit balance.

2 Likes

@Eileen_Adams

Wondering if there is any updates on this?

It is not particularly efficient to loop through customers to get something like this.

Ideally a top level query would allow visibility of storeCredits as well as status/currency/customer (so we can check if they are used or not).

This is used downstream to align with Accounting systems.

@Eileen_Adams @Liam-Shopify Also tried this to get a list of customer id and emails with store credit balance so that I can do another query to get the actual store credit accounts but it fails even though it is a valid query:

      query queryWithVariables( $query: String!, $after: String) {
        customerSegmentMembers(first: 250, query: $query, after: $after) {
          edges {
            node {
              id
              defaultEmailAddress {
                emailAddress
              }             
              firstName
              lastName
            }
          }
          pageInfo {
            hasNextPage
            endCursor
          }
        }
      }

variables:

{ "query": "store_credit_accounts MATCHES (balance >= 0.01)" }

It is giving me this error even though it should be a valid query according to the shopify query syntax documentation:

status: 200, code: 0, body: [{"message"=>"Line 1 Column 0: 'MATCHES' is unexpected.", "locations"=>[{"line"=>2, "column"=>38}], "path"=>["customerSegmentMembers", "query"]}]
1 Like

I still haven’t really gotten this to work. the call suggested doesnt seem to work either.

Kind of really need the ability to query store credits and get a ledger, given the tax and accounting implications.

is there any ETA on this?

@Liam-Shopify @Eileen_Adams

Hello, you need to pass (and deal with) first (or last) to the second query too

es:

query GetStoreCreditAccountsWithBalance {
  customers(first: 10) {
    edges {
      node {
        id
        displayName
        storeCreditAccounts(first:10 query: "balance.amount:>0") {
          edges {
            node {
              id
              balance {
                amount
                currencyCode
              }
            }
          }
          pageInfo{
                hasNextPage
                endCursor
            }
        }
      }
    }
    pageInfo{
        hasNextPage
        endCursor
    }
  }
}

i hope this can help you

1 Like
{ "query": "store_credit_accounts MATCHES (balance >= 0.01)" }

work on 2026-01 api version
not on 2025-*
i think it’s a bug but who knows

thanks, assume you just iterated through the cursers? Feels so overkill.

oh yes i feel you … but it is what we have , sometimes we have a choice or a workaround sometimes no (this can be bad or good: it’s hard get a good salary from easy stuff ahah)

you are lucky enough to have a workaround (as @ChrisBradley wrote segmentation is your friend):

  • this will give you all customers IDs that have a positive credit_amount (with some manipulation)

    
    query { 
        customerSegmentMembers(first: 250, query:"store_credit_accounts MATCHES (balance >= 0.01)") {
            edges {
                node {id}
            }
            pageInfo {hasNextPage endCursor} 
        }
    }
    
    
  • you should expect an output like this (i have only 2 customers with a positive credit_amount on this test shop):

    ...
    "edges": [
        {
            "node": {
                "id": "gid://shopify/CustomerSegmentMember/7564964167925"
            }
        },
        {
            "node": {
                "id": "gid://shopify/CustomerSegmentMember/7671095951605"
            }
        }
    ]
    ...
    
  • now you only need to loop every nodes, replace this: gid://shopify/CustomerSegmentMember/ with this: gid://shopify/Customer/ and retrieve the balance in this way(i put them all together but you can do a gql call for every customer it’s your choice):

    query { 
      c1:customer(id: "gid://shopify/Customer/7564964167925") { 
          displayName
          storeCreditAccounts(first:100 query: "balance.amount:>0") {
            edges {
              node {
                id
                balance {
                  amount
                  currencyCode
                }
              }
            }
            pageInfo {hasNextPage endCursor}
          }
      }
      c2:customer(id: "gid://shopify/Customer/7671095951605") { 
          displayName
          storeCreditAccounts(first:100 query: "balance.amount:>0") {
            edges {
              node {
                id
                balance {
                  amount
                  currencyCode
                }
              }
            }
            pageInfo {hasNextPage endCursor}
          }
      }
    }
    

    of course both c[index] (if you plan to use a single query) and the id are hardcoded but you have to put them in variables by code

  • this is the final output (yay!!)

    {
      "data": {
          "c1": {
              "displayName": "emi cic",
              "storeCreditAccounts": {
                  "edges": [
                      {
                          "node": {
                              "id": "gid://shopify/StoreCreditAccount/618168565",
                              "balance": {
                                  "amount": "50000.0",
                                  "currencyCode": "EUR"
                              }
                          }
                      }
                  ],
                  "pageInfo": {
                      "hasNextPage": false,
                      "endCursor": "eyJsaW1pdCI6MTAwLCJzb3J0X2ZpZWxkcyI6ImlkIiwic29ydF9kaXIiOiJhc2MiLCJsYXN0X2lkIjo2MTgxNjg1NjUsImxhc3RfdmFsdWUiOlsiNjE4MTY4NTY1Il0sImRpcmVjdGlvbiI6Im5leHQifQ=="
                  }
              }
          },
          "c2": {
              "displayName": "emiliano cic",
              "storeCreditAccounts": {
                  "edges": [
                      {
                          "node": {
                              "id": "gid://shopify/StoreCreditAccount/618201333",
                              "balance": {
                                  "amount": "20000.0",
                                  "currencyCode": "EUR"
                              }
                          }
                      }
                  ],
                  "pageInfo": {
                      "hasNextPage": false,
                      "endCursor": "eyJsaW1pdCI6MTAwLCJzb3J0X2ZpZWxkcyI6ImlkIiwic29ydF9kaXIiOiJhc2MiLCJsYXN0X2lkIjo2MTgyMDEzMzMsImxhc3RfdmFsdWUiOlsiNjE4MjAxMzMzIl0sImRpcmVjdGlvbiI6Im5leHQifQ=="
                  }
              }
          }
      },
      "extensions": {
          "cost": {
              "requestedQueryCost": 114,
              "actualQueryCost": 10,
              "throttleStatus": {
                  "maximumAvailable": 2000.0,
                  "currentlyAvailable": 1990,
                  "restoreRate": 100.0
              }
          }
      }
    }
    

last but not least this is what i usually do when there are no workaround:

  • sort for update date and quit the loop on first “no change” (the first run is a paint , the nexts run smoother)

  • set sleep time based on actualQueryCost and not on requestedQueryCost

  • when nested loop use something like that:

    xxx(first:200){
      yyy(first:10){
        ....
      }
    }
    

    and when i need to iterate the internal loop i set external loop to 1 and internal loop to max length:

    xxx(first:1 after:kkk){
     yyy(first:250 after:qqq){
       ....
     }
    }
    

with this 3 step you could survive even to millions customers (lucky store)

I hope this helps because I have no more bullets to shoot :slight_smile:

1 Like