shopfleet
v0.1.9
Published
Multi-store Shopify CLI
Downloads
811
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 createproducts updateproducts 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 updatefulfillment 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, 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 create --title "Test product" --status draft
shopfleet products update 1234567890 --category sg-4-17-2-17
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 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.
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 --seo-title "New SEO title" --seo-description "Updated search snippet"
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.
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.
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.
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 seasonalChanging 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.
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.
