How to

Build product listing

Build a product listing page (PLP) with facets and pagination using Geins Merchant API.

Prerequisites

  • Merchant API key
  • Known context for url or categoryAlias (optional)

Goals

  • Retrieve products with minimal fields and total count
  • Fetch facets for filtering
  • Support simple pagination using skip and take

Architecture at a glance

  • Query products → Render list + pagination controls → Apply filters → Re‑query

APIs used

  • Merchant API: https://merchantapi.geins.io/graphql

Step-by-step

Get page info (SEO + subcategories)

Use listPageInfo to fetch basic SEO and header data for the listing context.

Try it out in the GraphQL Playground using the query, headers and variables below.

Request example

query listPageInfo(
  $url: String!, 
  $channelId: String, 
  $languageId: String, 
  $marketId: String
) {
  listPageInfo(
    url: $url, 
    channelId: $channelId, 
    languageId: $languageId, 
    marketId: $marketId
  ) {
    name
    alias
    canonicalUrl
    meta { title description }
    subCategories { name alias canonicalUrl }
  }
}

Fetch products for the list (with count)

Use products with skip/take to build pagination and select minimal fields for PLP cards.

Try it out in the GraphQL Playground using the query, headers and variables below.

Request example

query products(
  $skip: Int
  $take: Int
  $url: String
  $filter: FilterInputType
  $channelId: String
  $languageId: String
  $marketId: String
) {
  products(
    skip: $skip
    take: $take
    url: $url
    filter: $filter
    channelId: $channelId
    languageId: $languageId
    marketId: $marketId
  ) {
    products {
      productId
      alias
      name
      canonicalUrl
      productImages { fileName }
      unitPrice { sellingPriceIncVatFormatted }
      brand { name }
    }
    count
  }
}

Fetch facets for filters

Retrieve facet groups via the same products operation by requesting filters. This could be done in the same query as above but is usually separated for performance reasons. Fetching of facets can often be run in the background while the initial product list is loading.

Try it out in the GraphQL Playground using the query, headers and variables below.

Request example

query productFilters(
  $url: String 
  $filter: FilterInputType 
  $channelId: String 
  $languageId: String 
  $marketId: String
) {
  products(
    url: $url 
    filter: $filter 
    channelId: $channelId
    languageId: $languageId
    marketId: $marketId
  ) {
    count
    filters {
      facets {
        filterId
        group
        label
        type
        values { _id count facetId parentId label order hidden }
      }
    }
  }
}
The channelId, languageId, and marketId arguments are optional and can be left out to use default values.

Response example

200 OK
{
  "data": {
    "products": {
      "count": 128,
      "filters": {
        "facets": [
          {
            "filterId": "brand",
            "group": null,
            "label": "Brand",
            "type": "Brand",
            "values": [
              {
                "_id": "acme",
                "count": 45,
                "facetId": "b_acme",
                "parentId": null,
                "label": "Acme",
                "order": 1,
                "hidden": false
              },
              {
                "_id": "techco",
                "count": 32,
                "facetId": "b_techco",
                "parentId": null,
                "label": "TechCo",
                "order": 2,
                "hidden": false
              }
            ]
          },
          {
            "filterId": "1_2",
            "group": "Product Attributes",
            "label": "Color",
            "type": "Parameter",
            "values": [
              {
                "_id": "p_1_2_blue",
                "count": 28,
                "facetId": "color",
                "parentId": null,
                "label": "Blue",
                "order": 1,
                "hidden": false
              },
              {
                "_id": "p_1_2_red",
                "count": 22,
                "facetId": "color",
                "parentId": null,
                "label": "Red",
                "order": 2,
                "hidden": false
              }
            ]
          }
        ]
      }
    }
  }
}

Pagination

Use skip and take parameters for pagination:

  • skip: Number of products to skip (default: 0, max: 6000)
  • take: Number of products to return (default: 20, max: 200)
  • count: Total number of matching products (use for pagination controls)

Validation

  • Products array length matches take (except when fewer remain)
  • count reflects total items for pagination logic
  • Basic fields present (name, canonicalUrl, primary image, price)
  • Facets returned when filters requested

Options

Multi‑market support

The mutation supports optional parameters for multi-market configurations:

Read more about channelId, languageId, and marketId in the how-to about using multi-market support.

Authenticated access

While authentication is not required for these queries, including a JWT bearer token in the Authorization header can provide personalized results based on the authenticated user's context, for example personalized pricing and product availability.

To include authentication, add the JWT bearer token to your request headers:

"Authorization": "Bearer {JWT_TOKEN}"
Read more about obtaining and using JWT tokens in the guide about the authentication flow.

Common pitfalls

  • Using high skip/take leads to slow pages on large catalogs
  • Forgetting to request minimal fields can bloat payloads
  • Facets update based on filter and url context — keep them in sync
Related