Skip to content

List Products

The canonical admin product listing, mirroring the Bagisto admin Catalog → Products datagrid. Cursor-paginated, filterable, and sortable — this is the query behind a product-management screen.

For the product types, the two-step create flow, status versus visibleIndividually, and the per-product sub-resources, see the Products overview.

A separate slim search, adminProducts, powers the Create-Order "Add Product" modal only. It is not the listing.

Operation

OperationTypePagination
adminCatalogProductsQueryCursor (first / after)

Arguments

ArgumentTypeDescription
firstIntPage size, default 10, capped at 50.
afterStringCursor from a previous pageInfo.endCursor.
lastIntPage size when paging backwards.
beforeStringCursor from a previous pageInfo.startCursor.
product_idStringOne id ("142") or a comma-separated list ("1,2,3").
skuStringPartial SKU match.
nameStringPartial name match.
typeStringsimple, configurable, bundle, grouped, downloadable, virtual, or booking.
statusInt0 disabled, 1 enabled.
attribute_familyStringAttribute family id.
channelStringChannel code used to resolve values. Defaults to the current channel.
localeStringLocale code used to resolve name and category. Defaults to the app locale.
price_fromFloatMinimum price, inclusive.
price_toFloatMaximum price, inclusive.
sortStringColumn to sort by — see Sorting.
orderStringasc or desc. Defaults to desc.

Filters combine with AND, so each one you add narrows the result. Four of the argument names are snake_case (product_id, attribute_family, price_from, price_to) while the rest are single words — that is the literal schema, not a typo.

Node Fields

FieldTypeDescription
idID!Resource identifier in IRI form, /api/admin/catalog/products/<id>.
_idInt!Numeric product id.
skuString!Product SKU.
typeString!Product type.
parentIdStringParent product id for a configurable variant, null for a standalone product.
nameStringResolved for the requested channel and locale.
statusString"1" enabled, "" disabled.
priceStringDecimal string, four places ("3000.0000").
formattedPriceStringThe same price in the channel currency.
specialPriceStringSale price as a decimal string, null when none.
formattedSpecialPriceStringFormatted sale price, null when none.
specialPriceFromStringStart of the sale window, null unless a dated window is set.
specialPriceToStringEnd of the sale window, null unless a dated window is set.
quantityStringInventory summed across every source.
baseImageUrlStringURL of the first product image.
imagesCountStringNumber of images attached.
categoryIdStringId of the first category only, null when uncategorized.
categoryNameStringTranslated name of that first category.
channelStringChannel code used for resolution.
localeStringLocale code used for resolution.
attributeFamilyIdIntAttribute family id.
attributeFamilyNameStringAttribute family name.
urlKeyStringURL slug.
visibleIndividuallyString"1" when the product appears in listings, "" when it does not.
shortDescriptionStringShort description, HTML.
descriptionStringFull description, HTML.
metaTitleStringSEO meta title.
metaDescriptionStringSEO meta description.
metaKeywordsStringSEO meta keywords.
weightStringProduct weight.
featuredString"1" when flagged featured, "" when not.
newString"1" when flagged new, "" when not.
additionalStringType-specific extra data, null for most products.
createdAtStringISO 8601 with offset, 2026-04-19T11:56:43+05:30.
updatedAtStringISO 8601 with offset.

Four traps in these values:

  • Booleans arrive as "1" or the empty string "", never true/false. status, visibleIndividually, featured, and new are all typed String. "" is falsy in JavaScript so a plain if (node.featured) happens to work, but node.featured === false and node.status === "0" never match anything. Compare against "1", or coerce.
  • Numbers arrive as strings too. price, quantity, imagesCount, weight, and categoryId are String; only _id and attributeFamilyId are Int. Sorting a page client-side on price without casting compares "100.0000" below "60.9900".
  • categoryId and categoryName describe the first category only. A product in three categories reports one, matching what the datagrid shows. Read the full set from the categories connection on Product Detail.
  • quantity is a sum across inventory sources, not a per-source figure. For the breakdown use List Inventories.

Fields That Stay Null on the Listing

taxCategoryId, manageStock, and inStock are resolved per product and are always null here. So is every relation connection — translations, images, videos, categories, inventories, customerGroupPrices, superAttributes, variants, bundleOptions, linkedProducts, downloadableLinks, downloadableSamples, customizableOptions, attributeValues, channels, relatedProducts, upSells, and crossSells. They resolve only on adminCatalogProduct(id:).

Selecting them on the listing is not an error — you simply get nulls and empty edges, at no query cost.

Sorting

Pass sort with a column and order with a direction. The compound form sort: "name-asc" is also accepted and splits on the hyphen.

sort valueSorts by
product_idProduct id (default)
nameProduct name
skuSKU
pricePrice
quantityInventory quantity, summed across sources
statusEnabled / disabled
typeProduct type
attribute_familyAttribute family id
channelChannel code

Products with no resolved name in the requested locale sort first on an ascending name — they are not filtered out.

Cursor Pagination

first sets the page size. Pass the previous response's pageInfo.endCursor as after to advance. totalCount is the full match count across all pages, not the size of the current page.

How It Differs From adminProducts

adminCatalogProductsadminProducts
PurposeThe product listing, for catalog managementCreate-Order "Add Product" search
Row shapeEvery scalar columnNine fields
FiltersFull set including price range and attribute familyFree text, sku, type, status, categoryId
Scalar typesStrings, including the numbers and booleansReal Int, Float, and Boolean
Default status filterNone — all statuses returnedNone — all statuses returned

The scalar-type difference is the one that bites: the two queries describe the same products with different JSON types, so a shared parsing helper will break on one of them.

Search Engine

The listing always queries the database. Configuring the admin panel to use Elasticsearch for catalog search does not change this query's behaviour.

Released under the MIT License.