Shopify is introducing a limit of 250 dev stores per Dev Dashboard organization. CLI 4.8 gives teams a way to create, inventory, inspect, and delete those stores without making every lifecycle step a dashboard task. Client transfer and collaborator stores do not count toward the limit; organizations already above it have been granted an extended limit.[1]
For a Hydrogen team, the useful change is repeatability: create a test store, connect a storefront, run the checks that need Shopify state, then deliberately retire the environment. The unhelpful response is to create a store for every branch and trust a cleanup job to guess which ones are safe to delete.
This guide separates the new store commands from Hydrogen setup, then builds a review-first cleanup workflow. It is not a claim that Shopify now provides an unattended, end-to-end preview-environment service.
The short version
- The quota is dev-store-specific. It is not a combined limit on every store an agency can access.[1]
- The commands are public in CLI 4.8. The 4.8.0 release explicitly unhides
store create devandstore delete.[9] - Non-interactive creation needs explicit inputs. Supply
--name,--organization-id, and--plan;--jsonmakes the result machine-readable.[2] - Deletion is a privileged action. Shopify limits it to the store owner, organization owner, or organization administrator.[1]
- Oxygen works on dev stores, but the URLs require a store login. This is not public staging or production hosting.[6]
- A dev store is not a client handoff store. It cannot become a production store or be transferred to a client.[7]
Use a dev store when the test needs Shopify-specific state. For a UI experiment that does not, Hydrogen's quickstart can start with Mock.shop data instead.[8] Not provisioning a store is the simplest way to avoid accumulating one.
Check the CLI and the target organization first
Shopify CLI currently requires Node.js 22.12 or higher and Git 2.28.0 or higher.[12] The examples below target CLI 4.8.0, rather than assuming whatever version happens to be installed on a developer's machine.
shopify versionshopify store create dev --helpshopify store delete --helpshopify store list --helpshopify store info --helpshopify organization list
If the new commands are missing, update to 4.8 or later using Shopify's installation or upgrade instructions.[1][12] Choose the intended organization explicitly even when the CLI could select the only organization you belong to. The list command returns stores available to the current CLI account, not a promise that every developer can see the organization's entire inventory.[4]
Do not equate a command that accepts non-interactive flags with a working unattended CI identity. Establish and test authentication and permissions separately before moving this workflow onto a runner.
Create one disposable dev store
Set ORG_ID to the numeric organization ID from your own account. The name below is a team convention, not a reserved Shopify prefix or a source of automatic expiry.
The next command creates a real dev store. Run it only in the organization intended for this test.
: "${ORG_ID:?Set the target organization ID first}"shopify store create dev \--organization-id "$ORG_ID" \--name "hydrogen-qa-example" \--plan basic \--country US \--demo-data \--json > created-store.json
The CLI supports choosing the plan, a two-letter country code, optional demo data, and an optional feature-preview handle.[2] Start without a feature preview unless that feature is the subject of the test; the dev-store documentation notes that enabling a feature preview removes access to domains.[7]
Keep the returned store identity with the test record. Do not construct its myshopify.com domain from the display name. A failed or interrupted creation also needs reconciliation before retrying: inspect the inventory rather than assuming a missing local JSON file means no store was created.
--demo-data is a starting fixture, not a copy of a merchant's production catalog. When a test needs your own product or variant fixtures, seed those as a separate, scoped step. The store bulk execute command requires stored app authentication created with store auth, and mutations are disabled unless you explicitly allow them with --allow-mutations.[13] Store creation does not remove those prerequisites.
Connect Hydrogen separately
Creating the store does not create your local Hydrogen repository, select a storefront, or pull environment variables. Shopify's Hydrogen setup guide treats project creation, linking, environment sync, and Oxygen deployment as separate steps and requires the Hydrogen channel.[8]
Inside the intended Hydrogen project, the documented sequence is:
npx shopify hydrogen linknpx shopify hydrogen env pullnpm run dev# After verifying the linked store and local behavior:npx shopify hydrogen deploy
At each prompt, select the disposable store and its intended storefront. Before deploying, check the linked store domain and credentials rather than trusting a previous link in a reused checkout. Treat pulled environment files as secrets, not build logs or committed fixtures.
The deployment command builds and deploys the storefront; it is not a dry run.[8] For dev stores, the resulting Oxygen URL always requires a store login, even though a general getting-started guide may describe deploying a publicly accessible storefront.[6] Plan reviewer access accordingly.
These stores cannot process real transactions. Use Shopify's documented test-order mechanisms, and use a client transfer store for a merchant handoff rather than trying to promote this dev store to production.[7]
Inventory before cleanup
: "${ORG_ID:?Set the target organization ID first}"shopify store list --organization-id "$ORG_ID" --json > stores.json# Set STORE_DOMAIN to the exact domain returned by Shopify.: "${STORE_DOMAIN:?Set the exact myshopify.com domain first}"shopify store info --store "$STORE_DOMAIN" --json
store info reports available metadata such as the store ID, subdomain, organization, owner, type, and plan. Some details may be omitted.[5] Missing metadata is a reason to stop and inspect, not a reason to infer that the store is disposable.
CLI 4.8.0 also supports store list --type dev; we verified that flag in its executable help, and the release notes identify the store-type filter.[9] Use it for a narrower inventory, but keep the type and ownership checks in your cleanup procedure.
A cleanup plan, not a deletion loop
There are two different 250s to keep apart. One is Shopify's organization quota. The other is the CLI 4.8.0 list implementation: it fetches one page of up to 250 newest active, accessible stores, and does not paginate through the rest.[1][14][21]
If more results exist, JSON output includes truncated: true and the CLI warns on stderr.[15] That matters especially to an organization granted an extended quota: the oldest stores you intended to inspect may be outside the returned page. Do not invent a --page flag or call a partial response a full inventory.
The list response is an object containing a stores array, with domains in .stores[].store, creation dates in .createdAt, and the selected organization in .organization.[14][15] Creation time is not last-used time. A store can be old and still be somebody's active QA environment.
The following Python 3 snippet is our review-only planner, not a Shopify command. It selects stores older than an explicit UTC cutoff and present in a manually maintained disposable-store allowlist. It never calls Shopify, invokes a shell, or deletes anything.
Create disposable-stores.txt from your own test-environment records, with one exact myshopify.com domain per line. This is an ownership/intent list, not a list of every store you can access. Keep client work and shared fixtures out. Save the snippet as cleanup-plan.py:
import jsonimport reimport sysfrom datetime import date, datetime, timezonefrom pathlib import Pathorg, cutoff_text, allowlist_path = sys.argv[1:]cutoff = date.fromisoformat(cutoff_text)domain = re.compile(r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.myshopify\.com")owned = set(Path(allowlist_path).read_text().splitlines())if not re.fullmatch(r"[0-9]+", org) or not owned or any(not domain.fullmatch(h) for h in owned):raise SystemExit("Use a numeric org ID and an explicit canonical-domain allowlist.")data = json.load(sys.stdin)if not isinstance(data, dict) or any(data.get(k) for k in ("error", "notice", "truncated")):raise SystemExit("Inventory failed, is incomplete, or needs manual review.")organization, rows = data.get("organization"), data.get("stores")if not isinstance(organization, dict) or organization.get("id") != org or not isinstance(rows, list):raise SystemExit("Missing or mismatched organization/inventory.")candidates, seen = [], set()for row in rows:if not isinstance(row, dict) or row.get("organizationId") != org or row.get("type") != "dev":raise SystemExit("Inventory must contain only dev stores in the selected organization.")host = row.get("store")if not isinstance(host, str) or not domain.fullmatch(host) or host in seen:raise SystemExit("Noncanonical or duplicate store domain: review inventory manually.")seen.add(host)created = datetime.fromisoformat(row["createdAt"].replace("Z", "+00:00"))if created.tzinfo is None:raise SystemExit("Creation timestamps must include a timezone.")if host in owned and created.astimezone(timezone.utc).date() < cutoff:candidates.append(host)# A review plan, never deletion authorization. Emit nothing until all rows validate.print(json.dumps({"review_candidates": sorted(candidates)}, indent=2))
Run it only after a successful, explicitly scoped inventory command:
: "${ORG_ID:?Set the target organization ID first}": "${CUTOFF_UTC_DATE:?Set a YYYY-MM-DD creation-date cutoff}"shopify store list --organization-id "$ORG_ID" --type dev --json > stores.json &&python3 cleanup-plan.py "$ORG_ID" "$CUTOFF_UTC_DATE" disposable-stores.txt < stores.json
The output is a JSON review list, not authorization to delete. The planner stops on errors, notices, truncation, missing organization context, non-dev rows, noncanonical domains, duplicates, or unusable timestamps. It emits no candidates until every row validates.
The CLI can return a custom host in a list row.[14] This planner deliberately refuses that case rather than appending .myshopify.com and potentially selecting a different store. Resolve the canonical identity manually. It also cannot find stores the current account cannot access, and it cannot establish whether an allowlisted store has since become important.
That is the useful limit of this example: a small plan with a human decision, not a fleet-management service or a destructive nightly cron.
Delete one approved store, then check the result
Before deleting, re-run store info and compare the exact domain, store ID, organization, and type: "dev" with your creation record. Confirm that the test has ended, nobody still needs the environment, and required fixtures or results are saved. Optional/missing fields mean investigate, not assume.[5]
Shopify requires the store owner, organization owner, or organization administrator for deletion.[1] Without --force, CLI 4.8.0 requests interactive confirmation by store domain; --json does not bypass that confirmation. Non-interactive deletion requires --force, which skips the prompt, not authentication or server permission checks.[18]
The next command really deletes the selected dev store after confirmation. Run it separately, in a terminal, only after approving that store:
: "${ORG_ID:?Set the reviewed organization ID first}": "${STORE_DOMAIN:?Set the reviewed myshopify.com domain first}"# Run only after reviewing fresh store info and approving this exact store.# Remove an inherited force environment variable; keep the confirmation prompt.env -u SHOPIFY_FLAG_FORCE shopify store delete \--organization-id "$ORG_ID" \--store "$STORE_DOMAIN" \--json
Do not infer completion from exit code 0. The 4.8.0 success envelope contains .store.deletionRequested and .store.deletionConfirmed. A request can be accepted while deletionConfirmed remains false, with a message saying deletion may finish asynchronously.[16]
The CLI waits for confirmation for up to five minutes; if it cannot confirm, treat the result as pending rather than blindly resubmitting the mutation.[16] Do not pipe or redirect this confirmation step: keep it attached to an interactive terminal. Inspect the returned JSON and, when necessary, reconcile the store state in the Dev Dashboard. Missing access or a failed later lookup alone does not prove deletion succeeded.
What this means for a Hydrogen agency
Give every disposable environment a known owner and a teardown decision. Retain long-lived shared fixtures deliberately; do not pretend the same policy fits a one-off bug reproduction and a client demonstration.
The store lifecycle and the storefront editing workflow are separate concerns. For teams that also need visual editing, Weaverse's Hydrogen themes are the next layer to evaluate. This CLI workflow does not mean Weaverse provisions Shopify stores or manages their quota.
For adjacent topics, see our Shopify CLI 4.0 migration article and CLI bulk-operations guide. Use the current command references here for the store command family; do not apply an app deploy flag migration to store delete.
Verification scope: we executed the four commands' help output using Shopify CLI 4.8.0, inspected that release's source, and ran the planner against synthetic local fixtures. We did not create or delete a live store, configure a CI identity, or deploy this workflow to Oxygen. The creation and deletion examples are source-checked instructions, not a claimed end-to-end production run.
Sources: [1] https://shopify.dev/changelog/create-and-delete-dev-stores-in-shopify-cli [2] https://shopify.dev/docs/api/shopify-cli/store/store-create-dev [4] https://shopify.dev/docs/api/shopify-cli/store/store-list [5] https://shopify.dev/docs/api/shopify-cli/store/store-info [6] https://shopify.dev/changelog/oxygen-is-now-available-on-development-stores [7] https://shopify.dev/docs/apps/build/stores/development-stores [8] https://shopify.dev/docs/storefronts/headless/hydrogen/getting-started [9] https://github.com/Shopify/cli/releases/tag/4.8.0 [12] https://shopify.dev/docs/api/shopify-cli [13] https://shopify.dev/docs/api/shopify-cli/store/store-bulk-execute [14] https://github.com/Shopify/cli/blob/5a2a917367df7074b696342b435994805185e5e3/packages/store/src/cli/services/store/list/bp-source.ts [15] https://github.com/Shopify/cli/blob/5a2a917367df7074b696342b435994805185e5e3/packages/store/src/cli/services/store/list/result.ts [16] https://github.com/Shopify/cli/blob/5a2a917367df7074b696342b435994805185e5e3/packages/store/src/cli/services/store/delete/dev.ts [18] https://github.com/Shopify/cli/blob/5a2a917367df7074b696342b435994805185e5e3/packages/store/src/cli/commands/store/delete.ts [21] https://github.com/Shopify/cli/blob/5a2a917367df7074b696342b435994805185e5e3/packages/store/src/cli/services/store/list/constants.ts



