shopfleet
v0.1.14
Published
Multi-store Shopify CLI
Readme
Shopfleet
Command-line interface for managing Shopify stores from the terminal.
Features
Available commands and capabilities:
- multi-store configuration
- authentication against Shopify Admin GraphQL
shop infoproducts listproducts getproducts searchproducts importproducts createproducts updateproducts publishproducts variants updateproducts deleteorders listorders getorders transactionsorders cancelcustomers listcustomers getcustomers searchcustomers ordersgift-cards listgift-cards getgift-cards createpages listpages createblogs listblogs create-articlefinancial transactionsfinancial refundfinancial summaryinventory levelsinventory adjustinventory setinventory locationsmetafields listmetafields getmetafields setmetafields deletecollections listcollections getcollections productscollections updatecollections add-productspublications listfulfillment listfulfillment createfulfillment trackingdiscounts listdiscounts getdiscounts createanalytics customanalytics salesanalytics productsanalytics overview
Requirements
- Node.js 20 or higher
- An app created in Shopify Dev Dashboard and installed on each store
- Per-store credentials:
domain(*.myshopify.com, not the public domain)clientIdclientSecret
Legacy accessToken is also supported as an optional compatibility path for older apps.
Quickstart
Install the CLI globally:
npm install -g shopfleetAdd a store with Shopify Dev Dashboard credentials:
shopfleet config add main \
--domain main-store.myshopify.com \
--client-id your-client-id \
--client-secret your-client-secretSet it as the default store and verify the connection:
shopfleet config set-default main
shopfleet config list
shopfleet shop infoNotes:
--domainmust be the Shopify admin domain in the*.myshopify.comformat.- Use
--tokenonly for legacy setups that already rely on an Admin API access token. - If you do not set a default store, pass
--store <alias>to each command.
Install dependencies
npm installDevelopment
npm run dev -- --helpBuild
npm run buildStore configuration
The CLI stores configuration in:
~/.shopfleet/stores.jsonIf ~/.store-manager/stores.json exists from the previous CLI name, Shopfleet moves it automatically on first use.
Example:
{
"stores": {
"main": {
"name": "Main Store",
"domain": "main-store.myshopify.com",
"clientId": "your-client-id",
"clientSecret": "your-client-secret"
}
},
"defaultStore": "main"
}Create a custom app for a store
Starting on January 1, 2026, Shopify no longer lets you create new "legacy custom apps" from a store admin. New custom apps must be created and managed in Shopify Dev Dashboard.
For each store:
- Open the store admin.
- Go to
Settings > Apps. - Click
Develop apps. - Click
Build apps in Dev Dashboard. - In Dev Dashboard, click
Create app. - Open the
Versionstab and create the first version. - If the app is not embedded in the Shopify admin, set
App URLtohttps://shopify.dev/apps/default-app-home. - Choose a recent
Webhooks APIversion. - Add the Admin API scopes you need.
- Release the version.
- From
Home, install the app on that store. - Open the app
Settingsand copyClient IDandClient secret.
Shopfleet uses clientId and clientSecret for new apps. It requests an Admin API access_token from POST /admin/oauth/access_token with the client_credentials grant when needed. Tokens from this flow expire after 24 hours, so the CLI requests a fresh token again when required.
Recommended Admin API scopes:
write_products, read_publications, write_publications, write_orders, read_all_orders, write_customers, write_inventory, write_discounts, read_assigned_fulfillment_orders, write_assigned_fulfillment_orders, read_merchant_managed_fulfillment_orders, write_merchant_managed_fulfillment_orders, read_third_party_fulfillment_orders, write_third_party_fulfillment_orders, write_gift_cards, write_content, write_draft_orders, write_metaobject_definitions, write_metaobjects, write_online_store_navigation, read_price_rules, read_reports, read_locations, read_markets, read_themesMetafields do not use separate read_metafields or write_metafields Admin API scopes.
Access follows the owner resource:
- reading metafields requires read access to the owner resource
- writing metafields requires the same write access used to mutate the owner resource
- app-data metafields on
AppInstallationdo not require an extra OAuth scope
The recommended scope set above is already enough for the owner resources that Shopfleet currently supports. If you want to manage metafields on another Shopify resource type, add the corresponding owner resource scope for that type.
Then register the store in the CLI:
shopfleet config add main --domain main-store.myshopify.com --client-id your-client-id --client-secret your-client-secretNotes:
--domainmust be the Shopify admin domain in the*.myshopify.comformat.clientIdandclientSecretare the values from the appSettingspage in Dev Dashboard.- Legacy
accessTokensupport remains available only for older apps that already use that model.
First commands
shopfleet config add main --domain main-store.myshopify.com --client-id xxx --client-secret yyy
shopfleet config list
shopfleet shop info
shopfleet products list --limit 10
shopfleet products search "corona"
shopfleet products get gid://shopify/Product/1234567890
shopfleet products get 1234567890
shopfleet products get paso-macarena-miniatura --handle
shopfleet products import ./products.json --dry-run
shopfleet products create --title "Test product" --status draft
shopfleet products update 1234567890 --status unlisted
shopfleet products update 1234567890 --category sg-4-17-2-17
shopfleet publications list
shopfleet products publish 1234567890 --publication 987654321
shopfleet products variants update 1234567890 --sku MINI-001 --price 39.95
shopfleet products delete 1234567890 --force
shopfleet orders list --limit 10
shopfleet orders get 1234567890 --format table
shopfleet orders transactions 1234567890
shopfleet orders cancel 1234567890 --reason customer --refund-method original --force
shopfleet customers list --limit 10
shopfleet customers search [email protected]
shopfleet customers get 1234567890 --format table
shopfleet customers orders 1234567890 --limit 10
shopfleet gift-cards list --limit 10
shopfleet gift-cards get 1234567890 --format table
shopfleet gift-cards create --value 25
shopfleet pages list --limit 10
shopfleet pages create --title "About us" --body "<p>Who we are</p>"
shopfleet blogs list --limit 10
shopfleet blogs create-article --blog-id 1234567890 --title "Spring release" --author-name "Store Team"
shopfleet financial transactions 1234567890
shopfleet financial refund 1234567890 --line-items 987654321:1 --force
shopfleet financial summary --from 2026-03-01 --to 2026-03-14
shopfleet inventory levels --sku ABC-123
shopfleet inventory adjust --item-id 30322695 --location-id 124656943 --quantity -4
shopfleet inventory set --item-id 30322695 --location-id 124656943 --quantity 12
shopfleet inventory locations --limit 10
shopfleet metafields list --owner-id gid://shopify/Product/1234567890
shopfleet metafields get custom.material --owner-id gid://shopify/Product/1234567890
shopfleet metafields set --owner-id gid://shopify/Product/1234567890 --entry custom.material:single_line_text_field:resin
shopfleet metafields delete --owner-id gid://shopify/Product/1234567890 --identifier custom.material --force
shopfleet collections list --limit 10
shopfleet collections get 1234567890 --format table
shopfleet collections products 1234567890 --limit 10
shopfleet collections update 1234567890 --title "Holy Week 2026"
shopfleet collections add-products 1234567890 987654321
shopfleet fulfillment list --limit 10
shopfleet fulfillment create --order-id 1234567890
shopfleet fulfillment tracking 255858046 --tracking-number 1Z9999999999999999
shopfleet discounts list --limit 10
shopfleet discounts get gid://shopify/DiscountNode/1234567890 --format table
shopfleet discounts create --title "Spring 10" --code SPRING10 --starts 2026-03-14 --percentage 10
shopfleet analytics custom --query "FROM sales SHOW total_sales DURING last_month"
shopfleet analytics sales --during last_week --timeseries day --compare-to previous_period
shopfleet analytics products --during last_month --limit 10
shopfleet analytics overview --during last_month --format jsonAPI version
The default version is 2026-01. You can override it with:
SHOPIFY_API_VERSION=2026-01 shopfleet shop infoProducts
products list supports simple filters:
shopfleet products list --vendor Pichardo --type Pasito --status active
shopfleet products list --category sg-4-17-2-17
shopfleet products list --query 'tag:"miniatura" status:active' --sort updated-atproducts search uses Shopify's default search and sorts by relevance.
products get returns the basic product fields by default, including the current Shopify taxonomy category when one is assigned. Add --include-media to include descriptionHtml, up to 10 product images as URLs plus metadata, and detailed variant rows with inventoryItemId values.
Import a small product batch
products import creates a small batch of products from one JSON manifest. It supports top-level product fields, public HTTPS images, and optional product options and variants. Every product requires an explicit unique handle containing lowercase letters, numbers, and single hyphens.
Example products.json (also available at examples/products-import.json):
{
"products": [
{
"title": "Winter hat",
"handle": "winter-hat",
"descriptionHtml": "<p>Warm wool hat.</p>",
"vendor": "Pichardo",
"productType": "Hat",
"category": "sg-4-17-2-17",
"status": "draft",
"tags": ["winter", "hat"],
"seo": {
"title": "Winter hat",
"description": "Warm wool hat for winter."
},
"images": [
{
"url": "https://cdn.example.com/grey-hat.jpg",
"alt": "Grey winter hat",
"filename": "grey-hat.jpg"
},
{
"url": "https://cdn.example.com/black-hat.jpg",
"alt": "Black winter hat"
}
],
"options": [
{
"name": "Color",
"values": ["Grey", "Black"]
}
],
"variants": [
{
"optionValues": { "Color": "Grey" },
"sku": "HAT-GREY",
"price": "39.95",
"imageUrl": "https://cdn.example.com/grey-hat.jpg"
},
{
"optionValues": { "Color": "Black" },
"sku": "HAT-BLACK",
"price": "39.95",
"imageUrl": "https://cdn.example.com/black-hat.jpg"
}
]
}
]
}Validate locally before contacting Shopify:
shopfleet products import ./products.json --dry-runThen execute the import:
shopfleet products import ./products.jsonThe full manifest is validated before the first Shopify write. The command also checks every handle before creating anything and aborts when one already exists. Use --skip-existing to leave existing products unchanged and create only the missing handles, which makes a partially completed import safe to resume.
Images must be available from public HTTPS URLs; local syntax validation doesn't confirm that Shopify can download them, and Shopify processes accepted URLs asynchronously after product creation. Local file uploads are not supported. Options and variants must either both be omitted or both describe the complete variant structure. Variant imageUrl values must match an entry in the product images list. The command accepts at most three options and 250 images, values, tags, or variants per product.
Imports use two concurrent Shopify requests by default. Use --concurrency 1 through --concurrency 5 to change that bound. Products default to draft, and importing does not publish them to a sales channel. Use products publish explicitly after review.
Write commands are also available:
shopfleet products create --title "Test product" --vendor Pichardo --type Accesorio --tags test,cli
shopfleet products create --title "Test product" --category sg-4-17-2-17
shopfleet products create --title "Test product" --seo-title "Buy Test product online" --seo-description "Short search snippet"
shopfleet products update 1234567890 --category sg-4-17-2-17
shopfleet products update 1234567890 --clear-category
shopfleet products update my-product --handle --status draft --new-handle my-updated-product
shopfleet products update 1234567890 --status unlisted
shopfleet products update 1234567890 --seo-title "New SEO title" --seo-description "Updated search snippet"
shopfleet products publish 1234567890 --publication 987654321
shopfleet products variants update 1234567890 --sku MINI-001 --price 39.95 --compare-at-price 49.95
shopfleet products variants update 1234567890 --tracked false --requires-shipping false --cost 12.50
shopfleet products variants update 1234567890 --metafield custom.material:single_line_text_field:resin
shopfleet products delete my-updated-product --handle --forceFor category-aware workflows, --category accepts either a taxonomy category GID or a raw taxonomy category ID such as sg-4-17-2-17. Use --clear-category to remove the current category, and --delete-conflicting-metafields when Shopify requires constrained metafields to be cleared during a category change.
Product write commands support top-level product fields, including optional --seo-title, --seo-description, and taxonomy category assignment.
Product status accepts active, draft, archived, and unlisted. An unlisted product stays directly addressable without appearing in storefront search, collections, recommendations, or sitemap output.
Use products publish to make an active product available on one publication. The command accepts a product GID or numeric ID, or a handle with --handle, and requires a publication GID or numeric ID from publications list.
Use products variants update for variant pricing, barcode, tax fields, metafields, and linked inventory item metadata such as SKU, tracked, shipping, cost, and origin codes.
Inventory quantity changes live under the dedicated inventory command group.
Publications
publications list returns the sales channel publications visible to the configured Shopify app, including each Shopify-assigned publication name, GID, and available catalog metadata. Use the human-readable name to identify a channel such as Online Store, then pass its publication GID to products publish.
shopfleet publications list
shopfleet publications list --format json
shopfleet products publish 1234567890 --publication gid://shopify/Publication/987654321The Shopify app requires read_publications to list publications and write_publications to publish products. Publication catalog metadata also requires product read access, which the recommended write_products scope includes. If these scopes are added to an existing app version, release the version and approve the updated access on the store before using the commands.
Metafields
metafields provides generic metafield management for any Shopify owner resource GID that your app can access.
Examples:
shopfleet metafields list --owner-id gid://shopify/Product/1234567890
shopfleet metafields get custom.material --owner-id gid://shopify/Product/1234567890
shopfleet metafields set --owner-id gid://shopify/ProductVariant/1234567890 --entry custom.release_year:number_integer:2026
shopfleet metafields set --current-app-installation --entry app_config.feature_tier:single_line_text_field:premium
shopfleet metafields delete --owner-id gid://shopify/Product/1234567890 --identifier custom.material --forceNotes:
--owner-idexpects a Shopify owner GID such asgid://shopify/Product/1234567890.--current-app-installationresolves the currentAppInstallationowner automatically for app-data metafields.getanddeleteusenamespace.key.setusesnamespace.key:type:value. The value is always sent as a string. For JSON metafields, pass a serialized JSON string.- Shopify allows up to 25 metafields per
metafieldsSetormetafieldsDeleterequest.
Orders
orders list supports simple filters:
shopfleet orders list --financial-status paid --fulfillment-status unfulfilled
shopfleet orders list --from 2026-03-01 --to 2026-03-14 --sort processed-at --reverseorders get accepts a Shopify order GID or numeric order ID.
orders transactions returns the transaction history for the target order.
orders cancel is implemented with explicit safety guards:
shopfleet orders cancel 1234567890 --force
shopfleet orders cancel 1234567890 --reason customer --refund-method original --notify-customer --forceAnalytics
Analytics is implemented on top of ShopifyQL through Admin GraphQL. It is read-only.
Available commands:
shopfleet analytics custom --query "FROM sales SHOW total_sales DURING last_month"
shopfleet analytics sales --during last_week --timeseries day --compare-to previous_period
shopfleet analytics products --during last_month --group-by product_title,product_vendor --limit 10
shopfleet analytics overview --during last_month --format jsonJSON output is normalized for machines and includes:
- the executed query
- returned columns
- returned rows
- parse errors
- metadata about dataset, row count, and store
Customers
customers list supports raw Shopify customer search syntax:
shopfleet customers list --query "state:enabled" --sort updated-at
shopfleet customers list --query "tag:VIP" --reversecustomers search builds a search query over first name, last name, email, and phone.
customers get accepts a Shopify customer GID or numeric customer ID.
customers orders reads the customer's orders connection directly.
shopfleet customers search maria
shopfleet customers get gid://shopify/Customer/1234567890
shopfleet customers orders 1234567890 --sort processed-atGift Cards
gift-cards list supports raw Shopify gift card search syntax:
shopfleet gift-cards list --query "status:enabled balance_status:partial"
shopfleet gift-cards list --query "last_characters:1234" --format jsongift-cards get accepts a Shopify gift card GID or numeric gift card ID.
gift-cards create creates a new gift card and returns the generated code in the response.
shopfleet gift-cards get gid://shopify/GiftCard/1234567890
shopfleet gift-cards create --value 25
shopfleet gift-cards create --value 100 --recipient-email [email protected] --recipient-message "Happy birthday" --notifyNotes:
--expiresexpectsYYYY-MM-DD.--recipient-emailmust match an existing Shopify customer email because Shopify recipient data uses customer IDs.- Notifications are disabled by default. Pass
--notifyto let Shopify send them.
Content
pages list supports raw Shopify page search syntax:
shopfleet pages list --query "title:about" --sort updated-at
shopfleet pages list --query "published_status:published" --format jsonpages create creates an online store page with a small explicit field set:
shopfleet pages create --title "About us"
shopfleet pages create --title "Shipping policy" --handle shipping-policy --body "<p>Ships in 24h</p>"
shopfleet pages create --title "Coming soon" --hiddenblogs list supports raw Shopify blog search syntax:
shopfleet blogs list --query "title:news" --sort handle
shopfleet blogs list --query "handle:journal" --format jsonblogs create-article creates an article inside an existing blog:
shopfleet blogs create-article --blog-id 1234567890 --title "Spring release" --author-name "Store Team"
shopfleet blogs create-article --blog-id gid://shopify/Blog/1234567890 --title "Launch day" --author-name "Store Team" --tags launch,news
shopfleet blogs create-article --blog-id 1234567890 --title "Coming soon" --author-name "Store Team" --hiddenNotes:
--blog-idexpects a Shopify blog GID or numeric blog ID returned byblogs list.--publish-dateexpects a full ISO 8601 date-time.- Do not combine
--hiddenwith--publish-date.
Inventory
inventory levels lists inventory quantities per inventory item and location:
shopfleet inventory levels --limit 20
shopfleet inventory levels --sku ABC-123 --name available
shopfleet inventory levels --item-id 30322695 --location-id 124656943 --format jsoninventory levels accepts an inventory item GID or numeric inventory item ID with --item-id.
--location-id accepts a location GID or numeric location ID and narrows each inventory item to that location.
--name reads one inventory quantity state at a time and defaults to available.
inventory adjust applies a signed delta at one location:
shopfleet inventory adjust --item-id 30322695 --location-id 124656943 --quantity -4
shopfleet inventory adjust --item-id gid://shopify/InventoryItem/30322695 --location-id gid://shopify/Location/124656943 --quantity 10 --reference gid://shopfleet/InventoryAdjustment/2026-03-14-001inventory adjust expects an inventory item GID or numeric ID plus a location GID or numeric ID.
--quantity is a signed delta, not an absolute quantity.
Shopify requires --ledger-document-uri when --name is not available.
inventory set writes an absolute quantity at one location:
shopfleet inventory set --item-id 30322695 --location-id 124656943 --quantity 12
shopfleet inventory set --item-id gid://shopify/InventoryItem/30322695 --location-id gid://shopify/Location/124656943 --quantity 8 --change-from 10inventory set expects an inventory item GID or numeric ID plus a location GID or numeric ID.
--quantity is the target quantity, not a delta.
--change-from is optional but recommended when coordinating concurrent stock updates.
inventory locations lists stock locations:
shopfleet inventory locations --limit 20
shopfleet inventory locations --query 'name:warehouse' --include-inactiveLocations are active-only by default unless --include-inactive is set.
Fulfillment
fulfillment list reads Shopify fulfillment orders directly:
shopfleet fulfillment list --limit 20
shopfleet fulfillment list --status open --sort updated-at
shopfleet fulfillment list --query 'request_status:unsubmitted' --include-closedfulfillment create resolves fulfillment orders from one order and creates a fulfillment for the remaining quantities:
shopfleet fulfillment create --order-id 1234567890
shopfleet fulfillment create --order-id 1234567890 --tracking-number 1Z9999999999999999 --carrier UPS
shopfleet fulfillment create --order-id 1234567890 --fulfillment-order-id 987654321 --line-items 445529754:1,445529755:2fulfillment create expects an order GID or numeric order ID in --order-id.
--fulfillment-order-id expects a fulfillment order GID or numeric ID and is useful when Shopify splits the order across multiple open fulfillment orders.
--line-items expects fulfillment order line item IDs, not order line item IDs. When --line-items is omitted, the CLI attempts to fulfill all remaining quantities on the selected fulfillment order targets.
fulfillment tracking updates tracking data for an existing fulfillment:
shopfleet fulfillment tracking 255858046 --tracking-number 1Z9999999999999999
shopfleet fulfillment tracking gid://shopify/Fulfillment/255858046 --tracking-url https://example.com/track/255858046 --carrier UPS --notifyfulfillment tracking expects a fulfillment GID or numeric fulfillment ID.
At least one of --tracking-number, --tracking-url, or --carrier is required.
Financial
financial transactions returns the transaction history for the target order.
financial refund creates a refund through refundCreate with explicit safety guards:
shopfleet financial refund 1234567890 --line-items 987654321:1 --force
shopfleet financial refund gid://shopify/Order/1234567890 --line-items gid://shopify/LineItem/987654321:2 --shipping-amount 6.99 --restock --notify --forcefinancial refund expects an order GID or numeric ID.
--line-items expects Shopify line item GIDs or numeric line item IDs in the form <line-item-id>:<quantity>.
Provide --line-items, --shipping-amount, or both.
financial summary reads matching orders and calculates totals locally:
shopfleet financial summary --from 2026-03-01 --to 2026-03-14
shopfleet financial summary --financial-status paid --limit 250 --format json--limit caps how many matching orders are included in the summary.
Collections
collections list supports simple filtering and sorting:
shopfleet collections list --type custom --sort updated-at --reverse
shopfleet collections list --query 'title:miniatura'collections get accepts a Shopify collection GID or numeric collection ID.
collections products lists products inside the target collection with manual pagination.
collections update edits top-level collection fields such as title, HTML description, handle, SEO fields, sort order, and template suffix.
collections add-products adds one or more products to a custom collection. It accepts collection and product GIDs or numeric IDs.
shopfleet collections update 1234567890 --title "Holy Week 2026"
shopfleet collections update 1234567890 --description "<p>Featured collection</p>"
shopfleet collections update 1234567890 --handle holy-week-2026 --redirect-new-handle
shopfleet collections update 1234567890 --seo-title "Holy Week" --seo-description "Featured collection" --template-suffix seasonal
shopfleet collections add-products 1234567890 987654321 987654322Changing the title does not change the handle automatically.
Use --redirect-new-handle only together with --handle.
This update command focuses on top-level collection fields and does not edit smart collection rules, images, or metafields.
Shopify rejects explicit product membership changes for smart collections. The add operation is atomic: if any supplied product is already a member, Shopify rejects the full request.
Discounts
discounts list supports raw Shopify discount search syntax and a small set of convenience filters:
shopfleet discounts list --status active --method code
shopfleet discounts list --query 'title:"Spring" method:automatic' --sort starts-atdiscounts get accepts a Shopify discount node GID returned by discounts list or discounts create.
Numeric discount IDs are not supported because Shopify discount node IDs are type-specific.
discounts create creates a basic discount code with a focused set of options:
shopfleet discounts create --title "Spring 10" --code SPRING10 --starts 2026-03-14 --percentage 10
shopfleet discounts create --title "VIP 20" --code VIP20 --starts 2026-03-14T09:00:00Z --ends 2026-03-31T23:59:59Z --amount 20 --once-per-customerIt creates a basic discount code that applies to all buyers and all items.
Pass exactly one of --percentage or --amount.
