Tutorial ·
How to Get a List of Shopify Stores by Country and Category
Build a CSV of Shopify stores filtered by country, category, size and Shopify Plus status with a short TypeScript script, and keep the credit cost predictable.
By the end of this tutorial you'll have a TypeScript script that asks for every Shopify store in a country and category that fits a size band, pages through the results and writes them to a CSV you can open in a spreadsheet or import into a CRM. It uses the ShopLens GET /shops search, which filters stores by country, category, Shopify Plus status, estimated sales and traffic, product counts and creation date.
Prerequisites
- Node.js 20 or later, which ships fetch built in.
- A ShopLens API key, saved in an environment variable called SHOPLENS_API_KEY. You get one from the [dashboard](https://app.shoplens.dev).
- A clear idea of who you're looking for. Write it down as filters: "US apparel stores selling between $20k and $200k a month" is a filter; "good leads" is not.
Search costs one credit per store returned, so a list of 500 stores costs 500 credits. Credits and plans are on the pricing page.
Step 1: make a single request
Start with one request to see the shape of the response. This asks for ten US apparel stores, sorted by estimated monthly sales, highest first.
The response holds a page of stores and a cursor for the next page:
The example values are made up. Each store summary carries the same core fields as a full profile: location, currency, language, category, counts, the three estimates and the Plus flag.
Step 2: choose your filters
The search combines filters with AND. The ones you will use most:
| Parameter | What it does | Example |
|---|---|---|
| country | ISO country code of the store | US, GB, DE |
| category | One of the fixed categories in the docs | APPAREL, PETS, HOME_DECOR |
| status | Store status; ACTIVE skips closed and password-protected stores | ACTIVE |
| shopifyPlus | Only stores with a positive Plus signal, or only stores known not to be on Plus | true |
| minEstimatedMonthlySales, maxEstimatedMonthlySales | Inclusive sales range in USD | 20000, 200000 |
| minEstimatedMonthlyVisits, maxEstimatedMonthlyVisits | Inclusive traffic range | 10000 |
| minProductsCount, maxProductsCount | Catalog size | 50 |
| minCreatedAt, maxCreatedAt | When the store was created on Shopify | 2025-01-01 |
| q | Free-text search | linen |
The full list of categories and parameters is in the API docs. Two notes on filters that trip people up. First, the sales and traffic numbers are modeled estimates, good for bands and ranking; how to estimate a Shopify store's sales covers what that means. Second, shopifyPlus=false returns stores known not to be on Plus, which is different from "every store without a Plus signal". How to tell if a store is on Shopify Plus explains the three states.
Step 3: page through the results
Each response returns up to limit stores (the maximum is 50). While hasNextPage is true, send the same request again with cursor set to nextCursor. Keep the filters and the sort identical: a cursor issued for one sort is rejected with a 400 if you change it.
Here is the loop in TypeScript, with a hard cap so a broad filter can't spend more credits than you planned.
The loop waits and retries when it hits the per-minute rate limit, which answers 429 with a Retry-After header and spends no credits.
Step 4: write the CSV
Turn the results into rows. Keep the myshopify domain as the key column, because custom domains change and the myshopify address doesn't; how to find a store's myshopify domain explains why.
Step 5: put it together
Save the three blocks in one file, such as stores.ts, and run it with npx tsx stores.ts. With the filters above, the script stops at 500 stores and spends at most 500 credits.
Estimating the cost before you run it
Because search is billed per store returned, you can work out the cost of a list before you pull it. Run the request with limit=1 first and look at the result: if hasNextPage is true, the filter matches more than one store, and a request with limit=50 tells you whether it matches more than a page. The safest approach is the one in the script: set MAX_STORES to the number of stores you can actually work with, so the run stops there whatever the filter matches. A sales team that can contact 200 stores a month gains nothing from a list of 5,000.
Credits are shared between the REST API and the MCP server, and only successful requests spend them. A request that fails validation, or one that hits the rate limit, costs nothing, so a bug in your filters doesn't drain the balance.
Checking the list by hand
Before you hand the list to a sales team, open twenty stores at random from the CSV. Check that each one is live, sells in the category you asked for and looks like a business you'd want as a customer. If more than two or three of the twenty miss, tighten the filters and run the script again. Ten minutes of spot checks costs less than a week of outreach to the wrong stores.
Keeping the list fresh
Store data changes. Stores close, move country, change category or grow into a new sales band. If you keep the CSV in a CRM, re-run the script every month or quarter with the same filters and compare by myshopify domain:
- New rows are stores that now match: new launches, or stores that grew into your range.
- Missing rows are stores that no longer match. Check them before you delete anything; a store that dropped out of a sales band is still a store.
- Changed values such as a new Plus flag or a jump in estimated sales are worth a note on the account.
To find only stores created since your last run, add minCreatedAt with the date of that run and sort by createdAt.
Tuning the list
Run the script with a small MAX_STORES first, such as 50, and open the CSV. If the top rows don't look like stores you'd sell to, change the filters before you pull the full list. A few patterns that help:
- Too many tiny stores. Raise minEstimatedMonthlySales or minProductsCount.
- Too many giants. Set maxEstimatedMonthlySales. Large brands often have procurement processes that don't suit a small vendor.
- Wrong niche inside a category. Add q with a product word, such as "candle" inside HOME_DECOR.
- Only new stores. Set minCreatedAt to a recent date and sort by createdAt to find stores that just launched.
What to build next
A list is a starting point. The next step for sales teams is finding the right person at each store, covered in how to find the decision makers at a Shopify brand. If you want to know when the stores on your list change their prices or launch products, point a monitor at them as in how to track competitor prices on Shopify.