Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 131 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

Techdocs

Loading...

Implementing Nosto

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

APIs

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Introduction

This guide introduces you how to implement Nosto on a e-commerce store that does not yet have a dedicated plugin solution. The articles are designed as digestable step-by-step guides that walk you through how to establish the data exchange between the site and Nosto.

First its important to understand a few of the concepts before delving in to the documentation, to make sure that you as a integrator understand the different steps and tools required.

  • Nosto initializes by using a snippet of Javascript that starts the dialogue to your dedicated account.

  • Nosto depends on structured data that we extract from your site. At the bare minimum you need to go through all the steps listed under Manual Implementation - Essentials.

  • You can augment and extend the product tagging and the data collection process with the help of other articles found under this guide.

  • You can access your own account in the Nosto admin UI my.nosto.com/admin where you can enable/configure/modify features. All templating and layouts are handled within your account.

In case you have already implemented Nosto and established continuous data exchange you can find more information related to troubleshooting or setting up features at help.nosto.com.

Implementation Checklist

As a first step, please check if your e-commerce platform or software is already supported by a Nosto extension or integration. In case the platform is supported we warmly recommend to use an extension and reading the platform specific guide as this saves you some manual effort and time, since the implementation process differs a bit from the one described in this article series and practically automates all the steps.

Second, make sure that you have a Nosto account and if not, sign up on Nosto home page or contact Nosto sales.

Every Nosto account has a unique identifier known as accountID, which is required in the implementation. If you know that you have an account, but don’t know your accountID, log-in to your Nosto admin panel and learn where you can find it!

Third, if you first implement Nosto on a local device or on a development environment, bookmark this article about implementing Nosto on a test environment for later reading. No need to jump there yet, as it is also featured as the last article in this series.

Implement on your website

Choosing the implementation method

Most customers implement Nosto by installing a Nosto extension to their e-commerce platform. The extension provides a working out of the box implementation for majority of websites handling installing tagging and product updates. However, for customized PWA/SPA environments, you should follow the additional SPA/PWA guides.

Implementing by a Nosto Extension

Implement on your SPA

Implement on your PWA

Implement on your Headless

Implement on a standard e-commerce store

When implementing in SPA and PWA environments, product updates must be done via REST API. In case you are using some of the platforms that Nosto has extension for the extension takes care of the product updates.

SPA / PWA on top of a platform that Nosto has extension for

If you have implemented SPA / PWA on top a platform that Nosto has extension for you will still need to implement the frontend part using Session API. The extension will take care of the product updates, order confirmations, exchange rates and other background processes but displaying the recommendations, popups, etc. must be done using Session API.

When dynamic functionality is needed / no page reload

In case your website implement some dynamic functionality, you can use the JS API. Note that you can not mix Session API and JS API.

Manual Tagging - Essentials

To implement Nosto manually you will need to go through the following steps to ensure that the store data is captured by Nosto. The following steps will allow Nosto to gather product, cart and order data, and analyze how individual customers are interacting with this data. The implementation steps listed here are necessary for both functionalities based on crowd logic and 1-1 behavioral personalization.

If the store is built on explicitly supported platforms like Magento, Magento 2, Shopify, Prestashop or Shopware you should go through their platform specific guides instead.

Adding the Product Tagging

All product pages should contain the product tagging. The product tagging can be the entire metadata or only a small subset of it.

The product tagging is used to pass the context of the current product being viewed which in turn is used to personalise the recommendations e.g. cross-sellers, and commonly also periodically crawled by Nosto to build an index.

Note: The product tagging must be server-side rendered as the Nosto crawler does not execute Javascript.

You can tag your product pages in two different ways:

  1. This approach is the recommended way to tag your product pages. It contains the entirety of the product metadata and leverages the crawler to build a 1:1 replica of your catalog.

  2. This approach entails tagging just the product-id and requires you to leverage an API to build a 1:1 replica of your catalog. This is an advanced use-case and requires that your account-manager disables crawling for your website.

Adding the Search Tagging

Nosto utilizes tracks what a customer is searching for by reading the search query from the URL's query parameter or from the page source.

  • When the search term exists as a part of the URL's query parameters e.g. https://www.example.com?q=searchterm, Nosto can be configured to read the search term and you can skip the search term tagging.

  • When the search term exists as a part of the URL e.g. https://www.example.com/searchterm, Nosto is unable to read it from the URL and you will need to tag as a part of the page source. When you implement the tagging for the search term, remember that it is untrusted user input added as part of your page html and it should be html-encoded to prevent XSS vulnerabilities on the site.\

<div class="nosto_page_type" style="display:none" translate="no">search</div>
<div class="nosto_search_term" style="display:none" translate="no">green shoes</div>
Magento
Magento 2
Shopware
Shopify
Prestashop
BigCommerce
Salesforce
Session API
Session API
GraphQL
Manual Tagging
Manual Tagging - Essentials
Setting up your account
Adding the Nosto Script
Adding the Cart Tagging
Adding the Customer information
Adding the Product Tagging
Default Product Tagging
Basic Tagging
Adding the Category/Brand Tagging
Adding the Search Tagging
Adding the Order Tagging
Defining Nosto placements
Tagging your page types
Advanced Usage
Extending tagging with SKUs
Adding support for multi-currency
Adding support for customer group pricing
FAQ
Tagging all the metadata (Recommended):
Tagging the bare minimum:

Implement on native mobile

Implementing Nosto on a native mobile application allows a retailer to collect behavioral events similarly as within a traditional web page. This information will then be merged with Nosto data across other sources, impacting product relationships and real-time statistics across the board.

Read more about the commercial benefits of Mobile Application support here: https://www.nosto.com/products/mobile-app-personalization/

Due to the nature of the technical environment related to Native Application development, both behavioral and transactional data needs to be sent manually, and consequently all requests for personalization features needs to be tailored within the application as well.

Read more about Nosto's GraphQL API: https://docs.nosto.com/techdocs/apis/graphql-an-introduction

Read more about implementing Nosto with GraphQL on IOS and Android: https://docs.nosto.com/techdocs/apis/graphql-an-introduction/graphql-for-ios-and-android

Terminology

Action

Action in the Session API context means fetching recommendations for a specific view. For example when a visitor navigates from the front page to a product view you would most likely fetch recommendations related to a product. This would be considered as an Action. Setting the cart contents however would not be considered as action.

It's also good to keep in mind that each Action is considered as a page view in Nosto's analytics. It's recommended to perform an action only once per route change to keep the analytics consistent and comparable to traditional "static" e-commerce stores.

Route change

Route change in the context of this documentation means, as it stands, when the navigational route (url) of the application is changed. In traditional web sites this would be a (full) page load.

Other

Implement Autocomplete using the Nosto Autocomplete library

Nosto Autocomplete library is designed to simplify the implementation of Search Autocomplete functionality by providing:

  • Autocomplete products, keywords and history visualization.

  • Automatic bindings to Nosto Search API.

  • Autocomplete component state management.

  • Nosto Analytics out of the box, Google Analytics support.

  • Default Autocomplete components and templates.

  • Keyboard navigation.

The Nosto Autocomplete library is independent from the Search Templates offering which covers Search, Category Merchandising and Autocomplete.

Search Templates offers a hosted development environment based on Visual Studio Web using Preact components for the development of Search and Category Merchandising result pages, as well as Autocomplete experiences. Nosto Autocomplete covers only the Autocomplete part as an independent NPM package and provides integration into various rendering technologies such as React/Preact, Mustache and Liquid.

Frontend

Customers

Using Search Templates

Using the API

Working with Orders

Advanced Usage

Querying Search

Search GraphQL API uses different API endpoint. For more information see:

Adding the Nosto Script

To start tracking visits and content the Nosto script needs to be active on all pages within the store where the user might navigate. Replace $accountID from the code below with your own account ID and place the code within the <head> section of your sites HTML content. You can find your stores account IDs from the account list within the Nosto admin.

Note: The script and the snippet should be added as high up in the <head> portion of the page so the connection is initialised as soon as possible. As the script is flagged async, the page load isn’t delayed.

Note: This needs to exist on every page.

Alternatively to the script injection Nosto can also be used as a library dependency in your Javascript application via the following utility library

Troubleshooting Nosto script:

Once included on all pages, you can review if the site is transmitting data using the Nosto Debug Toolbar. If the debug toolbar executes and shows up on the page Nosto can track visits on the page. You can further verify your session in the Nosto admin by using the live feed under https://my.nosto.com/admin/$accountID/liveFeed

Adding the Customer information

On every page, the customer information should be tagged if the customer is logged in. If the customer isn't logged in, this but can be omitted.

The customer information is primarily used for sending personalised triggered emails and for building multi-channel experiences.

Tagging marketing permission

The new marketing-permission flag denotes whether the customer has consented to email marketing. If the marketing-permission field is omitted, we assume that the current customer has not given their consent and Nosto will refrain from sending out any personalized triggered emails.

The marketing permission is false by default but if a user has explicitly agreed to receive marketing then you can set it to true manually. In practice, this means reading and mapping the value from opt-in for marketing in your platform e.g. a consumer explicitly subscribed for marketing emails when checking out.

The marketing-permission should be included as a part of the customer tagging and should be rendered on all pages.

Tagging customer reference

The customer-reference can be leveraged to unify sessions across channels such as between online and offline. It is a unique identifier provided by you that is used in conjunction with the Nosto cookie. The customer-reference can also be used to uniquely identify users in lieu of an email address.

The customer-reference should be a long, secure and a non-guessable identifier. For example, use your internal customer-id or the customer's loyalty program identifier and use a secure hash function like an HMAC-SHA256 to hash it.

Basic Tagging

In the event that you are unable to expose the entire subset of the product tagging, you can simply tag the product-id.

Note: The product tagging must be server-side rendered as the Nosto crawler does not execute Javascript.

When the entirety of the product metadata is tagged, Nosto is able to crawl your site and build a 1:1 replica of your product catalog but in this basic example, you will need to use an alternative mechanism for synchronising your catalog with Nosto.

Note: If you do use this approach, your account-manager must disable crawling for your account. Failure to do so will result in a broken catalog replica.

Via an API

In order to keep your product catalog in Nosto up to date, you must leverage the .

Via a Feed

Nosto does not support a product feed and you must leverage the API in order to synchronise your product catalog.

Troubleshooting

Once included on all pages, you can review if the site is transmitting data using the . If you can see product attributes being picked up under "Tagging" then the product details are correctly set up. You can further verify that products are being indexed to the catalog under the Nosto admin by navigating to Tools → Products:

Translate attribute

The translate attribute is a which specifies whether the value of the element and it's Text node children should be translated. If your tagging elements are being translated by e.g. Google Translator then this is the way to opt out elements being translated by Google and possibly other vendors.

FAQ

Additionally, please refer to for further useful information!

Search returns up to 10000 documents

Due to performance optimization, the search function will calculate the total count up to 10,000. In this case the search page should display a count of 10,000+ to indicate that more than 10,000 products were found.

Filters and sorting operations are executed on all found products, even if there are more than this limit. Therefore, it's still possible to find other products if you filter or sort them. This should not affect the user experience in any way because it's unlikely that someone would actually view more than 10,000 products with a single search.

Products in category listings are in a different order as soon as I deliver results from Nosto API

As soon as you use the results delivered by Nosto API, you will see that the listings in categories have a different order than listings delivered by your native shop-system, even if you didn't set up any merchandising rules. The products are mainly delivered as indexed during the data sync, but there is no defined behaviour for this. We recommend to always set up at least one global rule before going live with Nosto category merchandising to create product listings matching your business strategies.

Further reading

See to see more detailed documentation of the library.

Product cards

Nosto recommends using shop-provided resources for rendering product cards in Nosto templates. Doing so offers:

  • Faster onboarding

  • Easier maintenance

  • Consistent styling and behavior

Below are the recommended approaches.

Custom Web Components

If your shop themes use web components, we suggest leveraging them in your Nosto templates as well. This avoids duplicating markup and logic between your shop and Nosto templates. For building web components efficiently, consider using or similar high-level frameworks.

Nosto Web Components

Nosto offers several web components designed to simplify product card integration:

  • NostoDynamicCard Renders product cards entirely on the Shopify side. Requires alternate product card templates to be available within Shopify themes.

  • NostoProduct Enhances static product card markup with interactive features such as:

    • Swatch selection

    • Add-to-cart interactions

    • Dynamic product image updates based on swatch and SKU selections

  • NostoProductCard Provides a platform-agnostic custom element that delegates rendering to a shop side templates (Handlebars and Liquid are currently supported).

Nosto's web component offering is documented

Style Reuse

If web components aren’t an option, we advise duplicating only the markup for product cards within Nosto templates while applying shop-side CSS rules to maintain consistent styling.

The Playground

Start exploring Nosto's GraphQL API on your account. GraphiQL is an in-browser IDE for exploring GraphQL. You don't need any access for using the GraphiQL Explorer.

Toy around, use the API and when you'd like API access to the endpoint for use through a library such as Apollo, please contact support.

Use the embedded GraphiQL explorer below to run queries. The GraphiQL Explorer is accessible at https://my.nosto.com/\[account-id]/graphql (please add your account ID).

Documentation

Documentation is a first-class citizen of GraphQL, and GraphiQL leverages it. The right-hand pane exists for you to explore the possible queries, mutations, fields, their types (if they’re required), the works. Even if your server doesn’t implement human-composed descriptions, you will always be able to explore the graph of possibilities.

Debugging

You don’t even have to read the documentation to discover our API. GraphiQL supports debugging as-you-type, giving hints and pointing out errors.

JSON Viewer

GraphiQL comes with a JSON viewer with all the niceties you’d expect: code folding, automatic indentation, copy support, and read-only so you won’t accidentally delete or edit.

Implement on a physical store

For multi-channel retailers tying the brick-and-mortar stores into Nosto's personalization solution allows retailers to collect all data into one. This information will then be merged with Nosto data across other sources, impacting product relationships and real-time statistics across the board. Retailers who have unified their data, can also start matching customers across different channels, and personalize every aspect of their shopping journey.

Read more about the commercial benefits of Mobile Application support here:

For iOS & Android

GraphQL is a query language for the APIs for getting your data. It is an alternative for the REST APIs. It is not specific to a single platform and works for all type of clients including Android, iOS or the web. It stands between your server and client and helps you to query your data in a more optimized way.

Building for Android

If you would like to leverage Nosto's intelligence engine in your Android app, please see our example app and the docs.

Building for iOS

Leveraging Nosto's intelligence engine on iOS is just as easy as on Android. While an example app is yet to be provided, please read the Apollo docs.

Testing and Debugging

The GraphQL endpoints provide functionality to make it easier to test against a real account.

Ignoring Test Requests

Every operation made against the GraphQL endpoints cause your website's data to be mutated. When doing performance testing or an equivalent, it is often necessary to exclude test traffic so as to not pollute your live account.

Using the header X-Nosto-Ignore: True will cause any traffic from being recorded. Queries and Mutations will work normally but any API calls containing this header will not be archived or accrue towards the statistics.

An example of how this header can be leveraged can be found on our app.

Debugging Requests

Every operation made against our GraphQL endpoint returns a unique request identifier contained in an X-Request-Id response header.

While we ensure that the APIs are as robust as possible if you do encounter an HTTP 5XX response from the endpoint, simply log the request identifier along with the error as the unique request identifier allows our engineers to troubleshoot the issue swiftly.

Using the API
<div class="nosto_customer" style="display:none" translate="no">
  <span class="email">[email protected]</span>
  <span class="first_name">John</span>
  <span class="last_name">Doe</span>
  <span class="customer_reference">e18daf14-d715-4d77-82f2-93eceb4ae1ef</span>
  <span class="marketing_permission">false</span>
</div>
our help pages
library TypeDoc page
Lit
here
https://www.nosto.com/products/in-store-personalization/
Android Example App Code
Android Example App Docs
Apollo Android Guide
Apollo iOS Guide
Nosto's GraphQL Android Example

Scripting

The content of this page only applies to templates used within:

  • Product Recommendations

  • Onsite Content Personalization

  • Pop-Ups

Nosto campaign templates support two ways to define JavaScript script elements as part of the templates.

In the legacy mode, the script contents are evaluated in the scope of the nosto iframe and can refer to the main window via the global _targetWindow variable.

To support ES module loading in placement and popup script elements, the client script supports the usage of script[type='module'] elements in both of these contexts. This newer module mode is evaluated in the main window but uses module scope for sandboxing. To write variables to the global scope, you will need to do so explicitly by declaring fields in the window object.

For new accounts, we recommend the use of ES module scripts. For older accounts with existing templates, the legacy script mode works as well, but interaction with the main window is a bit more verbose.

The differences between the two modes are summarized here:

Legacy scripts

  • Syntax: <script>...</script>

  • Loaded into the nosto iframe sandbox

  • Access to the site window happens via the _targetWindow variable

  • Helper modules in the nosto window namespace are available without a prefix

Module scripts

  • Syntax: <script type="module">...</script>

  • Loaded as sandboxed modules into the site window

  • Site window contents are directly available, e.g., jQuery

  • Helper modules in the nosto window namespace are available via the nosto. prefix

Examples

<script>
  _targetWindow.jQuery(
</script>

becomes

<script type="module">
  jQuery(
</script>

Additionally, module scripts support:

Import syntax

<script type="module">
  import { createApp } from 'https://unpkg.com/petite-vue?module'
  createApp().mount()
</script>

Top-level await

<script type="module">
  const response = await fetch('http://www.acme.com/myapi/myresource')
  // Do something with the response
</script>

Lightweight sandboxing

<script type="module">
  // Config is not written to the window namespace but scoped to the script tag
  const config = {
    key: "738209438"
  }
</script>

Further reading

JavaScript modules

Defining Nosto placements

You can define placements for Nosto to use via a code block called div-elements. Each element marks a location in your site where Nosto can hook into and expose onsite content.

Here is an example of a <div> tag on the site:

<div class="nosto_element" id="frontpage-nosto-1" translate="no"></div>

The class needs to always reference nosto_element so Nosto understands that this element is available for onsite content placement. However the id frontpage-nosto-1 is flexible but requires that each unique element-id has a matching placement defined in Nosto's admin dashboard in order to expose campaigns.

Here is an example of a page with multiple <div> elements:

// Three separate <divs> after another on a page

<div class="nosto_element" id="frontpage-nosto-1" translate="no"></div>
<div class="nosto_element" id="frontpage-nosto-2" translate="no"></div>
<div class="nosto_element" id="frontpage-nosto-3" translate="no"></div>

// You can also add the class and id to an element you are already using for other purposes

<div class="sidebar nosto_element" id="nosto-sidebar" translate="no">

   <h1>Hello World!</h1>
   <h2>This is the sidebar</h2>

   // You can also nest nosto elements within other wrapper elements

   <div class="nosto_element" id="nosto-sidebar-nested-1"></div>

</div>

Using Queries

The query methods allow you to fetch basic recommender data and does not require any sessions. Query operations are light and do not give you the benefit of personalization but can be used to fulfill simple use cases e.g. an in-store display that always shows the top 10 viewed products.

_The given example simply fetches products related to a given search term aliased as q_related and also fetches the most viewed products within the last week.

curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  recos (preview: false, image: VERSION_7_200_200) {
    hot_now: toplist(hours: 168, sort: VIEWS, params: {
      minProducts: 1
      maxProducts: 10
    }) {
      primary {
        name
        productId
      }
    }
    q_related: search(term: "black", params: {
      minProducts: 1
      maxProducts: 10
    }) {
      primary {
        name
        productId
      }
    }
  }
}
EOF

Updating Identities

Mutations can be used to update the email identities in Nosto. An "identity" is the personal information associated with an email address.

Upserting Identities

The upsertIdentity mutation allows you to upsert the details of an identity. The given example updates the customer attributes for the email [email protected] and requests the details of all the attributes of the identity.

If the identity for [email protected] does not exist, a new identity will be created.

Note: If a specified attribute already exists on that identity, it will be overwritten.

curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
mutation {
  upsertIdentity(identity: {
    email: "[email protected]",
    attributes: [
      {name: "loyalty-tier", value: "Gold"},
      {name: "shoe-size", value: "42"},
    ]
  }) {
    email,
    attributes {
      name,
      value
    }
  }
}
EOF

What can identity attributes be used for?

The attributes associated with an identity can be used to segment users. This works similarly to how the attributes can be leveraged when importing them via a CSV.

Deleting Identities

There is currently no way to delete an identity.

Template customization

This section describes template customization tools and best practices for the following Nosto products:

  • Product Recommendations

  • Onsite Content Personalization

  • Pop-Ups

These three products share the same templating technology and use Velocity based templates which are injected into the page via Nosto's client script.

In addition to Velocity-based server-side templating, we also have templating usage in other products:

  • Search Templates

  • Nosto Autocomplete

Web Components

For template customization we recommend the usage of both our own web component offering and third party tooling such as:

  • Swiper A modern touch slider used for creating responsive, mobile-friendly carousels. It offers smooth transitions, extensive configuration options, and high performance. Find more details at Swiper Homepage.

  • unpic A lightweight, on-demand image optimization library that delivers optimized images with lazy loading. It helps improve performance and user experience by automatically adjusting image sizes. Visit the unpic Homepage for additional information.

  • shoelace A collection of professionally designed, accessible, and customizable web components. It makes it easy to build modern web interfaces with consistent styling. Learn more on the shoelace Homepage.

Querying Products

List Products

Querying products gives access to Nosto's product catalogues current state. It can be useful for backend integration and verification purposes. It is not meant for online use purposes as it doesn't include any concepts related to user sessions or attribution, for those refer to GraphQL For Headless

curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  products(
    limit: 5
    offset: 0
    sort: {field: PRICE, reverse:true},
    filter: {categories: "shoes"}
  ) {
    products {
      productId
      url
      price
      categories
    }
  }
}
EOF

The maximum number of products that can be paged over is capped at 10000. If you need to get around this limitation, we recommend adding more restrictive filters to narrow down the result set.

Query by Product ID

curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  product(id: "5358") {
    productId
    name
    url
    price
    listPrice
    imageUrl
    attributes {
      key
      value
    }
    skus {
      name
      price
      listPrice
      availability
      imageUrl
      url
    }
  }
}
EOF

Tagging your page types

The page-type tagging enables Nosto to trigger actions, such as showing popups, depending upon a page type. Tagging the page types is optional but without the page-type tagging, you will not be able to avail the use of page type based triggers.

Here is a list of all the valid page types:

  • The home page of your store should be tagged as front.

  • All category pages should be tagged as category.

  • All product pages should be tagged as product.

  • The shopping cart page should be tagged as cart .

  • The checkout page, where order information is filled, should be tagged as checkout.

  • The order confirmation page should be tagged as order.

  • The search results page should be tagged as search.

  • All no-found pages should be tagged as notfound.

  • Other pages should be tagged as other.

 <div class="nosto_page_type" style="display:none" translate="no">product</div>

Session API

A single-page application (SPA) is a web application or web site that interacts with the user by dynamically rewriting the current page rather than loading entire new pages from a server. This approach avoids interruption of the user experience between successive pages, making the application behave more like a desktop application. In a SPA, either all necessary code – HTML, JavaScript, and CSS – is retrieved with a single page load,[1] or the appropriate resources are dynamically loaded and added to the page as necessary, usually in response to user actions. The page does not reload at any point in the process, nor does control transfer to another page, although the location hash or the HTML5 History API can be used to provide the perception and navigability of separate logical pages in the application.[2] Interaction with the single page application often involves dynamic communication with the web server behind the scenes.

If you have a website, that is built atop JavaScript frameworks, such as AngularJS, Ember.js, ExtJS, Knockout.js, Meteor.js, React or Vue.js, your site has likely adopted SPA principles.

Important: You cannot mix JS API and Session API. You need to use either or. If you are not sure which one to use, please contact Nosto's support.

Note: The examples in the documentation are written in ES5 and leverage advanced browser features such as Promises.

GraphQL

GraphQL is a query language for APIs. What does this mean for you? Unlike regular SOAP or REST APIs, GraphQL gives you the ultimate flexibility in being able to specify in your API requests specifically what data you need and get back exactly that.

As a query language, it provides you with a lot of flexibility that most normal APIs will not. Without needing to recreate endpoints, you can provide developers with the same functionality as a bulk endpoint. Your queries will be cleaner and easier to understand by combining multiple queries into one request.

What’s the difference between GraphQL API and the regular API?

The regular API is very well structured and specifically defined. The endpoints have their set requests and responses and that’s what you get whether or not that matches your usage pattern. GraphQL lets you control all of this so that the way you consume the data matches exactly what you need.

This is both a pro and a con. If your use case does not require all of the data, GraphQL can speed up your requests as we do less work on the server-side to fulfill those requests. Conversely, if you need all of the data in one request, your requests could slow down as we do more work to fulfill these requests.

Should I use GraphQL over the regular API?

If you would like to access our intelligence engine, you must use GraphQL. The legacy REST APIs are primarily used for sending orders, products and exchange rates.

Limitations

Geo Segments:

Geo segments rely on the ability to determine the geographical location of the end customer. This typically requires a client-side script that can access the user's IP address. In case of a GraphQL only implementation, this should still be possible if the GraphQL calls are made directly from the end user to Nosto. If the calls are being proxied to a backend server and subsequently forwarded to Nosto, this will essentially hide the end-user's IP from the request and instead, send the backend server IP to Nosto, making it impossible to determine the geolocation of the end user.

Starting points

Each new Nosto account comes with three base recommendation templates to customize.

Default

The Default template has the following features:

  • Recommended products in a grid

  • Alternate image on hover

  • Ribbons for new, most viewed, and top-selling products

  • Highlighting of discounts

  • Add to cart functionality

Carousel

The Carousel template extends the base template with a Swiper-based carousel to cycle between the recommended products. The library dependency is loaded via a script module, but a locally available version of the library can be used as well.

  • Carousel implementation via Swiper

  • Swiper is loaded via CDN URL into script module scope

  • Swiper default styles are injected into the DOM

  • Navigation module is loaded, and navigation buttons are provided in the DOM

  • Basic mobile breakpoints are provided

Swatches

The Swatches template extends the base template with SKU selection-aware product cards with swatches for color and size selection.

  • Color and size swatch rendering

  • NostoProduct and NostoSkuOptions web components to maintain swatch selection state and abstract the add-to-cart logic away

  • Web components library is loaded via CDN URL into script module scope

  • Product image is updated based on color swatch selection

  • Add to cart button becomes visible when color and size values have been chosen

Redacting customer data

This endpoint is used for redacting all personal data associated with an email. All requests to these endpoints are asynchronous and simply enqueue the email address for redaction. It may take up to 24 hours for the redaction process to complete. If the email address is found, a notification email (informing you about the redaction) will be sent once the process has completed.

Token

This endpoint requires an Email token.

Usage

curl -v -X DELETE https://api.nosto.com/v1/customers/redact/[email protected] \
--user :WI0j2oN7TgG42tlblX3yzOQ5xvCYc2oYj9eWg79lghVq8R0nKQXlVE9wvihBUFOw

Updating Categories

Mutations can be used to update the category listing in Nosto. The upsertCategories mutation allows you to update one or more category at one go. If the category doesn't already exist, a new one is created.

A category can have the following fields:

  • id The category identifier. If a category with this id doesn't already exist, a new one is created.

  • name The displayed name for the category.

  • urlPath The path that can be used to generate the URL of the category listing.

  • available If the category is visible on the store. This can be set to false to soft delete the category in an upsert.

Some stores support hierarchical categories i.e. a category may have parent and child categories. The following fields can be used for hierarchical categories:

  • parentId The identifier of the parent category.

  • fullName The name of every category in the hierarchy.

curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
mutation {
  upsertCategories(categories: [{
    id: "123",
    name: "Shoes",
    parentId: "456",
    urlPath: "/categories/mens/shoes",
    fullName: "/Mens/Shoes",
    available: true
  }]) {
    categoryResult {
      errors {
        field
        message
      }
      category {
        id
        name
        parentId
        urlPath
        fullName
        available
      }
    }
  }
}
EOF

Initiating data takeouts

This endpoint is used for initiating a personal-data takeout request. All requests to this endpoint are asynchronous and simply enqueue the email address for takeout. It may take up to 24 hours for the takeout process to complete. If the email address is found, a notification email (informing you about the takeout request) will be sent once the process has completed.

Token

This endpoint requires an Email token.

Usage

curl -v -X POST https://api.nosto.com/v1/customers/takeout/[email protected] \
--user :WI0j2oN7TgG42tlblX3yzOQ5xvCYc2oYj9eWg79lghVq8R0nKQXlVE9wvihBUFOw

Toggling marketing consent

This endpoint is used for toggling the marketing permission for an email. The marketing permission for an email is normally gathered via the customer tagging.

This endpoint is only intended for use when the consent needs to be programmatically managed and should be a considered an advanced use case.

Token

This endpoint requires an Email token.

Usage

Granting consent

curl -v -X POST https://api.nosto.com/api/v1/customers/consent/[email protected]/true \
--user :WI0j2oN7TgG42tlblX3yzOQ5xvCYc2oYj9eWg79lghVq8R0nKQXlVE9wvihBUFOw

Revoking consent

curl -v -X POST https://api.nosto.com/api/v1/customers/consent/[email protected]/false \
--user :WI0j2oN7TgG42tlblX3yzOQ5xvCYc2oYj9eWg79lghVq8R0nKQXlVE9wvihBUFOw

GraphQL: Updating Order Statuses

If you would like to update the order-status for a given order, you can do so using the following request.

mutation {
  updateStatus(number: "ORD102-33", params: {
    orderStatus: "fraud"
    paymentProvider: "klarna"
    statusDate: "2011-12-03T10:17:30"
  }) {
    number
    statuses {
      date
      orderStatus
      paymentProvider
    }
  }
}

FAQ

The nosto parameter allows Nosto to attribute clicks on content and recommendations. In the event, you'd like to omit the parameter, you'll need to manually send the product-view event. You can do so by executing the following snippet.

nostojs(api => {
   var request = api.createRecommendationRequest()
     .addEvent('vp', "product-id", "placement-id")
     .loadRecommendations();
});

How the attribution is tracked will be entirely dependent upon your implementation.

Products

<script type="text/javascript">
    (function(){var name="nostojs";window[name]=window[name]||function(cb){(window[name].q=window[name].q||[]).push(cb);};})();
</script>
<script src="https://connect.nosto.com/include/$accountID" async></script>
Nosto Js
Nosto debug toolbar
<div class="nosto_page_type" style="display:none" translate="no">product</div>
<div class="nosto_product nosto_basic" style="display:none" translate="no"> 
  <span class="product_id">Canoe123</span>
</div>
Products API
Nosto Debug Toolbar
https://my.nosto.com/admin/$accountID/campaigns/products/list
HTML5 standard attribute
Nosto debug toolbar / products
Nosto product catalogue
live-feed-product-view
screen shot 2018-08-26 at 12 06 07

Adding the Category/Brand Tagging

Nosto utilizes meta tags to track what category or brand a certain visitor is viewing or what page type the currently viewed page is. These values are then used for dynamic filtering for categories and brands applied through the Nosto admin UI or exposure of certain pop-up campaigns for page types.

The category tagging should be exposed whenever a user is viewing a certain category.

<div class="nosto_page_type" style="display:none" translate="no">category</div>
<div class="nosto_category" style="display:none" translate="no">/Mens/Jackets/Ski Jackets</div>

The brand tagging should be exposed whenever a user is viewing a certain brand or vendor.

<div class="nosto_page_type" style="display:none" translate="no">category</div>
<div class="nosto_category" style="display:none" translate="no">Acme</div>

Tagging the categories

Categories must always be delimited by a slash. For example, /Home/Accessories is a valid category while Home > Accessories is not.

Faceting by other attributes

With Nosto you can also expose other attributes that should be used for category/brand page filtering. For example when a user clicks on a certain color, only products with that certain color attribute should be exposed by both the category list, and Nosto Onsite Recommendations. Available values correspond to custom fields tagged as part of the Product Tagging.

<span class="nosto_tag" style="display:none" translate="no">color: Red</span>
<span class="nosto_tag" style="display:none" translate="no">gender: Mens</span>

Tagging the current page type

Page type tagging should be exposed whenever a user is interacting with a page so Nosto understands what kind of page this is.

<div class="nosto_page_type" style="display:none" translate="no">category</div>

Page type is optional and used mainly for triggering popups and also to understand what kind of page the user is currently interacting with. The page type must always be lowercase and the accepted values for page type are: front, category, produc, cart, order, search and notfound.

Troubleshooting

Once included on all pages, you can review if the site is transmitting data using the Nosto Debug Toolbar. If you can see order contents being picked up under "Tagging" → "Category" then the category and page type tagging are correctly set up in the source code.

Nosto debug category

Translate attribute

The translate attribute is a HTML5 standard attribute which specifies whether the value of the element and it's Text node children should be translated. If your tagging elements are being translated by e.g. Google Translator then this is the way to opt out elements being translated by Google and possibly other vendors.

Using Search Templates

Why use Search Templates?

Search Templates allow you to add a search function to your website quickly and easily without the need to use an API. You can customize the design of your search pages and autocomplete boxes to match your brand's look and feel. This saves you a lot of time compared to implementing search functionality through an API.

Get Started

To get started with Search Templates on your website, navigate to the Search tab under 'On-site' within the Nosto UI. This is where everything for Search can be configured and controlled, including designing the templates for search pages and autocomplete dropdowns, as well as search analytics, query rules and other settings. Synonyms for search queries can also be configured here.

To begin implementing Search, navigate to the Templates tab under Search, and Click on “Open Code Editor”.

Nosto Admin UI > Search

You will then be redirected to the Code Editor window, where you can see and edit all project files.

Code Editor

Search Templates ship with a library called @nosto/preact that contains functionality to interact with the Nosto Search product. API documentation for the library is available here

Project structure

Project structure has the following requirements:

  • index.js - this is application entry point that is used when building project. When building project it will recursively scan this file for imports.

  • build/ - this directory stores build output that will be used when deploying project.

Saving, testing & deploying

After saving changes (CTRL + S) build should be triggered and bundled code should be uploaded to CDN. You can preview final result on your website and deploy it when ready.

Styling

The content of this page only applies to templates used within:

  • Product Recommendations

  • Onsite Content Personalization

  • Pop-Ups

Styling the recommendations is generally quite straightforward. Just add a style block to the template and use CSS to style the recommendation elements as you would style any HTML content.

Encapsulating styles

While the basic styling of the recommendations works great, it can be problematic if there are multiple recommendation elements on the same page. If styles from different recommendations share the same class names, the last element on the page will override the styles of the previous recommendations.

You can use the $divId variable to print out the current placement ID in the template. Using the variable, you can form CSS selectors that start with the ID of the placement, which limits the scope of the CSS to only the current campaign. You can also target styles to the campaign directly by using the ID selector.

Nested CSS

We recommend the use of Nested CSS for scoped styling of campaign templates due to its more compact syntax and wide browser support.

As most of the recommendation template styles should be scoped to a specific slot, it is common to see scoping structures like this:

which can be expressed like this using nested CSS:

To make CSS Nesting in placement and popup templates also available for older browsers that don’t support this feature, the client script provides a polyfill for this. To use the nesting polyfill, you will need to provide the attribute nested on a style element.

Example conversion:

becomes the following with divId as nosto-product1:

The transpilation will be applied in debug mode for all browsers and in normal mode for browsers that don’t support CSS Nesting.

Further reading

Discontinuing Products

While Nosto's crawler attempts to keep its copy of your catalog as fresh as possible, there are scenarios where we may not be able to update all the information as quickly as needed.

In these scenarios, we recommend that you implement our Discontinue API which allows you to push all your product identifiers to discontinue them and have the changed instantly reflect across our entire engine.

Here's an example of a Curl request.

How can I get an API token?

You can request an API token (API_PRODUCTS) by getting in touch with our support personnel. Once the token has been granted, you will be able to find it listed in the

How many items can I update at a time?

The Discontinue API takes an array of product metadata and has no hard limit on the number of items that you can specify and is only limited by the maximum size of the payload of 2 MB.

How often can I discontinue products?

You can discontinue products as often as you need.

When should I make an API call?

You should make an API call when any product is discontinued and removed from your catalog. If a product has simply gone out of stock, we recommend making an API to update the product with the correct availability.

Is there any additional benefit of using the Discontinue API?

While Nosto crawls your site and automatically discontinues products that are no longer available, it may lag behind when your site has a large catalog. In these environments, it is recommended to use the Discontinue API to immediately discontinue the product.

Querying Orders

You can query orders using the GraphQL orders endpoint. So long as you are able to specify the order number or an external order reference, you will be able to query the order.

Setting up

Setting up your account

You must use a valid domain for your website. If you are creating a test account and running your store locally, you must use a valid TLD as using localhost is not supported.

You cannot use the following domains as they are reserved by the IANA.

  • .test

  • .example

  • .localhost

  • .invalid

We recommend using a valid TLD that is aliased to localhost for testing purposes. You will need to edit your operating-system dependent hosts file to add an alias for the domain you are using.

Setting up the catalog sync

While Nosto crawls sites to replicate the product catalog, it is unable to do so on SPAs. In order to synchronise your product catalog with Nosto, you'll need to to keep the Nosto catalog in sync.

Note: This step must be completed prior to proceeding with the implementation. Without this step, it will be troublesome to preview and debug the recommendations.

Including the script

To start tracking visits and content the Nosto script needs to be active on all pages within the store. Replace the account-id in the snippet below with your own account-id and place the code within the <head> section of your DOM. You can find your account-id from the admin.

The JS comprises of three parts - the first is the "stub" (which allows API usage prior to the script being loaded), the second is the "autoload" configuration (which prevents Nosto from automatically initiating once loaded.) and finally the Nosto script is loaded.

Note: The script and the snippet should be added as high up in the <head> portion of the page so the connection is initialised as soon as possible. As the script is flagged async, the page load isn’t delayed.

Note: This needs to exist on every page.

Alternatively to the script injection Nosto can also be used as a library dependency in your Javascript application via the following utility library

Installation

You can install the Nosto Autocomplete library via npm:

The Nosto Autocomplete library can be imported and used in various ways, depending on your preferred framework or template language. Some of the supported import methods include:

Framework
Import Statement

Choose the import method that aligns with your project's requirements and technology stack.

❗Do not combine multiple imports as it will fetch multiple bundles.❗

Blacklisting Customers

This endpoint is used for blacklisting email addresses from receiving Nosto's triggered emails such as cart-abandonment emails, order-follow emails, and we-miss-you emails.

Having the corresponding email address in our system prior to blacklisting or unblacklisting is not a prerequisite. If the specified email record does not exist in our system, it will be created.

Token

This endpoint requires an Email token.

Blacklisting an email

You can specify multiple email addresses separated by a newline.

Unblacklisting an email

You can specify multiple email addresses separated by a newline.

Recrawling Products

While Nosto's crawler attempts to keep its copy of your catalog as fresh as possible, there are scenarios where we may not be able to update all the information as quickly as needed.

In these scenarios, we recommend that you implement our lightweight Recrawl API which allows you programmatically instruct our crawler to reindex a given URL.

For example, if you add a discount of -10% to all the products in your "Shirts" category, you can use the Recrawl API to recrawl all the given URLs and update the prices.

Here's an example of a Curl request.

How can I get an API token?

You can request an API token (API_PRODUCTS) by getting in touch with our support personnel. Once the token has been granted, you will be able to find it listed in the

How many items can I recrawl at a time?

The Recrawl API takes an array of product ids and URLs and has no hard limit on the number of items that you can specify.

How often can I invoke a recrawl?

You can recrawl as often as you need but bear in mind that every recrawl adds an extra page load to your server.

Querying Segments

You can query all the configured segments using the GraphQL Segments endpoint. You are able to query all the names and identifiers of the segments.

Sample Output

Updating Rates

When using multi-currency, this endpoint is used to update the exchange rates for your account. When new rates are sent via this endpoint, the changes will reflect instantly on your store as the product prices are multiplied with the provided rates in real-time.

Note: This endpoint should only be used when using multi-currency. Please refer to our multi-currency guide prior to using this endpoint. Incorrect usage of these endpoints will result in a total outage of your personalization setup.

Token

This endpoint requires a Rates token.

Usage

How can I get an API token?

You can request an API token (API_PRODUCTS) by getting in touch with our support personnel. Once the token has been granted, you will be able to find it listed in the

How often should I send exchange-rates?

You can send exchange rates as often as you like but at the bare minimum, the exchange rates should be sent when they are changed.

curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  order(id: "M2_2") {
    number
    reference
    items {
      productId
      skuId
      unitPrice
      priceCurrencyCode
      quantity
    }
    recos(preview: true, image: VERSION_1_170_170) {
      related(params: {
        minProducts: 1,
        maxProducts: 5
      }) {
        primary {
          productId
          name
          price
        }
      }
    }
  }
}
EOF
npm install @nosto/autocomplete

Base

import { autocomplete } from "@nosto/autocomplete"

Mustache

import { autocomplete, fromMustacheTemplate, defaultMustacheTemplate } from "@nosto/autocomplete/mustache"

Liquid

import { autocomplete, fromLiquidTemplate, defaultLiquidTemplate } from "@nosto/autocomplete/liquid"

Preact

import { autocomplete, Autocomplete } from "@nosto/autocomplete/preact"

React

import { autocomplete, Autocomplete } from "@nosto/autocomplete/react"

curl -v -X POST https://api.nosto.com/v1/email/blacklist?op=add \
--user :WI0j2oN7TgG42tlblX3yzOQ5xvCYc2oYj9eWg79lghVq8R0nKQXlVE9wvihBUFOw -d '
[email protected]
[email protected]'
curl -v -X POST https://api.nosto.com/v1/email?op=remove \
--user :WI0j2oN7TgG42tlblX3yzOQ5xvCYc2oYj9eWg79lghVq8R0nKQXlVE9wvihBUFOw -d '
[email protected]
[email protected]'
curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  segments{
    segments{
      id,
      name
    }
  }
}
EOF
{
  "data": {
    "segments": {
      "segments": [
        {
          "id": "5a497a000000000000000001",
          "name": "First-Time Visitors"
        },
        {
          "id": "5b71f1500000000000000006",
          "name": "Returning Visitors"
        },
        {
          "id": "5a497a000000000000000002",
          "name": "Prospects"
        },
        {
          "id": "5a497a000000000000000003",
          "name": "First-Time Customers"
        },
        {
          "id": "5a497a000000000000000004",
          "name": "Repeat Customers"
        },
        {
          "id": "5a497a000000000000000005",
          "name": "Loyal Customers"
        },
        {
          "id": "5a497a000000000000000000",
          "name": "All Customers"
        }
      ]
    }
  }
}
#$divId .nosto-block {
  ...
}
#$divId .nosto-header {
 ...
}
#$divId .nosto-list {
 ...
}
#$divId {
  .nosto-block {
    ...
  }
  .nosto-header {
    ...
  }
  .nosto-list {
    ...
  }
}
<style nested>
#$divId {
  .wrapper {
    .blue {
      color: blue;
    }
    .red {
      color: red;
    }
  }
}
</style>
<style nested data-transpiled="true">
#nosto-product1 .wrapper .blue { color: blue; }
#nosto-product1 .wrapper .red { color: red; }
</style>
CSS Nesting
Browser support
curl -v --user :WI0j2oN7TgG42tlblX3yzOQ5xvCYc2oYj9eWg79lghVq8R0nKQXlVE9wvihBUFOw -H "Content-Type: application/json" -X POST https://api.nosto.com/v1/products/discontinue -d '
[
  '101',
  '102' 
]'
authentication tokens section in the admin.
<script type="text/javascript">
  (() => {window.nostojs=window.nostojs||(function(cb){(window.nostojs.q=window.nostojs.q||[]).push(cb);});})();
</script>
<script type="text/javascript">
  nostojs(api => api.setAutoLoad(false));
</script>
<script src="//connect.nosto.com/include/$accountID" async></script>
leverage the product API
Nosto Js
curl -v --user :WI0j2oN7TgG42tlblX3yzOQ5xvCYc2oYj9eWg79lghVq8R0nKQXlVE9wvihBUFOw -H "Content-Type: application/json" -X POST https://api.nosto.com/products/recrawl -d '{  
  "products":[  
    {  
      "product_id":"339",
      "url":"https://magento1.plugintest.nos.to/retro-chic-eyeglasses.html?___store=default"
    }
  ]
}'
authentication tokens section in the admin.
curl -v -X POST https://api.nosto.com/exchangerates \
--user :WI0j2oN7TgG42tlblX3yzOQ5xvCYc2oYj9eWg79lghVq8R0nKQXlVE9wvihBUFOw \
-H "Content-Type: application/json" -d '
{
  "rates":{
    "GBP":{
      "rate":0.77,
      "price_currency_code":"GBP"
    },
    "EUR":{
      "rate":0.91,
      "price_currency_code":"EUR"
    }
  },
  "valid_until":"2015-02-27T12:00:00Z"
}'
authentication tokens section in the admin.
Testing & deployment

Product images

This article covers how Nosto provides product images in on-site recommendations and helps you how to change the image settings if these look unsharp or otherwise bad to you. On the contrary, if recommendation images load slowly the article covers also how to adjust settings to speed up image load times and other common errors with the images.

Introduction

The implementation of Nosto on a web-site maps out an original product image used on a product detail page (PDP) to Nosto automatically. In the background, an automated service fetches the default product image and creates eight different image versions out of the image, which are stored at Nosto and made available in our Content Delivery Network (CDN).

Some image versions are processed slightly by resizing, cropping and zooming the image a bit. Applicable image versions in Nosto’s CDN are:

Version
Dimensions

1

170x170 pixels

2

100x100 pixels

3

90x70 pixels

4

50x50 pixels

5

30x30 pixels

6

100x140 pixels

7

200x200 pixels (original aspect ratio)

8

400x400 pixels (original aspect ratio)

9

750x750 pixels (original aspect ratio)

These are the the available image sizes via Nosto's CDN. Alternatively you can use original image , but in this case an image is loaded from your servers possibly affecting site load times, entirely depending on how images are hosted on your servers. Use following variable to use the original image.

$!product.imageUrl

The Nosto CDN thumbnails are accessible via

$!product.thumb(size)

where size is a number between 1-7 basd on the version mapping above.

Custom thumbnail sizes

For Shopify and Bigcommerce Nosto supports also direct access to Shopify and Bigcommerce CDN thumbnails.

These are accessed via the following pattern:

$!product.image.getThumb(300, 300)

To access the first alternate image scaled down to a 300x300 image use

$!product.alternateImages[0].getThumb(300, 300)

The custom thumbnails are served without any cropping.

It is also possible to leave width or height undefined to define only a limit for one of the dimensions:

$!product.image.getThumb(300, null)

This would scale down the image to have a max width of 300 pixels.

Troubleshooting Common Issues

Product Images Don’t Appear

If a product image doesn’t appear or if the image link is broken this usually due to error in image url tagging, erroneous url mapping or a temporarily error while fetching the images from your site to Nosto’s CDN. If you very recently implemented Nosto occasionally errors might occur and re-indexation of images is needed.

In any case, please review markup-example in tagging guide and then use the Nosto debug-toolbar and check that the image url mapped to Nosto is correct.

If image tagging is correct, there might have been an unusual error with Nosto’s image processing. In this case a manual reindex of product details is required which is launched from the Nosto admin.

In case Nosto is implemented on a site unaccessible from the Internet, this is expected behaviour. Read about implementing Nosto on test environments.

New Product Image Doesn’t Update

If you update a new image version for an old product, preferably save the image with a new file name, which typically generates a new image url for the product image as well.

Nosto doesn’t recognize images by file size or image content but by URL, hence if a url for an image is unaltered, Nosto doesn’t update image version in it’s index and uses older version of the product image in recommendations. On the contrary, new products and product images are automatically fetched and processed and no manual input is needed.

Manual re-index for an image version can be launched by enabling debug-mode and clicking Send Product Update Request-button on a product page.

For a full re-index, log in to Nosto’s admin and navigate to Tools > Products > Update products.

Unsharp Images

Typically when an image in a recommendation looks unsharp it’s simply because a small image version is upscaled to a bigger physical area in the template. Applying a bigger image version in a recommendation template will fix the issue, so essentially you simply need to adjust the styling slightly and use a bigger image version stored in Nosto’s CDN

When creating a recommendation template on Nosto Admin Panel, you can choose which image size you would like to use. Nosto doesn’t improve or manipulate pictures, however image versions 1-6 are cropped into squares, sharpened a bit, and zoomed slightly closer to details. Two versions, seven and eight, are original versions, but with aspect limitations of 200px (7) and 400px (8) respectively.

Increased Load Time or Page Size

In the event of slow recommendation load time or increased page size, most likely a Nosto recommendation either uses an original product image or too big image version, which are downscaled to fit a smaller physical area in a browser, making it an opposite scenario to the unsharp images, with different consequences.

Debug by reviewing that the template uses a suitable image version and not an original product image as high-resolution image versions can often exceed size of 1M, consequently slowing down the recommendation load time if multiple products are displayed in a recommendation.

Vertical and Horizontal Product Images

Nosto doesn’t recognize details or tell the difference between horizontal and vertical images, so be sure that most of the details of a product are in the center of the picture. Alternatively you might need to adjust the implementation slightly so that you would consistently map either horizontal or vertical version.

Updating Products

Mutations can be used to update the product catalog in Nosto. The updateProducts mutation allows you to update one or more products at a go.

Any validation errors in the product data are accessible in the response. The entire product object is accessible in the response too. In the event that a product validation error led to the product to not be updated, the response would contain the errors as well as the invalid product data.

The given example updates the product #101 and requests the details of the updated products and any associated errors.

curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
mutation {
  updateProducts(products: [
    {
      productId: "101"
      url: "http://mridang.dev.nos.to:8890/product.htm"
      imageUrl: "https://example.com/product/sku-1.jpg"
      priceCurrencyCode: "EUR"
      price: 10
      skus: [
        {
          id: "sku-1"
          name: "One"
          availability: "InStock"
          price: 100
          listPrice: 111
          imageUrl: "https://example.com/product/sku-1.jpg"
        }
      ]
    }
  ]) {
    result {
      errors {
        field
        message
      }
      data {
        productId
      }
    }
  }
}
EOF

The given example updates the price of #101 and requests the details of the updated products and any associated errors.

curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
mutation {
  updateProducts(products: [
    {
      productId: "101"
      url: "http://mridang.dev.nos.to:8890/product.htm"
      price: 10
    }
  ]) {
    result {
      errors {
        field
        message
      }
      data {
        productId
      }
    }
  }
}
EOF

Using Mutations

The mutation methods allow you to change the session on Nosto's end and request personalized recommendations. Each mutation operation allows you to update the cart and customer information, all while giving you access to recommendations for the sessions.

Any mobile experience built atop Nosto's GraphQL API should use the mutation operation as it feeds data into to the recommender systems while providing personalization data.

_The given example updates the customer's information and his current shopping cart contents sends an event that the customer is currently viewing product number 400 and requests the personalized recommendations associated with a given product for him aliased as front_page_1.

curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
mutation MySession {
  updateSession(id: "ad8f0d0e-1156-4df2-b385-10e03f8f8a44",
    params: {
      customer: {
        firstName: "John"
        lastName: "Doe"
        marketingPermission: true
        customerReference: "319330"
      }
      event: {
        type: VIEWED_PRODUCT
        target: "400"
      }
      cart: {
        items: [
          {
            productId: "100",
            skuId: "100-1",
            name: "#100",
            unitPrice: 199,
            priceCurrencyCode: "EUR",
            quantity: 1
          },
          {
            productId: "200",
            skuId: "200-1",
            name: "#200",
            unitPrice: 299,
            priceCurrencyCode: "EUR",
            quantity: 2
          },
          {
            productId: "300",
            skuId: "300-1",
            name: "#300",
            unitPrice: 399,
            priceCurrencyCode: "EUR",
            quantity: 3
          },
        ]
      }
    }) {
      id,
      recos (preview: false, image: VERSION_8_400_400) {
        front_page_1: related(productIds: ["525834092559"],
          relationship: VIEWED_TOGETHER
          params: {
            minProducts: 3,
            maxProducts: 5
        }) {
        primary {
          productId
        }
      }
    }
  }
}
EOF

Submit search

When submitting Search results through Autocomplete, submit callback is called on these events:

  • Enter key press.

  • Submit button click.

  • Keyword click.

By default submit checks if query/keyword length satisfies minQueryLength, sends Search Submit event to Nosto Analytics, and sends Search request to the Nosto Search API.

In the usual scenario, you want to render Search Results on submit, so you should override submit function:

submit: async (query, config, options) => {
    if (
        query.length >= config.minQueryLength
    ) {
        const response = await search(
            {
                query,
            },
            {
                redirect: true,
                track: config.nostoAnalytics ? "serp" : undefined,
                ...options
            }
        )
        // Do something with response. For example, update Search Engine Results Page products state.
    }
},

To disable submit pass undefined value. \

📊 Nosto Analytics (enabled by default)

Setting nostoAnalytics: true will enable Nosto Analytics tracking. Tracking results can be seen in the Nosto Dashboard under Search & Categories -> Analytics page.

❗Note: you should additionally add click events on your search results page according to Nosto Tech Docs with type: serp || category according to the results page type.❗ \

📈 Google Analytics (enabled by default)

By default we send pageview events to existing GA tag, found in shop site. To send pageview events with correct search information, a minimal configuration is needed in googleAnalytics property.

  • serpPath - Search query url parameter name

  • queryParamName - Search query url parameter name

  • enabled - Enable Google Analytics

For example, if search results URL is https://examplenostoshop.com/search-results?query=shoes, then configuration should be:

googleAnalytics: {
    serpPath: "search-results",
    queryParamName: "query",
    enabled: true
}

To disable Google Analytics, set googleAnalytics: false.

Custom logic

If you want to attach stateful logic as event handlers to your template elements, petite-vue is a useful tool. petite-vue is an alternative distribution of Vue optimized for progressive enhancement. It provides the same template syntax and reactivity mental model as standard Vue. However, it is specifically optimized for "sprinkling" a small amount of interactions on an existing HTML page rendered by a server framework.

Some rules/constraints to consider:

  • Leave the HTML rendering primarily to Velocity

  • Maintain minimal state in the Vue context, enough to satisfy your use case

  • Use a single Vue app context for the whole template

  • Make sure that the recommendation template renders correctly without the Vue layer

These rules will guide you to use petite-vue within it's intended use cases, for Progressive Enhancement, and not like Vue, a SPA framework.

Some use cases where petite-vue is useful are listed below

Event handlers to call external APIs and libraries

This example is stateless and show cases how functions can be exposed to template

<script type="module">
import { createApp } from "https://unpkg.com/petite-vue?module"

createApp({ 
  addToCart(productId) {
    // call platform specific add to cart API  
  }    
 }).mount("#$divId")
</script>

Usage from template

<span @click="addToCart('$product.productId')">Add to cart</span>

Product selection for bundle creation and related total

In this case the selection state is kept in petite-vue and hooked into the template

<script type="module">
import { createApp } from "https://unpkg.com/petite-vue?module"

const prices = {
#foreach($product in $!products)
  "$product.productId": $product.price.asNumber(),
#end
}

createApp({  
  selected: [],
  toggle(id) {
    const idx = this.selected.indexOf(id)
    if (idx > -1) {
      this.selected.splice(idx, 1)
    } else {
      this.selected.push(id)
    }
  },
  get total() {
    return this.selected
      .map(id => prices[id] ?? 0)
      .reduce((acc, curr) => acc + curr, 0)
  }
}).mount("#$divId")
</script>

template usage

<div class="product-grid">
#foreach($product in $!products)
  <div class="product" @click="toggle("$product.productId")">
    ...
  </div>
#end
</div>

<div>Total: {{ total }}

Supporting opt-out and do-not-track

You can disable Nosto for a given customer if they don't give consent for data processing or if legal reasons such as COPPA requires to disable Nosto for an individual.

This method disables the initialization of Nosto entirely, meaning that all associated Nosto features on your web store for that user are also disabled.

You will need to wrap the Nosto script in a conditional that activates only if the customer has accepted to activate Nosto. Below is an example that simply checks for the existence of a cookie called accepts_marketing but you should change the conditional to match your consent management implementation.

<script type="text/javascript">
if (document.cookie.indexOf('accepts_marketing') >= 0) {
  var head = document.getElementsByTagName('head')[0];
  var js = document.createElement("script");
  js.type = "text/javascript";
  js.src = "//connect.nosto.com/include/$accountID";
  head.appendChild(js);
}
</script>

Opting out of session tracking

Normally Nosto uses a cookie to identify browsers between their visits to the site. The purpose and usage of this cookie is very similar to how web analytics tools, such as Google Analytics, work.

You can use opting out of session tracking in case you want to enable Nosto's features in a limited manner where Nosto doesn't track browser sessions. When users opt out of session tracking, Nosto treats each request like it would be a new session that is then immediately discarded. In this mode, Nosto can still serve generic trend based recommendations, but any feature that relies on users actions over multiple requests will never activate.

Any information sent to Nosto with the requests activated by the implementation will still be processed normally by Nosto's service even when users have opted out of session tracking. Merchant should ensure necessary consent is obtained for this processing.

Nosto supports opting out of session tracking via the "Do Not Track" feature. If you leverage Nosto's "Do Not Track", Nosto will avoid setting a session identifier.

This feature does not disable cookies entirely. It only disables the 2c.cId cookie that is used for identifying browsers between their visits to the site.

You can opt-out of tracking by using the setDoNotTrack method. You must do so before making any requests to Nosto.

nostojs(api => api.visit.setDoNotTrack(true));

Having done so, you can also verify that you have opted out by using the isDoNotTrack() method

nostojs(api => console.log(api.visit.isDoNotTrack()));

Impact on features

This section lists the impact of enabled doNoTrack mode on various Nosto features.

Recommendations and Onsite Content Personalization

Normally served but taking only the current request information into account (Customer history or profile won't be available)

Segments

Taking only the current request information into account (Customer history or profile won't be available)

Popups

Fully disabled

Search and Category Merchandising (Universal)

Analytics tracking disabled

Category Merchandising (Platform)

Segmentation and A/B testing logic is disabled for CM (Platform) Only the main sorting will be applied to categories

Adding the Order Tagging

All thank-you and order-confirmation pages must have the conversion tracking markup.

The conversion metadata is used for sending personalised order-followup emails, personalise the recommendations e.g. order-related, for segmentation insights and conversion statistics.

<div class="nosto_page_type" style="display:none" translate="no">order</div>
<div class="nosto_purchase_order" style="display:none" translate="no">
    <span class="order_number">1445</span>

    <div class="buyer">
        <span class="email">[email protected]</span>
        <span class="first_name">John</span>
        <span class="last_name">Doe</span>
        <span class="marketing_permission">false</span>
    </div>

    <span class="payment_provider">checkmo</span>
    <span class="order_status_code">pending</span>

    <div class="purchased_items">
        <div class="line_item">
            <span class="product_id">Canoe123</span>
            <span class="quantity">1</span>
            <span class="name">Acme Canoe</span>
            <span class="unit_price">999.00</span>
            <span class="price_currency_code">EUR</span>
        </div>

        <div class="line_item">
            <span class="product_id">Canoe245</span>
            <span class="quantity">3</span>
            <span class="name">Acme Large Canoe</span>
            <span class="unit_price">19.00</span>
            <span class="price_currency_code">EUR</span>
        </div>
    </div>
</div>

Note: The product ID of the product tagging, cart tagging and order tagging must match. Failure to do so will lead to a mismatch in both attribution and statistics across the Nosto product.

Tagging the buyer

You can omit the buyer tagging either partially, or completely if you do not want Nosto to crawl this information. The user details are stored for possible marketing purposes and mainly observed email address is used in this context. Marketing permission is false by default but if this user has explicitly agreed to receive marketing then you can set it to true manually.

Tagging the currencies

Currencies should always be represented in the ISO-4471 three-letter format. For example, use the code USD instead of $ to represent the United States Dollar.

Tagging the payment provider

Payment provider is the payment method or provider used by a shopper to pay the purchase. Omit the payment provider detail if you do not want Nosto to crawl this information.

Tagging the order status code

Status code will be used to track the order state. Different payment providers may use different status codes.

Adding support for advanced use cases

Many ecommerce stores utilize SKU:s or "child" products that are sorted under the same "parent" product. To extend the above example with SKU support refer to this article

In cases where a product might have multiple prices in differing currencies you can also add support for multi-currency. Refer to this article

Troubleshooting order tagging:

Once included on all pages, you can review if the site is transmitting data using the Nosto Debug Toolbar. If you can see order contents being picked up under "Tagging" → "Order" then the order details are correctly set up in the source code.

You can further verify your session in the Nosto admin by using the live feed under: https://my.nosto.com/admin/$accountID/liveFeed to see if Nosto correctly picks up product view → product carted → product bought events. You can export all the order history from the store under Settings → Other → Order report under https://my.nosto.com/admin/$account/account/orders/report

Nosto debug toolbar order
live-feed-product-order

Translate attribute

The translate attribute is a HTML5 standard attribute which specifies whether the value of the element and it's Text node children should be translated. If your tagging elements are being translated by e.g. Google Translator then this is the way to opt out elements being translated by Google and possibly other vendors.

Implement Search & Categories

Nosto Search uses product and user behavior data from the Nosto Platform, so if you are implementing Nosto Search, first of all, you need to implement Nosto Platform to your website.

Your search engine will be ready after the Nosto representative enables the Search module for your account. Then you will need to integrate Nosto Search to the website.

Implementation methods

Search Templates

By using a pre-built template that can be customized to fully match a website's design using built-in code editor directly in https://my.nosto.com/. In the code editor, you can fully customize the pre-built template's JavaScript, HTML, and CSS code. Changes to the template can be implemented either by client’s developers or Nosto team. Frontend integration uses Preact and JSX templates and renders search results page dynamically in website’s frontend, so no additional integration is needed to the backend. When using Nosto services, no development is needed from the client.

API

API integration - a robust Search GraphQL API allows to implement Nosto Search into any website or app and gives complete flexibility for developers to build frontend and backend features.

JavaScript Library

For frontend integrations you can also use our JavaScript library. This library wraps the Search GraphQL API and provides its functionality in a programmatic way.

Compare implementations

Search Templates
API
JavaScript Library

Can be implemented by Nosto team

Yes

No

No

Expected time to launch live

1-3 weeks*

4-8 weeks

3-6 weeks

Headless compatible

Yes

Yes

Yes

Fully customizable frontend

Yes

Yes

Yes

Customized and managed only in Nosto dashboard

Yes

No

No

Suitable for complex use cases

Sometimes

Yes

Yes

Merchandising rules applied automatically**

Yes

Yes

Yes

Analytics

Yes

Yes***

Yes

Segmentation

Yes

Yes***

Yes

A/B testing

Yes

No

Yes

* This estimation is based on the merchant's team building the templates. When Nosto's frontend team builds templates via the Code Editor, this can take longer due to overall bandwidth from the team.

** Matching merchandising rules are applied automatically based on requested search queries, categories, and segments, without the need to request them in API requests.

*** Full functionality is only possible as a hybrid solution in combination with the JavaScript library for manual segment retrieval and analytics reporting.

If you are looking for a fast launch without much effort we recommend going with the fully customizable pre-built templates. This type of integration does not support full API access but comes complete with an out-of-the-box search result page and autocomplete templates that can easily be customized to match most website designs and integrate even advanced custom functionality.

If however you need full control over the search frontend or require complex custom functionality we recommend to go with the API or JavaScript integrations. These integrations do not provide out-of-the-box templates but provide direct access to the Search API, allowing you use the data in whatever way is required for your use cases.

Implement Autocomplete

Autocomplete provides keyword suggestions to assist users in completing their queries, supplemented by a selection of the most relevant products with the ability to see all products on the search results page.

Check out .

When integrating Autocomplete you have the option to directly access the API, or you can use our existing that provides most of the required functionality out of the box.

API Requests

Example

Query

Response

Highlight

API can return highlights indicating which parts of a keyword match the search query. This HTML can be used to render and emphasize the matching sections during display.

Redirects

Redirects are configured in the search dashboard and can be used to forward users to specific pages depending on what they type into the search field. For example, users searching for "shipping" could be directed to .

API only returns redirect url, the actual browser redirect must be implemented by the merchant on keyword selection

Analytics

Nosto Analytics

To analyze user behavior you need to implement tracking. This can be achieved using our . You need to implement the following methods with type = autocomplete:

  • to track users typing in the search field and viewing suggestions

  • to track clicks on autocomplete suggestions

Additionally, see the .

Google Analytics

Each autocomplete product click should be tracked as a search page virtual view to ensure that the Google Analytics search feature displays the correct conversion rate. For example, if you click any product when the typed query is phone it should track a virtual page view with the URL /search?q=phone (adjust the search path and query parameter to match your search page).

Since a product link click would redirect to a new page, to ensure that Google Analytics has time to send the tracking request, it's recommended to save the search query to local storage and track it on page load.

Default Product Tagging

Basic tagging

Nosto also supports multiple optional values which may enrich the usage of the service, but are not required. These span elements should be inserted into the "nosto_product" parent container.

Tagging attribute extension

Tagging the prices

Prices must always be denoted in a simple numerical form using dot as the decimal separator. For example, 1.234,45 is invalid while 1234.45 is valid.

Tagging the categories

Categories must always be delimited by a slash. For example, /Home/Accessories is a valid category while Home > Accessories is not.

Tagging the currencies

Currencies should always be represented in the ISO-4471 three-letter format. For example, use the code USD instead of $ to represent the United States Dollar.

Tagging the availability

The availability of a product is represented by InStock or http://schema.org/InStock for products that are in stock and saleable. For products that are out of stock or you don't want to be recommended, you can use OutOfStock or http://schema.org/OutOfStock

Tagging the Google Categories

The category of your item based on the Google product taxonomy. Use the schema provided by Google here ()

Tagging the Rating

The rating of a product must be represented as a number between 0.0 and 5.0. For example, a product cannot be rated 9.1. You must normalize your rating value to fit our specified range.

Tagging the Tags and/or Custom fields

The three tag fields, tags1, tags2 and tags3 are simply labels that can be used to annotate tags like discounted, limited collection or other use cases where you might want to filter your Nosto recommendations by certain product groupings.

Custom fields accept a key:value pair where the class of the attribute is the key. Common use cases are material, color or other similar unique identifiers.

Tagging the currently viewed sku

It is possible to tag also the currently viewed product sku. Typically, this would be done on a product detail page when the user chooses a specific color or size and you would like to update recommendations to highlight other products with similar attributes. Most common approach would be to implement it by calling the or the from a click-listener to send the sku information and update the recommendations. If, however, the preference is to use tagging to specify the selected sku instead, that can be done through tagging by adding a span under product with the class name selected_sku_id, for example: <span class="selected_sku_id">40822930473153</span>

Fields that are not exposable in tagging

Nosto also supports two attributes that are not crawlable through tagging. This is due to the sensitive nature of the attributes. Those are: supplier_cost and inventory_level. To send these two values to Nosto you will need to use the .

Adding support for advanced use cases

Many e-commerce stores utilize SKU:s or "child" products that are sorted under the same "parent" product. To extend the above example with SKU support refer to

In cases where a product might have multiple prices in differing currencies, you can also add support for multi-currency. Refer to

Supplier Cost / Margin Filter

If you want to use Nosto’s margin filter, you need to send supplier cost via since it's a sensitive data that you might not want to expose in the product tagging.

Troubleshooting

Once included on all pages, you can review if the site is transmitting data using the . If you can see product attributes being picked up under "Tagging" then the product details are correctly set up. You can further verify that products are being indexed to the catalog under the Nosto admin by navigating to the Catalog Explorer: https://my.nosto.com/admin/$accountID/products

Translate attribute

The translate attribute is a which specifies whether the value of the element and it's Text node children should be translated. If your tagging elements are being translated by e.g. Google Translator then this is the way to opt out elements being translated by Google and possibly other vendors.

Create Autocomplete template

Attributes

The following data-* attributes are required by the library to handle attributions (click events) for products/keywords/history items rendered in the autocomplete result:

data-ns-hit

This attribute should be used on clickable keyword, product, history list elements. This attribute handles submit keyword/history as search, redirect to product, analytics (if enabled) request.

Encode HTML content

This is specific to cases where no template language like liquid/handlebars is used and the content is rendered using plain HTML.

Make sure to HTML encode content passed to this attribute. Bacause JSON.stringify may produce result that can't be directly used in HTML especially when the content includes special characters.

For example, consider the below example

can be encoded as

Following table shows value for this attribute depending on the rendering context.

Context
Value

data-ns-remove-history

This attribute should be used to delete history entries in the autocomplete

To make an element delete a single history entry when clicked, add data-ns-remove-history={hit.item} to an element. In order to delete all history entries, add data-ns-remove-history="all" to clear button.

Starter templates

This section provides links to default startup templates for different rendering frameworks. These templates can be copied and customized as needed.

, , ,

Mustache helpers

Mustache is based on logic-less templates which can be enhanced with helpers, e.g toJson, imagePlaceholder, showListPrice in example template.

GraphQL: Placing Orders

When a user places an order onsite or offsite, you must send the conversion tracking information to Nosto.

Orders can be associated with a customer either by or by customer id. The customer id matches the Nosto cookie (this cookie is typically called 2c.cId).

Tracking orders by customer id looks like the following:

Working with recommendations

On the Order-Confirmation Page

To fetch the recommendations for the order-confirmation page, simply use the GraphQL field called forOrderPage to fetch all the recommendations for the order-confirmation page.

Adding support for customer group pricing

In this article, you will learn how to implement multi-variants in Nosto. When the implementation is complete, you will be able to display different products at different prices to different customer groups.

Note: You can only change the pricing and the availabilities using this feature.

Note: You cannot use SKUs with this feature at the time of writing.

You will need to implement the multi-variate tagging if you have any such scenarios:

  • Your store has different prices for B2B and B2C customers

  • Your store has different prices for logged-in and logged-out customers

  • Your store has different prices for loyalty customers

  • Your store has different prices and availabilities for different locations

Prior to the multi-variate implementation, ensure that the Nosto tagging is correctly in place. Some of the tagging must be slightly amended to support multi-variants.

Changes to the product tagging

The product page tagging must be amended to denote the primary variation code of the product.

For example, a retailer who has different prices for normal and loyal customers would have GENERAL as the default variation id and LOYAL as an extra variation.

An additional span tag must be placed within the product page tagging with a class name variation_id. The tag is a child element of the nosto_product element.

Note: The code in the variation_id element must remain static, regardless of the current context. For example, if a loyal customer is logged in, the variation_id field would still GENERAL and not change.

What about the prices in the cart and the order tagging?

The cart and order tagging can be left as-is but the prices must be in the customer's currently active currency. For example, a customer shopping in Swiss Francs (CHF) should have all the cart items tagged in Swiss Francs (CHF). Failure to do so will result in incorrect prices in any triggered emails such as abandoned cart or order followup.

Specifying the active variation

Once you have amended the product tagging, an additional DIV element must be added to all the other pages (including the product page itself). The tag should not be encapsulated in the nosto_product DIV tag. The information sent in the tag refers to the segment of the customer.

For example, on the site of a retailer, who has different prices for normal (GENERAL) and loyal (LOYAL) customers, if the customer is a logged in customer and is a known loyalty customer, the nosto_variation element should show LOYAL. If the customer logs out or a new customer visits, and there is no way to identify him as a loyal customer, the nosto_variation element should show GENERAL.

Enabling multi-variants from the admin

Once the tagging changed have been done and the API implemented, you need to configure and enable it from your admin panel under Settings > Other > Multi-Currency. Toggle the Use Multiple Currencies switch on and Use Exchange Rates switch off and set the variation ID of the primary currency via the input field and toggle on the exchange rates switch.

Note: Ensure that the Variation ID of the primary currency matches the value sent via the variation_id element in the product tagging.

Note: Multi-variants cannot be used in conjunction with exchange-rates based multi-currency feature. You must keep the Use Exchange Rates switch off.

You will also need to configure the price formatting for your primary and secondary currencies.

Reviewing your changes

Once you enabled multi-variants you can preview the product prices for different groups by navigating to Tools > Products and choosing a product.

You will see one or more dropdowns that contain the prices and the availability for that group.

When you have reviewed your set-up, you’re all set and ready to go live with our features. Nosto will automatically handle the different customer groups across its feature set.

REST

Nosto offers a multitude of APIs for different use cases. The APIs are not entirely RESTful but provide lightweight endpoints that expose similar usability.

All the APIs reside at https://api.nosto.com and must be accessed over HTTPS.

Note: If you happen to call the interface via HTTP using a valid API key, that API key will be invalidated immediately and a notice of the token revocation will be sent to the account owner.

Authentication

Authenticating with the API is done by using . You authenticate by using your API key as the password and the username is left empty.

You can see your API keys in the Nosto Backend under account settings. API key is always tied to a single store in your account.

Note: Keep your API key secret and delete it immediately if you think someone untrusted might have had access to it.

HTTP Header
Value

Requesting access

To get access to our APIs, please log in to your Nosto account at and contact support via chat. When you request API access, please provide following information:

  • What is the API in question?

  • What is the purpose for the API use?

  • What is the volume of requests?

  • What is the request distribution, on-demand or periodic?

Token types

You can get token values from page under your Nosto Account. Each set of endpoints are secured using different token types.

Token type
Description

Rate Limits

Nosto does not rate-limit the API usage but follows a fair-use policy. Nosto reserves the right to revoke API access for any abusive API usage patterns.

Handling Placements

Onsite features such content and recommendations are injected into what we call "placements".

Nosto requires that you specify an array of placements and will respond with the response for those specified placements. There are two types of placements and it is possible to leverage either of them when implementing Nosto on an SPA:

Static Placements

These are defined as empty hidden elements in the DOM with the class nosto_element. The identifier of the element denotes the placement identifier. An example placement is as follows:

The Div element remains hidden until Nosto injects content into it.

Dynamic Placements

These are defined in the administration panel and are CSS and URL rules to define where content is to be injected. In order to allow these dynamic placements to be usable, you should follow the guide below for managing placements automatically.

Managing Placements Automatically

When using the Session API, the placements are set through a function call setPlacements which expects an array of strings. In order to support dynamic placements setup in the Nosto backend, we recommend that instead of setting the placements array contents directly from your application, you use the following API to get a list of placements for the page:

The function scans the current page (document object) for any valid placements at the time of execution and returns them in an array suitable for the setPlacements-function. As long as it is called after the targeted elements have been rendered into the document, it will return both the static and the dynamic CSS and URL rules based placements on the page. Dynamic placements are picked up regardless of them being rendered or not. Static placements are always obtained through scanning the page. Freshly created Nosto accounts come with static placements only. ( In case you have programmatic placement codes that are not in the page document at all, you can add them to the array to combine both manual and automatic approaches. )

Here's an example call where placements are scanned automatically:

To continue with the example, let's assume the api.placements.getPlacements() returned an array with 2 placements: ['frontpage-center-1', 'frontpage-banner'] and then let's assume that we would have set up in the backend a recommendation campaign for frontpage-center-1 and a content campaign for frontpage-banner.

Here is an example of a sample response.

The response object will contain a campaigns field which has both content and recommendations under their own fields and within those the keys are the placement identifiers and the value will be an object containing the payload and parameters used for click attribution.

Rendering campaign results

By default the API returns campaigns data in JSON format. Product recommendation campaigns contain an array of recommended products and content campaigns contain an html field. To ensure correct attribution HTML campaign results should always be injected via Nosto API functions. Nosto provides a function to inject the HTML based campaign results into the right place: api.placements.injectCampaigns(). The function expects an object where the field keys are the placements to be injected and values are either a string or an object with a string field named html. The function will scan the document to find the active placements and insert the HTML to the right location. Any javascript blocks within the HTML content will be executed as well.

Here is an example of rendering campaign results.

This example assumes the implementing application has a utility function to transform products JSON into HTML to be inserted, alternatively there could also be a function that gets the product JSON and renders them itself directly.

Offloading campaign rendering and injection fully to Nosto

For HTML based campaign results we recommend to offload the campaign injection fully to Nosto. For HTML based results you can skip transforming the products JSON to HTML and instead use the recommendation templates in the Nosto backend to produce HTML. In that case you would set the response mode in the Session API to be 'HTML' and enable campaign injection via enableCampaignInjection().

Here's an example call

Testing & deployment

Testing

How to use debug toolbar to preview search

With the , you can see all the changes made to your website right away. To enable this feature, simply turn on the preview mode. After saving any changes in the code editor, you will be able to see them directly on your website.

How to use preview:

  1. Navigate to your website

  2. In the URL, append ?nostodebug=true to enable the debug toolbar

  3. The Nosto debug toolbar should open up, where you will be asked to log in

  4. Once you have logged in, enable the Preview toggle button at the bottom

  5. You should now be able to view your changes live, via the Search box

Manual testing

Before each deployment search should be manually tested to ensure that everything works correctly.

What to test?

  • Autocomplete returns results

  • Search displays results, facets with counts

  • You can select multiple facets (on the same field and different fields)

  • Sorting is working

  • Pagination is working

Test both mobile & desktop view using Chrome .

Deployment

When you finished working on search implementation & carefully tested everything using , it's time to deploy everything.

How to deploy?

  1. Navigate to the Nosto Admin UI > Search > Templates

  2. Click on Deploy latest and launch live

It can take up to 15 minutes for deployment to be visible

Rollback previous deployment

If the most recent update doesn't work properly, you have the option to revert to any previous update. Reverting won't alter the source, which means you can deploy the latest changes again simply by clicking on the main deployment button.

How to revert deployment

  1. Navigate to the Nosto Admin UI > Search > Templates

  2. Click on the desired deployment, click on ... and then on Redeploy

Disable Templates

If your latest update doesn't work and you don't have a previous working version to go back to, or if you want to completely remove the search function, you can turn off the search templates for a temporary period.

Reverting won't alter the source, which means you can deploy the latest changes again simply by clicking on the main deployment button.

How to disable templates?

  1. Navigate to the Nosto Admin UI > Search > Templates

  2. Click on the Disable Templates button.

Querying Identities

You can query identities using the GraphQL Identities endpoint. An "identity" is the personal information associated with an email address.

So long as you are able to specify the email address, you will be able to query the identity, its associated customer affinity and the personalized recommendations for that identity.

What can identity attributes be used for?

The attributes associated with an identity can be used to segment users. This works similarly to how the attributes can be leveraged .

Using Search Templates
Using the API
Using the JavaScript Library
{
  "keyword": "new year's eve",
  "_highlight": { "keyword": "new year's eve" }
}
{&quot;keyword&quot;:&quot;year's eve&quot;,&quot;_highlight&quot;:{&quot;keyword&quot;:&quot;<strong>year</strong>'s eve&quot;}}

keyword

value from response.data.search.keywords

Code example:

const { keywords } = response.data.search
const contentToRender = keywords.map(keyword => 
    `
    <div data-ns-hit="${JSON.stringify(keyword)}" ....>
        ....
        ....
    </div>
    `
)

Value example:

{
    "keyword": "midi dresses",
    "_highlight": { "keyword": "midi <strong>dress</strong>es" }
}

product

productId and url from response.data.search.products.hits

Code example:

const { hits } = response.data.search.products
const contentToRender = hits.map(({ productId, url }) => 
    `
    <div data-ns-hit="${JSON.stringify({ productId, url })}" ....>
        ....
        ....
    </div>
    `
)

Value example:

{
    "productId": 123456,
    "url": "https://example.com/products/example-product-handle"
}

history

historyEnabled & historySize config. Refer Initialization

import { fromMustacheTemplate } from '@nosto/autocomplete/mustache'

fromMustacheTemplate(template, {
    helpers: {
        toJson: function () {
            return JSON.stringify(this)
        },
    },
})
Handlebars
Mustache
Liquid
React/Preact (HTML)
mutation {
  placeOrder(by:BY_REF, id: "514421fce84abcb61bd45241", params: {
    customer: {
      firstName: "Mridang"
      lastName: "Agarwalla"
      email: "[email protected]"
      marketingPermission: false
    }
    order: {
      number: "25435"
      orderStatus: "paid"
      paymentProvider: "klarna"
      ref: "0010"
      purchasedItems: [
        {
          name: "Shoe"
          productId: "1"
          skuId: "11"
          priceCurrencyCode: "EUR"
          unitPrice: 22.43
          quantity: 1
        }
      ]
    }
  }) {
    id
  }
}
mutation {
  placeOrder(by:BY_CID, id: "5d3ef53010b4f8a24c2acf9a", params: {
    customer: {
      firstName: "Mridang"
      lastName: "Agarwalla"
      email: "[email protected]"
      marketingPermission: false
    }
    order: {
      number: "25435"
      orderStatus: "paid"
      paymentProvider: "klarna"
      ref: "0010"
      purchasedItems: [
        {
          name: "Shoe"
          productId: "1"
          skuId: "11"
          priceCurrencyCode: "EUR"
          unitPrice: 22.43
          quantity: 1
        }
      ]
    }
  }) {
    id
  }
}
mutation {
  placeOrder(by:BY_REF, id: "514421fce84abcb61bd45241", params: {
    customer: {
      firstName: "Mridang"
      lastName: "Agarwalla"
      email: "[email protected]"
      marketingPermission: false
    }
    order: {
      number: "25435"
      orderStatus: "paid"
      paymentProvider: "klarna"
      ref: "0010"
      purchasedItems: [
        {
          name: "Shoe"
          productId: "1"
          skuId: "11"
          priceCurrencyCode: "EUR"
          unitPrice: 22.43
          quantity: 1
        }
      ]
    }
  }) {
    id
    pages {
      forOrderPage(value: "25435", params: {
        imageVersion: VERSION_3_90_70
        isPreview: true
      }) {
    divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}
customer reference

Authorization

Basic :NOSTO_API_TOKEN

API_REC

A deprecated token for using the legacy recommendations API

API_PRODUCTS

A token for accessing the Products API

API_EMAIL

A token for managing customers using the Blacklist and GDPR APIs

API_APPS

A token for accessing the new GraphQL APIs

API_OMNICHANNEL

A token for accessing the new Omni-channel API

API_RATES

A token for updating rates using the Exchange Rates API

API_SETTINGS

A token for configuring your account using the Settings API

"Basic" authentication
https://my.nosto.com
authentication tokens
<div class="nosto_element" id="home-1" style="display: none;"></div>
api.placements.getPlacements()
nostojs(api => {
  api.defaultSession()
    .viewFrontPage()
    .setPlacements(api.placements.getPlacements())
    .load()
    .then(response => console.log(response.campaigns))
});
{ 
    "campaigns": {
        "content": {
            "frontpage-banner": {
                "div_id": "frontpage-banner",
                "result_id": "5fc6390c60b2ecd3cc0c2d4f",
                "html": "< Campaign html content >",
                "params": ...
            }
        },
        "recommendations": {
            "frontpage-center-1": {
                "div_id": "frontpage-center-1",
                "result_id": "frontpage-center-1",
                "products": [ /* Array of products */ ],
                "params": ...
            }
        }
    }
}
/* TODO for the application to implement */
function createProductRecsHtml(recommendations) {
  return new Promise((resolve, reject) => {
    /* TODO your code to create the HTML for each recommendation 
     * with the placements identifiers as keys, example:
     * recsHtml = { "frontpage-center-1": "HTML here" }
     */ 
    resolve(recsHtml);
  }
}

nostojs(api => {
  api.defaultSession()
    .viewFrontPage()
    .setPlacements(api.placements.getPlacements())
    .load()
    .then(response => { 
      /* Render content campaigns */ 
      api.placements.injectCampaigns(response.campaigns.content);
      
      /* Transform products JSON to HTML and render */
      createProductRecsHtml(response.campaigns.recommendations).then(recsHtml => {
        api.placements.injectCampaigns(recsHtml);  
      });
    })
});
nostojs(api => {
  api.defaultSession()
    .setResponseMode('HTML')
    .enableCampaignInjection()
    .viewFrontPage()
    .setPlacements(api.placements.getPlacements())
    .load()
});
Placements - General Article
Nosto debug toolbar
device simulation
Nosto debug toolbar
curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  identity(email:"[email protected]") {
    email,
    attributes {
      name,
      value
    }
    recos(preview: true, image: VERSION_ORIGINAL) {
      history(params: {
        minProducts: 3,
        maxProducts: 10,
        showRelatedProducts: true,
        skipProductsInCart: true,
        skipBoughtProducts: true
      }) {
        primary {
          url
          imageUrl
        }
      }
    }
  }
}
EOF
when importing them via a CSV
query {
  search(
    accountId: "YOUR_ACCOUNT_ID"
    query: "green"
    products: { size: 5 },
    keywords: { size: 5 }
  ) {
    products {
      hits {
        productId
        name
      }
      total
    }
    keywords {
      hits {
        keyword
        _redirect
        _highlight {
          keyword
        }
      }
    }
    query
  }
}
{
  "data": {
    "search": {
      "query": "green",
      "products": {
        "hits": [
          {
            "productId": "1",
            "name": "My product"
          }
        ]
        "total": 1
      },
      "keywords": {
        "hits": [
          {
            "keyword": "green",
            "_redirect": "https://example.com/green.html",
            "_highlight": {
              "keyword": "<em>green</em>"
            }
          },
          {
            "keyword": "green energy",
            "_redirect": null,
            "_highlight": {
              "keyword": "<em>green</em> energy"
          }
        ]
      }
    }
  }
}
autocomplete's look & feel guidelines
Autocomplete JavaScript library
https://example.com/shipping.html
JavaScript library
recordSearch
recordSearchClick
tracking instructions for search form submissions
Example Autocomplete

FAQ

Since Search Templates utilize the Search API under the hood, please also check the Search API FAQ

Share search templates code between multiple accounts

The functionality enables the sharing of a single instance of search templates across multiple dashboard accounts through linking. This feature proves particularly advantageous in scenarios where there are multiple websites with identical or similar characteristics that necessitate the creation of multiple dashboard accounts (such as supporting multiple languages or environments).

To link multiple accounts contact support!

How to implement some differences between websites?

Even if multiple websites share the same search templates code, we can easily have different logic based on website host or language (or other criteria).

function detectWebsite() {
    if (location.hostname === 'de.website.com') {
        return 'de'
    } else if (document.documentElement.lang === 'de') {
        return 'de'
    }
    return 'en'
}

const helloText = {
    de: 'Hallo',
    en: 'Hello'
}[detectWebsite()]

Different CSS between websites

Since it's not possible to target CSS rules by domain, it's recommended to add a special class to the page body and use it to target CSS rules.

Example JS

init({
    ...
}).then(() => {
    if (location.hostname === 'de.website.com') {
        document.body.classList.add('nosto-search-de')
    }
})

Example CSS:

.nosto-search-de .my-title {
    color: black;
}

Show specific SKU when searching by SKU color or ID

By default, the search indexes all SKUs as a single product. Therefore, even when searching by a specific SKU attribute (like SKU color or ID), the search will return the main product along with all SKUs.

However, because the search returns all SKUs, SKU selection logic can be implemented on the frontend side. This is achieved by checking each SKU to see if it contains text from the search query.

Implementation

These is an example on how to implement SKU selection:

function getSku(query, product) {
    const words = query.toLowerCase().split(/\s+/)

    return product?.skus?.find((sku) => {
        // If the query is a SKU ID, return the SKU
        if (sku.id == query) {
            return true
        }
        const color = sku?.customFields?.color?.toLowerCase()?.split(/\s+/)

        return words.some((word) => color.includes(word))
    })
}

export default ({ product }) => {
    const query = useAppStateSelector((state) => state.query.query)
    const sku = getSku(query, product)

    return <a href={sku?.url || product.url}>
        <img src={sku?.imageUrl || product.imageUrl}/>

        {sku?.name || product.name}
    </a>
}

Use more than 100 facet values

By default, the search API returns 100 values for each facet, and it's not possible to control that limit in the dashboard. However, it is possible to override the facet configuration directly from the code.

Get facet ID from the dashboard

The first step is to know the facet ID of the facet you want to overwrite. The facet ID is stored in the dashboard URL. For example, if you open the facet in the facet manager, the URL should be https://my.nosto.com/admin/shopify-123/search/settings/facetManager/6406df867f8beb629fc0dfb9. This means the facet ID is 6406df867f8beb629fc0dfb9.

Override facet settings

Once the ID is known, it's possible to override any facet setting by specifying overwrite properties in the customFacets parameter:

index.js
init({
    serpQuery: {
        products: {
            customFacets: [
                {
                    "id": "6406df867f8beb629fc0dfb9",
                    "size": 10
                }
            ]
        },
    }
})

SEO

Although search engines can understand some JavaScript-rendered code, they often miss search templates. The reason for this is that the rendering of search templates is delayed in order not to hinder the page loading speed.

However, it's still possible to achieve great SEO results while using the code editor:

  • Category pages - Ensure that the category page already returns the correct meta tags and page title according to SEO recommendations, they bring the biggest impact to the SEO. In category page search templates render only products, so it doesn't significantly impact SEO. Most search engines support structured data (the category page backend should generate these tags using original products). However it does not have any big impact to the SEO. Furthermore, search engines may not favor the discrepancy between structured data and the actual rendered products (cause search engine will see either empty page with loader or nosto search results that are different compared to original).

  • Search pages - Most search engines don't index search pages, so no optimizations are needed.

If you still have concerns regarding SEO, please consider using API integration.

GDPR

<div class="nosto_page_type" style="display:none" translate="no">product</div>
<div class="nosto_product" style="display:none" translate="no"> 
  <span class="product_id">Canoe123</span>
  <span class="name">Acme Canoe</span>
  <span class="url">https://example.com/canoe123</span>
  <span class="image_url">https://image.example.com/canoe1.jpg</span>
  <span class="availability">InStock</span>
  <span class="price">999.50</span>
  <span class="price_currency_code">USD</span>
</div>
<span class="category">/Mens/Jackets</span>
<span class="category">/Mens/Jackets/Ski Jackets</span>
<span class="brand">Acme</span>
<span class="description">This is a great product!</span>
<span class="google_category">Interior > Towels</span>
<span class="list_price">1299.00</span>
<span class="tag1">sporty</span>
<span class="tag2">new-in</span>
<span class="tag3">limited-offer</span>
<span class="tag3">add-to-cart</span>
<span class="rating_value">3.8</span>
<span class="review_count">36</span>
<span class="alternate_image_url">https://image.example.com/canoe2.jpg</span>
<span class="alternate_image_url">https://image.example.com/canoe3.jpg</span>
<span class="custom_fields">
  <span class="material">Cotton</span>
  <span class="weather">Summer</span>
</span>
https://support.google.com/merchants/answer/6324436?hl=en
Session API
JS API
Products API
this article
this article
API
Nosto Debug Toolbar
HTML5 standard attribute
Nosto debug toolbar / products
The UI of the Catalog Explorer (CE)
<div class="nosto_product" style="display: none;" translate="no">
  ...
  ...
  ...
  <!-- Variation ID for the primary currency --> 
  <span class="variation_id">GENERAL</span>
  <!-- Variation block for a secondary currency -->
  <div class="variation">
    <span class="variation_id">LOYAL</span>
    <span class="price_currency_code">EUR</span>
    <span class="price">27.00</span>
    <span class="list_price">45.19</span>
    <span class="availability">InStock</span>
  </div>
  <!-- Variation block for a secondary currency -->
  <div class="variation">
    <span class="variation_id">B2B</span>
    <span class="price_currency_code">GBP</span>
    <span class="price">24.00</span>
    <span class="list_price">41.55</span>
    <span class="availability">OutOfStock</span>
  </div>
</div>
<div class="nosto_variation" style="display: none;">GENERAL</div>

Implement Category pages

Nosto provides functionality to retrieve all products for a specific category. This is useful when you want to implement category merchandising using the same API as for Search.

API Requests

Using category ID and category path

Using the category ID is only fully supported for Shopify merchants. Others should use the category path instead to benefit from full functionality.

Provide the categoryId API parameter to fetch all products associated with that category. Additionally categoryPath should be provided for better analytics data.

Query

query {
  search(
    accountId: "YOUR_ACCOUNT_ID"
    products: {
      categoryId: "123456789",
      categoryPath: "Pants"
    }
  ) {
    products {
      hits {
        productId
        name
        url
        imageUrl
        price
      }
      total
      size
    }
  }
}

Product fields that can be requested in the hits object are documented here. All indexed fields are accessible via the API.

Using only category path

Provide the categoryPath API parameter to fetch all products associated with that category. This parameter is the same as the categories product field.

Query

query {
  search(
    accountId: "YOUR_ACCOUNT_ID"
    products: {
      categoryPath: "Pants"
    }
  ) {
    products {
      hits {
        productId
        name
        url
        imageUrl
        price
      }
      total
      size
    }
  }
}

Product fields that can be requested in the hits object are documented here. All indexed fields are accessible via the API.

Child category handling

Depending on your configuration, fetching a parent category will also include products from the child categories. For example, fetching products for the category Pants would also include products from the categories Pants -> Shorts and Pants -> Khakis.

This is an admin-only setting. Please contact your Nosto representative to adjust this setting.

Using custom filters

In some rare cases categoryId or categoryPath is not enough. In these cases custom filters can be used to build any query for category & landing pages.

Query

query {
  search(
    accountId: "YOUR_ACCOUNT_ID"
    products: {
      preFilter: [
        {
          field: "productId",
          value: [
            "2276",
            "2274"
          ]
        }
      ],
    }
  ) {
    products {
      hits {
        productId
        name
      }
      total
      size
    }
  }
}

Other features & implementation

The category page shares a lot of similarities with the search page, so please refer to the search page documentation:

Analytics

Nosto Analytics

To analyze user behavior you need to implement tracking. This can be achieved using our JavaScript library. You need to implement the following methods with type = category:

  • recordSearch to track category page visits

  • recordSearchClick to track clicks on category results

Extending tagging with SKUs

Nosto supports individual product SKUs under parent products. If you have not set up you should start there and extend the tagging if needed.

The SKU attributes should be listed on the last row of the nosto_product block that you have already implemented on the product pages.

What is a SKU?

Many e-commerce stores have a parent product with individual child products. The parent product is usually something along the lines of "Ski Jacket" whereas the SKUs would then be "Ski Jacket, Blue, Small", "Ski Jacket, Red, Medium". If your store uses SKUs you should add the following attributes to extend your product tagging.

Note: The attribute custom_fields can contain whatever unique information for individual SKUs that you can >consider helpful. Frequently used attributes would be size, color, material.

Extending the cart tagging with SKU metadata

When tagging the cart contents as outlined here, you can also tag information of the actual SKU that was added to cart. Notice the extra <span class="sku_id"> attribute.

Extending the order tagging with SKU metadata

When tagging the order contents as outlined here, you can also tag information of the actual SKU that was added to cart. Notice the extra <span class="sku_id"> attribute inside each of the purchased_items.

Validating

Once included you can review if the SKUs are picked up by using the . If you can see individual SKUs being picked up below the original product details then this is correctly set up.

You can further verify that products are being indexed to the catalogue under the Nosto admin by navigating to Tools → Products ()

FAQ

Do I need to view events for when an SKU is viewed?

No, Nosto does no recommend individual SKUs. While this is something on our roadmap, at the moment, you do not need to send any events when an SKU is selected.

For example, assume you had a product page selling a shoe. In this case, the product tagging would always point to the id of the shoe. No events should be dispatched when the customer selects a particular size such as S, M, L.

Implementing Category pages

Category pages can be rendered using search templates over existing category pages.

Configuration

To render the category page, provide additional configuration parameters to the init function in the index.js entry point file. Default configurations for categoryQuery and isCategoryPage are already provided. Custom configuration is necessary only if the default settings are not suitable for your application.

The default isCategoryPage function checks for the presence of an element in the DOM and determines if the page should be considered a category page based on its content.

Category query parameter as function

In the example above, we supply autocomplete query parameters as an object. Additionally, the categoryQuery parameter can also be supplied as a function. The function flavor can be used for building complex query parameters and provides access to other pre-defined configuration parameters. Section below shows an example of categoryQuery supplied as a function which provides the product variationId by accessing the pre-defined categoryId and categoryPath methods from the default configuration.

If you want to integrate only Categories without Search or Autocomplete, ensure that the following entries are removed or commented out:

serpComponent

historyComponent

autocompleteComponent

Handling native results

Nosto will attempt to display the original category page products in case Nosto service is unavailable or can't be reached. In addition, the original products are made available for the SEO crawlers, improving the page's ranking in the search engines. To make it possible, it's recommended to hide the original category page products instead of removing or blocking them.

The best approach is to add ns-content-hidden class name to the same element you are targeting with contentCssSelector or categoryCssSelector. This class name will be stripped away by Nosto automatically as soon as the script is initialized.

In addition, you should define CSS to hide the target element:

Detect search page

The isCategoryPage function should detect whether the current page is a category page. Its result should not be cached, as it needs to dynamically detect the page type since it can change during the app's execution. For example, if the user navigates from a category page to a search page, the function should reflect that change in page type.

Category query

The categoryQuery should generate a category page query based on the current page. It must return either categoryPath (identical to the categories field filter) or categoryId to select the current category. The default implementation extracts the categoryId and categoryPath fields from the DOM.

Category component

The category component should also be implemented. In most cases, it is the same component as the search page component, except that the search query should not be displayed.

Other features & implementation

The category page shares a lot of similarities with the search page, so please refer to the search page documentation:

Analytics

Search automatically tracks to Google Analytics & Nosto Analytics when using the SerpElement component.

Setting up your account

You must use a valid domain for your website. If you are creating a test account and running your store locally, you must use valid TLD as using localhost is not supported.

You cannot use the following domains as they are reserved by the IANA.

  • .test

  • .example

  • .localhost

  • .invalid

  • .local

  • .localdomain

  • .domain

  • .lan

  • .home

  • .corp

  • .wip

We recommend using a valid official TLD that is aliased to localhost for testing purposes. You will need to edit your operating-system dependent hosts file to add an alias for the domain you are using.

Nosto periodically crawls your website to keep the catalog data in sync and therefore if you run your webshop locally, Nosto will be unable to crawl your website. In order to overcome this, you will either need to:

  1. make it publicly available using a service such as or

  2. Use the to keep your catalog in sync

Implement Search results page
Pagekite
Ngrok
Products API

Managing Sessions

Setting the cart

The cart content must updated whenever the cart contents change. The cart contents are the 1:1 representation of the user's cart. Also note that the cart contents is only send to Nosto when you perform an action. If you want to send the cart contents to Nosto right away, you add .viewCart().update() to your call.

You may also pass null or undefined to signify that there was no change in the cart. The default state of the cart in the session is empty.

The cart information is used by the Nosto to tailor the recommendations, dispatch abandoned cart emails and fire Facebook pixel events for retargeting purposes.

nostojs(api => {
  api.defaultSession()
   .setCart({
     items: [
      {
        name: "Men's Running Shirt",
        price_currency_code: "EUR",
        product_id: "181503",
        quantity: 2,
        sku_id: "181505",
        unit_price: 123.45
      },
      {
        name: "Men's Training Shoe",
        price_currency_code: "EUR",
        product_id: "34552",
        quantity: 1,
        sku_id: "39912",
        unit_price: 999.00
      }
    ]
   })
});

or when sending the contents to Nosto right away

nostojs(api => {
  api.defaultSession()
   .setCart({
     items: [
      {
        name: "Men's Running Shirt",
        price_currency_code: "EUR",
        product_id: "181503",
        quantity: 2,
        sku_id: "181505",
        unit_price: 123.45
      },
      {
        name: "Men's Training Shoe",
        price_currency_code: "EUR",
        product_id: "34552",
        quantity: 1,
        sku_id: "39912",
        unit_price: 999.00
      }
    ]
   })
   .viewCart()
   .update()
});

Note: Passing null or undefined will prevent the cart contents from being mutated on Nosto. Passing an empty object {} will reset the cart contents.

Setting the customer

When a visitor logs in customer information should be passed. If the customer isn't logged in, this can be omitted. Similar to setting the cart contents, customer data is only sent to Nosto when an action is performed.

The customer information is primarily used for sending personalised triggered emails and for building multi-channel experiences.

nostojs(api => {
  api.defaultSession()
    .setCustomer({
      customer_reference: "b369f1235cf4f08153c560.82515936",
      email: "[email protected]",
      first_name: "Nosto",
      last_name: "Test",
      newsletter: true
    })
});

or when sending the contents to Nosto right away

nostojs(api => {
  api.defaultSession()
   .setCustomer({
      customer_reference: "b369f1235cf4f08153c560.82515936",
      email: "[email protected]",
      first_name: "Nosto",
      last_name: "Test",
      newsletter: true
    })
   .viewCart()
   .update()
});

Note:

  1. Passing null or undefined will prevent the customer information from being mutated on Nosto. Passing an empty object {} will reset the customer information.

  2. Here viewCart is only provided as an example. But the actual tracking method to be invoked will depend on customer's current page. For more information on this, please refer Tracking Events

Tagging marketing permission

The new marketing-permission flag denotes whether the customer has consented to email marketing. If the marketing-permission field is omitted, we assume that the current customer has not given their consent and Nosto will refrain from sending out any personalized triggered emails.

The marketing permission is false by default but if a user has explicitly agreed to receive marketing then you can set it to true manually. In practice, this means reading and mapping the value from opt-in for marketing in your platform e.g. a consumer explicitly subscribed for marketing emails when checking out.

The marketing-permission should be included as a part of the customer tagging and should be rendered on all pages.

Tagging customer reference

The customer-reference can be leveraged to unify sessions across channels such as between online and offline. It is a unique identifier provided by you that is used in conjunction with the Nosto cookie. The customer-reference can also be used to uniquely identify users in lieu of an email address.

The customer-reference should be a long, secure and a non-guessable identifier. For example, use your internal customer-id or the customer's loyalty program identifier and use a secure hash function like an HMAC-SHA256 to hash it.

Advanced Usage

Nosto also supports some advanced use-cases depending on how your store is currently built. You should first read through the following topics to have an understanding of how Nosto works before moving to the advanced use-cases:

  • Manual Tagging - Essentials

    • Adding the Nosto Script

    • Adding the Product Tagging

    • Adding the Category/Brand Tagging

    • Adding the Cart Tagging

    • Adding the Order Tagging

    • Adding the Customer information

The topics listed below extend the essential tagging with support for SKUs, Multi-currency and Customer group pricing.

  • Manual Tagging - Advanced

    • Extending tagging with SKUs

    • Adding support for multi-currency

    • Adding support for customer group pricing

index.js
import { init } from '@nosto/preact'
import categoryComponent from './category'

init({
    ...window.nostoTemplatesConfig,
    inputCssSelector: '#search',
    contentCssSelector: '#content', // or categoryCssSelector
    categoryComponent: categoryComponent,
    categoryCssSelector: '#MainContent',

    categoryQuery: {
      products: {
        categoryId: "1234567",
        categoryPath: "dresses",
        size: defaultConfig.serpSize,
        from: 0,
      }
    }
})
import { init } from '@nosto/preact'
import categoryComponent from './category'

init({
    ...window.nostoTemplatesConfig,
    inputCssSelector: '#search',
    contentCssSelector: '#content', // or categoryCssSelector
    categoryComponent: categoryComponent,
    categoryCssSelector: '#MainContent',

    categoryQuery: () => {
        return {
          products: {
            categoryId: this.categoryId(),
            categoryPath: this.categoryPath(),
            size: defaultConfig.serpSize,
            from: 0,
          }
        };
    }
})
css
.ns-content-hidden {
    display: none;
    /* Or other styles as needed */
}
category/index.jsx
import { useAppStateSelector } from '@nosto/preact'

export default () => {
    const { products, loading } = useAppStateSelector((state) => ({
        products: state.response.products,
        loading: state.loading,
    }))

    return (
        <div>
            {loading && <div>Loading...</div>}
            {products.total ? <div>
                {products.hits.map(hit => <div>
                    {hit.name}
                    {hit.price} 
                </div>)}
            </div> : <div>
                Category is empty
            </div>}
        </div>
    )
}
export default ({ product }) => {
    return (
        <SerpElement as="a" hit={product}>
            {product.name}
        </SerpElement>
    )
}
Implementing Search page
<div class="nosto_product" style="display:none" translate="no">
  ...
  ...
  ...
  <span class="nosto_sku">
    <span class="id">1</span>
      <span class="name">S-Orange</span>
      <span class="price">1269.00</span>
      <span class="list_price">1299.00</span>
      <span class="inventory_level">55</span>
      <span class="url">http://www.example.com/product/CANOE123#/1-size-s/13-color-orange</span>
      <span class="image_url">http://www.example.com/product/images/CANOE123-1.jpg</span>
      <span class="availability">InStock</span>
      <span class="custom_fields">
        <span class="size">S</span>
        <span class="color">Orange</span>
      </span>
  </span>

  <span class="nosto_sku">
    <span class="id">2</span>
      <span class="name">S-Blue</span>
      <span class="price">1269.00</span>
      <span class="list_price">1299.00</span>
      <span class="inventory_level">12</span>
      <span class="url">http://www.example.com/product/CANOE123#/1-size-s/14-color-blue</span>
      <span class="image_url">http://www.example.com/product/images/CANOE123-2.jpg</span>
      <span class="availability">InStock</span>
      <span class="custom_fields">
        <span class="size">S</span>
        <span class="color">Blue</span>
      </span>
  </span>

</div>
<div class="nosto_cart" style="display:none" translate="no">

    <div class="line_item">
        <span class="product_id">Canoe123</span>
        <span class="sku_id">201-1</span>
        <span class="quantity">1</span>
        <span class="name">Acme Canoe</span>
        <span class="unit_price">999.00</span>
        <span class="price_currency_code">EUR</span>
    </div>

    <div class="line_item">
        <span class="product_id">Canoe123</span>
        <span class="sku_id">201-2</span>
        <span class="quantity">1</span>
        <span class="name">Acme Canoe</span>
        <span class="unit_price">999.00</span>
        <span class="price_currency_code">EUR</span>
    </div>

    <div class="line_item">
        <span class="product_id">Canoe245</span>
        <span class="sku_id">101-1</span>
        <span class="quantity">3</span>
        <span class="name">Acme Large Canoe</span>
        <span class="unit_price">19.00</span>
        <span class="price_currency_code">EUR</span>
    </div>

</div>
<div class="nosto_purchase_order" style="display:none" translate="no">
    <span class="order_number">1445</span>

    <div class="buyer">
        <span class="email">[email protected]</span>
        <span class="first_name">John</span>
        <span class="last_name">Doe</span>
    </div>

    <div class="purchased_items">
        <div class="line_item">
            <span class="product_id">Canoe123</span>
            <span class="sku_id">101-1</span>
            <span class="quantity">1</span>
            <span class="name">Acme Canoe</span>
            <span class="unit_price">999.00</span>
            <span class="price_currency_code">EUR</span>
        </div>

        <div class="line_item">
            <span class="product_id">Canoe245</span>
            <span class="sku_id">101-1</span>
            <span class="quantity">3</span>
            <span class="name">Acme Large Canoe</span>
            <span class="unit_price">19.00</span>
            <span class="price_currency_code">EUR</span>
        </div>
    </div>
</div>
Product tagging
Nosto Debug Toolbar
https://my.nosto.com/admin/$accountID/campaigns/products/list
Sku debug toolbar

Check your setup

Step by step guide for checking that our client script is tracking events on your site.

Session view in the debug toolbar

  1. Navigate to your site in incognito mode.

  2. Load the debug toolbar by appending nostodebug=true to your store's URL e.g. https://example.com?nostodebug=true

  3. Click the login link and navigate to the session view.

Click the login button
After logging in, the session tab should be visible on the left-hand side

View Category

Navigate to a category page and verify that the View Category event is tracked with the following details:

  • Target matches the viewed category.

Viewing the "Bags" category

If the category doesn't appear in the session, these pages might help

  • Tagging: category

  • Session API: category

View Product

Navigate to a product page and verify that the View Product event is tracked with the following details:

  • Target matches the viewed product ID.

Viewing product 784735895612

If the product doesn't appear in the session, these pages might help:

  • Tagging: product

  • JS API: product

  • Session API: product

View a Recommended Product

Click on a product recommendation and verify that the View Product event is tracked with the following details:

  • Target matches the viewed product ID.

  • Ref matches the previously viewed recommendation slot ID.

Viewing product 789528674364 from recommendation slot productpage-nosto-14-high-upsell

If the product id and recommendation slot id don't appear in the session, these pages might help:

  • JS API: Attribution for recommended Products

  • JS API: Sending Add to Cart-Events

  • Session API: handling attribution

Cart

Add the product to cart and verify that the cart’s contents are correct. Ensure that the product details in the cart match the product that was previously viewed.

  • Product ID

  • Price & Currency

  • Quantity

If the cart doesn't correctly show in the session view, these pages might help:

  • Tagging: cart

  • JS API: cart

  • Session API: cart

Order

Purchase the product and verify that the Buy Product event is tracked with the following details:

  • Ref matches the recommendation slot id

  • Target matches the product id

Wait 30 minutes for the session to expire and then verify that the order shows up in the orders page of the Nosto admin UI.

Buy product 789528674364 from recommendation slot productpage-nosto-14-high-upsell.

If the order doesn't correctly show in the session view, these pages might help:

  • Tagging: order

  • Session API: order

If it’s not possible to perform a test order, the Nosto admin UI's orders page can be used to review that orders link to known products in the catalogue and that some items have click attribution towards the visible recommendations.

Measuring Performance

We’ve integrated with the User Timing API to be able to show some performance measurements of our client script. The supported measurements are:

  • nosto.get_dynamic_placements Parse the DOM and find placements on the page. Performed before a recommendations request is sent.

  • nosto.load_recommendations Send the recommendations request and handle the recommendation result. This encompasses measuring the recommendations request and nosto.inject_campaigns.

  • nosto.inject_campaigns Inject both dynamic and static campaigns into the DOM. This encompasses both nosto.inject_static_campaigns and nosto.inject_dynamic_campaigns.

  • nosto.inject_static_campaigns Inject static campaigns into the DOM.

  • nosto.inject_dynamic_campaigns Inject dynamic campaigns into the DOM.

  • nosto.evaluate_js Extract and evaluate embedded JavaScript from recommendation templates.

Lighthouse

Chrome's lighthouse tool can be opened by doing the following:

  1. Right click and select Inspect.

  2. Navigate to the Lighthouse tab.

  3. Ensure the Performance category is enabled.

  4. Click Generate Report

Once the report is generated, you can expand the “User Timing marks and measures” section under the Diagnostics section to see some basic information like how long after the page started to load the measurements start and how long each measurement took.

You can scroll back up and click the View Original Trace button to view a timeline of the measurements. You may need to expand the Timings section to view the client script’s recorded measurements. You can zoom in an out of this graph and swipe side to side to get a better view of things.

Leveraging Features

Working with recommendations

The load method returns a promise, which can be consumed to get the raw recommendation data. The raw recommendation data is an object containing the recommendation data.

Reporting correct page views - load() vs update()

Recommendation results are returned in a promise by calling either load() or update(). The difference between these two methods is:

  • load() causes the load count of the most recently tracked page view to be incremented.

  • update() does not automatically increment the page load count.

In order to achieve an accurate reach statistic for recommendations, load() should be called once per page view when fetching recommendations and all proceeding recommendation requests should use update().

Reporting correct product views - requesting product recommendations outside product detail page

Normally requesting product related recommendations happens in relation to viewing a product detail page, so a typical product related recommendation request looks like this:

The above will also add a product view event for the user. In some cases there are needs to request product related recommendations even when the user is not looking at a product. In these cases, to avoid reporting the user event, include a { trackEvents: false } flag to the load function, for example:

If for implementation specific reasons there are multiple calls for product related recommendations on the product page, only the first request should track the event and subsequent requests should have the { trackEvents: false } flag include.

Handling attribution

When a recommended product is viewed, the result_id from the recommendation result should be used. Below is an example recommendation result (some fields have been omitted).

The snippet of code below attributes product1 to the result id nosto-frontpage-1-fallback from the recommendation result above and fetches recommendations for nosto-productpage-1.

Letting Nosto render the HTML

If you wish Nosto to render the recommendations and returns HTML for the recommendations you can set the response mode to HTML by adding .setResponseMode('HTML') into your call. A full example for 404 page would look like this

Please note that Nosto will not inject the returned HTML into the DOM even if the response mode is set to HTML. The logic for injecting the recommendations must be implemented in the application side.

Including Add to Cart-buttons into Recommendations

Usually adding an Add to cart button directly into the recommendation which lists multiple products is fairly straightforward, the website just needs to have an appropriate javascript function to do the operation. Nosto provides ready-made functions for multiple platforms so check the Nosto documentation specific to your e-commerce platform or if needed look into your e-commerce platform's documentation and support on how to create such a function.

Once the Add to cart button is pressed it should call that function to add the products to the cart, but it should also additionally let Nosto know that the button was pressed so Nosto can track the sales from the recommendation. This can be done with the reportAddToCart function as follows:

⚠️ The api call is only for informing Nosto about the attribution (that the product was added from the recommendation), setCart function in the Session API should be used to tell Nosto the user’s cart contents.

Working with content

The load method returns a promise which can be consumed to get the raw placement data. The raw recommendation data is an object containing the placement data.

Working with popups

Popups are handled automatically and you do not need to control the rendering of the popups. As events are dispatched, popups will show and hide automatically. If you would like to have programmatic control over the popups, see .

Working with affinities

Customer affinities are taken into account by the recommendation engine and matched with the affinities defined on the products. The affinities are determined automatically, but the API allows for a more granular manual control when required.

In case addAffinity is called multiple times, the provided values will all be merged together.

Note: If the same key is provided more than once, the latest encountered value is used, and the earlier keys are overridden.

It's possible to provide multiple affinities in a single call to addAffinity.

If the current state of the request needs to be cleared, a second parameter can be provided to the addAffinity call. In the example below, sku.size key will be removed from the request.

Note: This only affects the current local state of the request. It will not remove user's affinities already saved on the backend.

nostojs(api => {
  api.defaultSession()
    .setResponseMode('HTML')
    .enableCampaignInjection()
    .viewProduct('product1') // id of product currently being viewed
    .setPlacements(api.placements.getPlacements())
    .load()
});
nostojs(api => {
  api.defaultSession()
    .setResponseMode('HTML')
    .enableCampaignInjection()
    .viewProduct('product1') // id of product currently being viewed
    .setPlacements(api.placements.getPlacements())
    .load({ trackEvents: false })
});
{
    "recommendations": {
        "nosto-frontpage-1": { // requested placement id
            "result_id": "nosto-frontpage-1-fallback", // slot id that served the recommendations
            "products": [{
                "url": "https://example.com/products/product1",
                "product_id": "product1"
            }, {
                "url": "https://example.com/products/product2",
                "product_id": "product2"
            }],
            "result_type": "REAL",
            "title": "Most Popular Right Now",
            "div_id": "nosto-frontpage-1" // requested placement id
    }
}
nostojs(api => {
  api.defaultSession()
   .viewProduct('product1') // id of product currently being viewed
   .setRef('product1', 'nosto-frontpage-1-fallback') // id of product and slot that resulted in the product view
   .setPlacements(['nosto-productpage-1']) // placements to request content for on the product page
   .load()
   .then(data => console.log(data))
 })
nostojs(api => {
  api.defaultSession()
    .setResponseMode('HTML')
    .viewNotFound()
    .setPlacements(['notfound-nosto-1', 'bestseller-recs'])
    .load()
    .then(data => {
      // Here you would inject the recommendations to DOM
      console.log(data.recommendations);
    })
});
nostojs(api => {
  api.defaultSession()
    .reportAddToCart('product-id', 'element')
    .update()
});
nostojs(api => {
  api.defaultSession()
      .viewFrontPage()
      .addAffinity({ brand: ["nike"] })
      .addAffinity({ productType: ["shoes"] })
      .load()
      .then(handleResponse)
})
nostojs(api => {
  api.defaultSession()
      .viewFrontPage()
      .addAffinity({ productType: ["shoes"], "sku.size": ["42"] })
      .load()
      .then(handleResponse)
})
nostojs(api => {
  api.defaultSession()
      .viewFrontPage()
      .addAffinity({ "sku.size": ["42"] })
      .addAffinity({ productType: ["shoes"] }, { clear: true })
      .load()
      .then(handleResponse)
})
our guide on the Popup JS API

Updating Products

While Nosto's crawler attempts to keep its copy of your catalog as fresh as possible, there are scenarios where we may not be able to update all the information as quickly as needed.

In these scenarios, we recommend that you implement our enhanced Products API which allows you push all your product metadata and have the changes instantly reflect across our entire engine.

For example, if you add a discount of -10% to all the products in your "Shirts" category, you can use the Products API to bulk update all the products.

Here's an example of a Curl request.

curl -v --user :WI0j2oN7TgG42tlblX3yzOQ5xvCYc2oYj9eWg79lghVq8R0nKQXlVE9wvihBUFOw -H "Content-Type: application/json" -X POST https://api.nosto.com/v1/products/upsert -d '
[
  {
    "url":"http://shop.example.com/product/123",
    "product_id":"123",
    "name":"Example product 123",
    "image_url":"http://cdn.example.com/product_123.jpg",
    "price_currency_code":"EUR",
    "availability":"InStock",
    "rating_value":"3.8",
    "review_count":"15",
    "categories":[
      "sale",
      "sale/summer",
      "sale/summer/shirts",
      "sale/summer/shirts/long-sleeve-shirts"
    ],
    "description":"Example description",
    "price":10.00,
    "list_price":12.34,
    "brand":"Example Brand",
    "tag1":[
      "red",
      "green"
    ],
    "tag2":[
      "women"
      "promo:mens"
    ],
    "tag3":[
      "Foldable"
    ],
    "date_published":"2013-­04-­23",
    "variation_id":"EUR_1",
    "alternate_image_urls": [
      "http://cdn.example.com/product_123-1.jpg",
      "http://cdn.example.com/product_123-2.jpg",
      "http://cdn.example.com/product_123-3.jpg",
    ],
    "variations":{
      "USD_2":{
        "price_currency_code":"USD",
        "availability":"InStock",
        "price":12.00,
        "list_price":15.67
      },
      "GBP_3":{
        "price_currency_code":"GBP",
        "availability":"OutOfStock",
        "price":20.00,
        "list_price":21.99
      }
    },
    "inventory_level":25,
    "supplier_cost":1312.96,
    "custom_fields":{
      "material":"Cotton",
      "weather":"Summer"
    },
    "skus":[
      {
        "id":"123-2",
        "name":"S-Blue",
        "price":1269.0,
        "list_price":1299.0,
        "url":"http://www.example.com/product/CANOE123#/1-size-s/14-color-blue",
        "image_url":"http://www.example.com/product/images/CANOE123-1.jpg",
        "availability":"InStock",
        "inventory_level":35,
        "custom_fields":{
          "size":"S",
          "color":"Blue"
        }
      },
      {
        "id":"123-1",
        "name":"S-Orange",
        "price":1269.0,
        "list_price":1299.0,
        "url":"http://www.example.com/product/CANOE123#/1-size-s/13-color-orange",
        "image_url":"http://www.example.com/product/images/CANOE123-1.jpg",
        "availability":"InStock",
        "inventory_level":42,
        "custom_fields":{
          "size":"S",
          "color":"Orange"
        }
      }
    ]
  }
]'

Sending the categories

Categories must always be delimited by a slash. For example, Home/Accessories or sale/summer/shirts/long-sleeve-shirts is a valid category while Home > Accessories is not.

How can I get an API token?

You can request an API token (API_PRODUCTS) by getting in touch with our support personnel. Once the token has been granted, you will be able to find it listed in the authentication tokens section in the admin.

How many items can I update at a time?

The Products API takes an array of product metadata and has no hard limit on the number of items that you can specify and is only limited by the maximum size of the payload of 2 MB.

How often can I update products?

You can update products as often as you need although, initially you'll need to send the entire catalog to populate the replica of your catalog on Nosto.

When should I make an API call?

You should make an API call when any product information changes. If a non-critical field of the product e.g. description, has changed, you can delay the product update as descriptions are rarely visible to the end customer. When the price or availability of the product changes, you should make an API call right away as this will instantly reflect in the recommendations.

Is there any additional benefit of using the product API?

While the product API contains the full superset of the information in the tagging, it also allows you to pass sensitive information to us. Sensitive information is fields such as the supplier cost (margin) and the inventory level. These fields are only mutable via the API.

What fields are required?

The following fields are required:

  • product_id

  • name

  • url

  • image_url

  • availability

  • price

  • price_currency_code

Using the API

In order to use the GraphQL endpoints, you'll need to authenticate yourself. You will need a Apps token to access this endpoint. Only a subset of the endpoints can be accessed with a public token. This makes it possible to access functionality like querying product recommendations in an environment where it's not possible to protect the token, for example in a web browser. Each publicly accessible endpoint is denoted in the embedded documentation inside the playground.

Note: Nosto does not rate-limit the API usage but follows a fair-use policy. Nosto reserves the right to revoke API access for any abusive API usage patterns.

Sending JSON GraphQL queries

You can send your GraphQL requests as JSON to our API and have it correctly interpolate variables passed into it. To do so, set the Content-Type header to application/json.

Authentication
Token
Method
Endpoint

Basic

API_APPS

POST

https://api.nosto.com/v1/graphql

curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/json' \
-d @- << EOF
{
  "query": "query { recos (preview: false, image: VERSION_7_200_200) { toplist(hours: 168, sort: BUYS, params: { minProducts: 1 maxProducts: 10 } ) { primary { name productId } } } }"
}
EOF

Sending raw GraphQL queries

If you want to send raw GraphQL queries to the API, you can still do so but you must set the Content-Type header to application/graphql.

Note: If you do not set the correct Content-Type header, the request will be interpreted as JSON and will fail.

Authentication
Token
Method
Endpoint

Basic

API_APPS

POST

https://api.nosto.com/v1/graphql

curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  recos (preview: false, image: VERSION_7_200_200) {
    toplist(hours: 168, sort: BUYS, params: {
      minProducts: 1
      maxProducts: 10
    }) {
      primary {
        name
        productId
      }
    }
  }
}
EOF

Using Fetch

You can use the browser's fetch API to request data from GraphQL. You will need to authenticate yourself and set the appropriate content-type headers.

Sending JSON GraphQL queries

You can send your GraphQL requests as JSON to our API and have it correctly interpolate variables passed into it. To do so, set the Content-Type header to application/json.

const body = JSON.stringify({
  query: `
  query {
    order(id: "5192009") {
      number
      reference
      items {
        productId
        skuId
        unitPrice
        priceCurrencyCode
        quantity
      }
      recos(preview: true, image: VERSION_1_170_170) {
        related(params: {
          minProducts: 1,
          maxProducts: 5
        }) {
          primary {
            productId
            name
            price
          }
        }
      }
    }
  }
`});

fetch('https://api.nosto.com/v1/graphql', {
  method: 'POST',
  headers: new Headers({
    'Content-Type': 'application/json',
    'Authorization': 'Basic ' + btoa(":" + "<token>")
  }),
  mode: 'cors',
  body
})
  .then((response) => response.json())
  .then((json) => {
    console.log(json);
  });

Sending raw GraphQL queries

If you want to send raw GraphQL queries to the API, you can still do so but you must set the Content-Type header to application/graphql.

**NOTE:**If you do not set the correct Content-Type header, the request will be interpreted as JSON and will fail.

const body = `
query {
  order(id: "5192009") {
    number
    reference
    items {
      productId
      skuId
      unitPrice
      priceCurrencyCode
      quantity
    }
    recos(preview: true, image: VERSION_1_170_170) {
      related(params: {
        minProducts: 1,
        maxProducts: 5
      }) {
        primary {
          productId
          name
          price
        }
      }
    }
  }
}
`;

fetch('https://api.nosto.com/v1/graphql', {
  method: 'POST',
  headers: new Headers({
    'Content-Type': 'application/graphql',
    'Authorization': 'Basic ' + btoa(":" + "<token>")
  }),
  mode: 'cors',
  body
})
  .then((response) => response.json())
  .then((json) => {
    console.log(json);
  });

Record Attribution

Summary

Nosto already offers the Session API, for recording attribution. But it's limited to vp events so far. In an attempt to expand this functionality to any event types, a new Session API method recordAttribution has been introduced. This method can be utilized to update current visit with details of events for attribution.

Deprecated

The recordAttribution usage with Session API is deprecated and no longer recommended. The API will continue to work without any issues but we recommend using the JS API version. For more info on JS API, please refer JS API/Record Attribution

Usage

The Session API method recordAttribution accepts one event at a time, but users can chain multiple calls to recordAttribution and add as many events as they want.

Parameters

name
field type
is required
description

type

string

yes

type of event to which a placement (ref) should be attributed

target

string

yes

id of the element that's been loaded as a result of the event

ref

string

no

id of the element that hosted the link which triggered the event

refSrc

string

no

id of parent element of the link that triggered the event

Event Types

Nosto supports following predefined event types

Type
Description

View Product (VP)

An event associated with viewing a single product

View Category (VC)

An event associated with event a single category of items

Internal Search (IS)

An event associated with results of search internal to a merchant's website

Add to cart (CP)

An event associated with adding a product or bundle to a cart

External Campaign (EC)

An event associated with campaigns, which are not part of nosto, that directed a user to a merchant website. These campaigns will contain google's UTM parameters ( for more info.) in ev1 request URL

Custom Campaign (CC)

An event associated with campaigns, which are not part of nosto, that directed a user to a merchant website. These campaigns will not contain google's UTM parameters in ev1 request URL

External Search (ES)

An event associated with search outside of merchant's website (Google for e.g.) that brought the user to merchant's website

Give Coupon (GC)

An event associated with a coupon code campaign popup in which the customer has acted upon

Source (SRC)

An event associated with a customer action which needs to be attributed to one of Nosto's feature (api, email, rec, imgrec, cmp). Here the information is usually passed through a pre-configured source parameters name (nosto_source, by default) in ev1 request URL

Cart Popup Recommendations (CPR)

An event associated with a recommendation, that's shown when a product is added to cart, in which a customer has acted upon

Page Load (PL)

An event associated with a page load merchant's website

Content Campaign (CON)

Event triggered when a customer performs an action inside a content campaign

Querying Recommendations

You can generic recommendations using the GraphQL orders endpoint. The recommendations offered here don't use sessions so they aren't personalized but still offer enough flexibility to support a multitude of use-cases.

Fetching toplists recommendations

The endpoint can be used to fetch toplist recommendations i.e. best-sellers. Toplists recommendations are either sorted by views or buys.

Fetching random recommendations

The endpoint can be used to fetch random recommendations i.e. previews. Random recommendations are a totally randomized selection of products and often used for testing purposes.

Fetching related recommendations

The endpoint can be used to fetch related recommendations i.e. cross-sellers. Cross-sell recommendations allow you fetch related products to a given set of products.

Example: If you were to use this to add recommendations to a product page, the productIds parameter would be a single-item array containing the product identifiers of the product that is being viewed.

Example: If you were to use this to add recommendations to an order-follow email, the productIds parameter would be an array containing the product identifiers of the products that were purchased.

Fetching search recommendations

The endpoint can be used to fetch recommendations related to a search term.

Example: If you were to use this to add recommendations to a search page, the term parameter would be the entire search term as queries by the user.

Note: This endpoint cannot be used for building auto-complete style integrations.

Add inclusive or exclusive filters

In case some cases it is desired to filter products by their properties. include and exclude fields of primary should be used to achieve this. The former will filter only the products that match the specified attribute, the later will filter out the products that match the attribute.

include field expects a type of InputIncludeParams, whose attributes are

exclude field expect type InputFilterParams whose attributes are:

Here is an example of a query including filters

Implementing Autocomplete

Autocomplete is an element shown under search input used to display keywords and products for a partial query.

Check out .

Configuration

To enable autocomplete, additional configuration should be passed to init function.

Autocomplete query parameter as function

In the example above, we supply autocomplete query parameters as an object. Additionally, the autocompleteQuery parameter can also be supplied as a function. The function flavor can be used for building complex query parameters and provides access to other pre-defined configuration parameters. Section below shows an example of autocompleteQuery supplied as a function which provides the product variation id by accessing the pre-defined variationId method from the default configuration.

Customizing dropdown position

When the autocomplete component is injected, by default it will become the next sibling of the input field. It is possible to override that behaviour by specifying the dropdownCssSelector value. If this selector is specified, the dropdown will be injected as the last child of the specified element.

It can also be set to be the first child of the element by using the object selector syntax.

The full list of Configuration options is documented

Autocomplete component

Voice to text search

To implement voice to text search in search templates, additional configuration params need to be provided:

Configuration parameters:

  • speechToTextComponent – The component that renders the voice search button.

  • speechToTextEnabled – A flag to enable the voice search feature, disabled by default

The voice search button will be injected adjacent to the search input field, positioned as an overlay on the right end of the input.

Within the button component, the useSpeechToText hook is used to toggle voice input on and off.

The @nosto/preact package exports two useful utilities:

  • useSpeechToText – A hook to control the voice-to-text functionality.

  • speechToTextSupported – A variable indicating whether the current environment supports the feature.

Element selection

Wrap each keywords and product to AutocompleteElement element - it will allow clicking or selecting the element directly with keyboard.

Search submit

To submit a search directly from the autocomplete, use the <button type="submit"> element. This will submit the search form.

History component

History component renders user search history. It is displayed when user clicks on empty search box.

HistoryElement renders clickable element that triggers search event with provided query.

Analytics

Autocomplete automatically tracks to Google Analytics & Nosto Analytics when using <AutocompleteElement /> component.

Querying Category Merchandising Products (CM 1.0)

This documentation is only for the implementation of CM 1.0 and most likely this doc shouldn't be used anymore. If you'd like to implement CM 2.0, please check this . If you're not sure which version you'd like to implement, please contact your technical solutions manager or our support.

Nosto's category merchandising APIs are meant to be used for resorting your existing product listing pages. It has support for listing the products in the order defined within Nosto for different categories, including paging and filtering support. It doesn't support taking over the product listing pages fully as it doesn't include support for creating category specific filters and calculating their facet counts. The expected implementation leveraging these APIs would keep on using their current solution for building most of the product list pages, but swap the shown products with the information retrieved from this APIs results.

You can query using the query below:

Session Lookup

In order to be able to provide personalized results, we will need to look up a session either by id or by reference. You read more about managing sessions on our wiki page.

Attribution

Automatic Attribution

Setting the addAttributionParameters parameter to true, causes attribution parameters to be automatically rendered in product URLs. For example, the url https://example.com/product becomes https://example.com/product?nosto_source=cmp&amp;nosto=5e5e09f060b232790cbbccbf. These parameters allow our client script to track the performance of Category Merchandising results.

Manual Attribution

If you wish to handle attribution parameters manually, the addAttributionParameters parameter can either be set to false or can be omitted.

Product views resulting from clicking products of a category listing need to contain the required attribution parameters:

  • type: VIEWED_PRODUCT

  • target: (product id)

  • ref: (resultId from the category merchandising response)

  • refType: CATEGORY_MERCHANDISING

For example, given the following category merchandising response:

If a customer clicks on Cool Kicks, the following GraphQL query should be sent to fetch the product's details and to attribute the product view to the category merchandising result:

Event handling

Graphql calls using Category Merchandising methods are treated as a category view by default. This behavior can be changed by including skipVCEvent: true into the graphql request. All product URLs on a category page must be appended with #nosto\_cmp fragment. An example of such a product URL would be www.test-store.com/product1#nosto\_cmp where #nosto\_cmp is the added fragment.

Pagination

The batchToken can be used the next batch of results. This is useful if you only wish to fetch the first 10 products when there may be thousands of results. To fetch the next batch, use the batchToken in the next query like in the example below:

If you wish to skip the first number of pages, you can use the skipPages parameter instead of the batchToken. A page size is calculated from the maxProducts parameter.

Filtering Results

Results can be filtered by specifying the include and exclude parameters. You can explore more parameters in the

Tracking Events

Upon viewing the homepage

When viewing a homepage, there's no context to be provided, so invoking the viewFrontPage will suffice.

Upon viewing a product

When viewing a product, you should send the product-id of the current product being viewed. Unlike the regular implementation, you do not need to pass the entirety of the product metadata.

Upon viewing a product variant

Optional event that can be sent to signal that a specific product variant (SKU in Nosto terms) is being viewed. Typical use case for sending this event would be from product detail page when the user selects a product variant, such as some specific color and/or size. The recommendations can then be configured to update and give preference for products that have similar variants available. For example "Other products also available in the same size". Product variant views are added with the function viewProductSku:

This example leverages dynamic placements and html rendering

Upon viewing a category

When viewing a category, you should send the fully qualified name of the current category. This is not necessarily the same as the handle of the category used in the address of the category page.

When the category or collection id is available it can be provided via

It is needed for accurate attribution in certain Nosto products such as Category Merchandising.

You don’t need to ensure the case-sensitivity of the category being passed so long as the path is tagged in the same way as your products’ categories are.

On Shopify, the collection title should be used, so in the snippet above, use simply "Dresses" instead of the collection handle.

Tagging the categories

Nested categories must always be delimited by a slash. For example, /Home/Accessories is a valid category while Home > Accessories is not.

Upon doing a search

When viewing the results of a search, you must send the exact search-term as queried for.

Note: You don’t need to normalize or encode the search query in any way.

Upon starting a checkout

When viewing a checkout page, there's no context to be provided, so invoking the viewCart will suffice.

Upon placing an order

On all thank-you and order-confirmation views, the order confirmation metadata must be passed.

The order confirmation metadata is used for sending personalised order-followup emails, personalise the recommendations e.g. order-related, for segmentation insights and conversion statistics.

Important Even if you would not display any recommendations in your order-confirmation view you must still set placements (.setPlacements(...)) and load (.load()) the results. Setting the order works in a similar manner than and and an must be performed for the data to be sent to Nosto.

Upon viewing a page that was not found (404)

When viewing a page / view that was not found, there's no context to be provided, so invoking the viewNotFound will suffice.

Upon viewing a general page

When a page with a type - other than the ones listed here - is viewed, there's no context to be provided, so invoking the viewOther will suffice.

curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  recos (preview: false, image: VERSION_7_200_200) {
    toplist(hours: 168, sort: BUYS, params: {
      minProducts: 1
      maxProducts: 10
    }) {
      primary {
        name 
        productId
      }
    }
  }
}
EOF
curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  recos (preview: false, image: VERSION_7_200_200) {
    random(params: {
      minProducts: 1
      maxProducts: 10
    }) {
      primary {
        name 
        productId
      }
    }
  }
}
EOF
curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  recos (preview: false, image: VERSION_7_200_200) {
    related(relationship: VIEWED_TOGETHER, productIds: [
        "8685560646"  
    ],
    params: {
      minProducts: 1
      maxProducts: 10
    }) {
      primary {
        name 
        productId
      }
    }
  }
}
EOF
curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  recos (preview: false, image: VERSION_7_200_200) {
    search(term: "shoes",
    params: {
      minProducts: 1
      maxProducts: 10
    }) {
      primary {
        name 
        productId
      }
    }
  }
}
EOF
brands: [String]
The list of brand used for inclusion or exclusion

categories: [String]
The list of categories used for inclusion or exclusion

customFields: [InputAttributeParams]
The map of product attributes used for inclusion or exclusion

discounted: Boolean
A boolean indicating whether discounted products should be excluded

fresh: Boolean
A boolean value denoting the inclusion of new products

price: InputPriceParams
The price range used for inclusion

productIds: [String]
The list of product identifiers used for inclusion or exclusion

rating: Float
The minimum rating score for the inclusion

reviews: Int
The minimum amount of reviews for the inclusion

search: String
Search all products containing this text

stock: InputStockParams
The stock level range used for inclusion

tags1: [String]
The first set of tags used for inclusion or exclusion

tags2: [String]
The second set of tags used for inclusion or exclusion

tags3: [String]
The third set of tags used for inclusion or exclusion
brands: [String]
The list of brand used for inclusion or exclusion

categories: [String]
The list of categories used for inclusion or exclusion

customFields: [InputAttributeParams]
The map of product attributes used for inclusion or exclusion

discounted: Boolean
A boolean indicating whether discounted products should be excluded

productIds: [String]
The list of product identifiers used for inclusion or exclusion

search: String
Search all products containing this text

tags1: [String]
The first set of tags used for inclusion or exclusion

tags2: [String]
The second set of tags used for inclusion or exclusion

tags3: [String]
The third set of tags used for inclusion or exclusion
curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  recos (preview: false, image: VERSION_7_200_200) {
    toplist(hours: 168, sort: BUYS, params: {
      minProducts: 1
      maxProducts: 10
      include: {
        customFields: [
          {
            attribute: "pattern"
            values: ["Solid"]
          }
        ]
        discounted: true
      }
    }) {
      primary {
        name 
        productId
      }
    }
  }
}
EOF
UTM parameters
curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  session(by: BY_CID, id: $customerId) {
    id
    recos(
      preview: false,
      image: VERSION_ORIGINAL,
      addAttributionParameters: true) {
      category(
        category: $categoryName,
        minProducts: 1,
        maxProducts: 10
      ) {
        primary {
          productId
          url
        }
        batchToken
        totalPrimaryCount
        resultId
      }
    }
  }
}
EOF
{
  "data": {
    "updateSession": {
      "recos": {
        "category": {
          "resultId": "623473861055d80027efe482",
          "primary": [{
            "productId": "9497180547",
            "name": "Cool Kicks",
            "url": "https://example.com/products/cool-kicks"
          }]
        }
      }
    }
  }
}
mutation($sessionId: String!) {
  updateSession(by: BY_CID, id: $sessionId, params: {
    event: {
      type: VIEWED_PRODUCT
      target: "9497180547"
      ref: "623473861055d80027efe482"
      refType: CATEGORY_MERCHANDISING
    }
  }) {
    primary {
      productId
      url
      name
      description
      priceText
    }
  }
}
curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  session(by: BY_CID, id: $customerId) {
    id
    recos(
      preview: false,
      image: VERSION_ORIGINAL,
      addAttributionParameters: true) {
      category(
        category: $categoryName,
        minProducts: 1,
        maxProducts: 10,
        batchToken: "n200MjA2NDc0OTUyNzg1+z/wAAAAAAAA/w=="
      ) {
        primary {
          productId
          url
        }
        batchToken
        totalPrimaryCount
        resultId
      }
    }
  }
}
EOF
curl -0 -v -X POST https://api.nosto.com/v1/graphql \
-u ":<token>" \
-H 'Content-Type: application/graphql' \
-d @- << EOF
query {
  session(by: BY_CID, id: $customerId) {
    id
    recos(
      preview: false,
      image: VERSION_ORIGINAL,
      addAttributionParameters: true) {
      category(
        category: $categoryName,
        minProducts: 1,
        maxProducts: 10,
        include: {
          customFields: [{
            attribute: "color",
            values: ["white"]
          }]
        }
        exclude: {
          discounted: true
        }
      ) {
        primary {
          productId
          url
        }
        batchToken
        totalPrimaryCount
        resultId
      }
    }
  }
}
EOF
documentation
category merchandising products
onsite sessions
GraphQL Playground
nostojs(api => {
  api.defaultSession()
    .viewFrontPage()
    .setPlacements(['homepage-nosto-1', 'bestseller-recs'])
    .load()
    .then(data => {
      console.log(data.recommendations);
    })
});
nostojs(api => {
  api.defaultSession()
    .viewProduct('product-id')
    .setPlacements(['product-crosssells'])
    .load()
    .then(data => {
      console.log(data.recommendations);
    })
});
nostojs(api => {
  api.defaultSession()
    .setResponseMode('HTML')
    .enableCampaignInjection()
    .viewProductSku('product-id-x', 'sku-id-y')
    .setPlacements( api.placements.getPlacements() )
    .load()
});
nostojs(api => {
  api.defaultSession()
    .viewCategory('/Womens/Dresses')
    .setPlacements(['category-related'])
    .load()
    .then(data => {
      console.log(data.recommendations);
    })
});
nostojs(api => {
  api.defaultSession()
    .viewCategory('/Womens/Dresses')
    .setCategoryIds(['431783631'])
    ...
});
nostojs(api => {
  api.defaultSession()
    .viewSearch('womens dresses')
    .setPlacements(['search-related'])
    .load()
    .then(data => {
      console.log(data.recommendations);
    })
});
nostojs(api => {
  api.defaultSession()
    .viewCart()
    .setPlacements(['cart-related'])
    .load()
    .then(data => {
      console.log(data.recommendations);
    })
});
nostojs(api => {
  api.defaultSession()
    .addOrder(
      {
        "external_order_ref": "145000006",
        "info": {
          "order_number": "195",
          "email": "[email protected]",
          "first_name": "Mridang",
          "last_name": "Agarwalla",
          "type": "order",
          "newsletter": true
      },
      "items": [
        {
          "product_id": "406",
          "sku_id": "243",
          "name": "Linen Blazer (White, S)",
          "quantity": 1,
          "unit_price": 455,
          "price_currency_code": "EUR"
        }
      ]
    })
    .setPlacements(['order-related'])
    .load()
    .then(data => {
      console.log(data.recommendations);
    })
});
nostojs(api => {
  api.defaultSession()
    .viewNotFound()
    .setPlacements(['notfound-nosto-1', 'bestseller-recs'])
    .load()
    .then(data => {
      console.log(data.recommendations);
    })
});
nostojs(api => {
  api.defaultSession()
    .viewOther()
    .setPlacements(['general-recommendations'])
    .load()
    .then(data => {
      console.log(data.recommendations);
    })
});
cart
customer
action
index.js
import { init } from '@nosto/preact'

import autocompleteComponent from './autocomplete'
import historyComponent from './history'

init({
    ...window.nostoTemplatesConfig,
    historyComponent,
    autocompleteComponent,
    inputCssSelector: '#search',
    autocompleteQuery: {
        name: 'autocomplete',
        products: {
            size: 5,
        },
        keywords: {
            size: 5,
            fields: [
                'keyword', '_highlight.keyword'
            ],
        },
    }
})
index.js
import { init } from '@nosto/preact'

import autocompleteComponent from './autocomplete'
import historyComponent from './history'

init({
    ...window.nostoTemplatesConfig,
    historyComponent,
    autocompleteComponent,
    inputCssSelector: '#search',
    autocompleteQuery() {
        return {
            name: 'autocomplete',
            products: {
                size: 5,
                variationId: this.variationId()
            },
            keywords: {
                size: 5,
                fields: [
                    'keyword', '_highlight.keyword'
                ],
            },
        }
    }
})
index.js
import { init } from '@nosto/preact'

init({
    // ...
    inputCssSelector: '#search',
    dropdownCssSelector: 'body',
})
index.js
import { init } from '@nosto/preact'

init({
    // ...
    inputCssSelector: '#search',
    dropdownCssSelector: {
        selector: 'body',
        position: 'first', // 'first' or 'last'
    },
})
autocomplete/index.jsx
import { useAppStateSelector, AutocompleteElement } from '@nosto/preact'

export default () => {
    const { products, keywords } = useAppStateSelector((state) => ({
        products: state.response.products,
        keywords: state.response.keywords
    }))

    if (!products?.hits?.length && !keywords?.hits?.length) {
        return
    }

    return (
        <div>
            {keywords?.hits?.length > 0 && <div>
                <div>
                    Keywords
                </div>
                <div>
                    {keywords.hits.map((hit) => (
                        <AutocompleteElement hit={hit} key={hit.keyword}>
                            {
                                hit?._highlight?.keyword
                                    ? <span dangerouslySetInnerHTML={{ __html: hit._highlight.keyword }}></span>
                                    : <span>{hit.keyword}</span>
                            }
                        </AutocompleteElement>
                    ))}
                </div>
            </div>}
            {products?.hits?.length > 0 && <div>
                <div>
                    Products
                </div>
                <div>
                    {products.hits.map((hit) => (
                        <AutocompleteElement hit={hit} key={hit.productId} as="a">
                            <img src={hit.imageUrl}/>
                            <div>
                                {hit.name}
                            </div>
                            <button
                                // Allow the button to be clicked only once
                                disabled={addedToCart}
                                // Add the product to the cart when the button is clicked
                                onClick={(event) => {
                                    // Don't navigate to the product page
                                    event.preventDefault()

                                    // Update the button text and disable it
                                    setAddedToCart(true)

                                    // Add the product to the cart, this depends on the cart implementation
                                    jQuery.post("/cart/add.js", {
                                        quantity: 1,
                                        id: product.productId
                                    })
                                }}
                            >
                                // Show different text if product was added to the cart
                                {addedToCart ? "Added to cart" : "Add to cart"}
                            </button>
                        </AutocompleteElement>
                    ))}
                </div>
            </div>}
            <div>
                <button type="submit">
                    See all search results
                </button>
            </div>
        </div>
    )
}
index.js
import { init } from '@nosto/preact'

import speechToTextComponent from "./SpeechToTextComponent"

init({
    ...window.nostoTemplatesConfig,
    speechToTextComponent,
    speechToTextEnabled: true,
    ...
})
history/index.jsx
import { useAppStateSelector, HistoryElement } from '@nosto/preact'

export default () => {
    const historyItems = useAppStateSelector((state) => state.historyItems)

    if (!historyItems || !historyItems.length) {
        return
    }

    return (
        <div>
            <div>Recently Searched</div>`
            <div>
                {historyItems.map((item) => (
                    <HistoryElement query={{ query: item }}>
                        {item}
                    </HistoryElement>
                ))}
            </div>
        </div>
    )
}
autocomplete's look & feel guidelines
here
Example Autocomplete

Render results

Once the autocomplete component binds to input via inputSelector and dropdownSelector, it then renders autocomplete provided in a render function. It is called on input focus and change events, and renders a dropdown element with the current search result state:

  • if input is empty and history entries exist, it renders dropdown with history list,

  • if input is not empty and it passes minQueryLength rule, it render dropdown with keywords and products.

Render can be adjusted to the desired framework. Moreover, the library provides helpers for Mustache/Liquid template languages.

Examples

Suppose index.html is

<form id="search-form">
    <input type="text" id="search" placeholder="search" />
    <button type="submit" id="search-button">Search</button>
    <div id="search-results" className="ns-autocomplete"></div>
</form>

List of autocomplete initialization examples:

  1. Liquid Example below uses fromLiquidTemplate helper which renders string template. Library provides default autocomplete template via defaultLiquidTemplate and default css for default template:

import {
    autocomplete,
    search,
    fromLiquidTemplate,
    defaultLiquidTemplate,
} from "@nosto/autocomplete/liquid"
import "@nosto/autocomplete/styles.css"

autocomplete({
    fetch: {
        products: {
            fields: ["name", "url", "imageUrl", "price", "listPrice", "brand"],
            size: 5,
        },
        keywords: {
            size: 5,
            fields: ["keyword", "_highlight.keyword"],
            highlight: {
                preTag: `<strong>`,
                postTag: "</strong>",
            },
        },
    },
    inputSelector: "#search",
    dropdownSelector: "#search-results",
    render: fromLiquidTemplate(defaultLiquidTemplate),
    submit: async (query, config, options) => {
        if (query.length >= config.minQueryLength) {
            const response = await search(
                {
                    query,
                },
                {
                    redirect: true,
                    track: config.nostoAnalytics ? "serp" : undefined,
                    ...options
                }
            )
            // Do something with response. For example, update Search Engine Results Page products state.
        }
    },
})

The template also can be loaded from a file. The library includes a default template, equivalent to string template in above example:

import {
    autocomplete,
    search,
    fromRemoteLiquidTemplate,
} from "@nosto/autocomplete/liquid"
import "@nosto/autocomplete/styles.css"

autocomplete({
    // ...
    render: fromRemoteLiquidTemplate(
        `./node_modules/@nosto/autocomplete/dist/liquid/autocomplete.liquid`
    ),
})
  1. Mustache Mustache template is rendered similarly as Liquid template in the above example:

import {
    autocomplete,
    search,
    fromMustacheTemplate,
    defaultMustacheTemplate,
} from "@nosto/autocomplete/mustache"
import "@nosto/autocomplete/styles.css"

autocomplete({
    // ...
    render: fromMustacheTemplate(defaultMustacheTemplate),
})

Or from a file:

import {
    autocomplete,
    search,
    fromRemoteMustacheTemplate,
} from "@nosto/autocomplete/mustache"
import "@nosto/autocomplete/styles.css"

autocomplete({
    // ...
    render: fromRemoteMustacheTemplate(
        `./node_modules/@nosto/autocomplete/dist/mustache/autocomplete.mustache`
    ),
})
  1. React/Preact One way to initialize autocomplete in a React app, is to call autocomplete from the useEffect on component mount, using default <Autocomplete /> component and styles:

import { useEffect } from "react"
import { createRoot } from "react-dom/client"
import {
    autocomplete,
    search,
    Autocomplete,
} from "@nosto/autocomplete/react"
import "@nosto/autocomplete/styles"

let reactRoot = null

export function Search() {
    useEffect(() => {
        autocomplete({
            fetch: {
                products: {
                    fields: [
                        "name",
                        "url",
                        "imageUrl",
                        "price",
                        "listPrice",
                        "brand",
                    ],
                    size: 5,
                },
                keywords: {
                    size: 5,
                    fields: ["keyword", "_highlight.keyword"],
                    highlight: {
                        preTag: `<strong>`,
                        postTag: "</strong>",
                    },
                },
            },
            inputSelector: "#search",
            dropdownSelector: "#search-results",
            render: function (container, state) {
                if (!reactRoot) {
                    reactRoot = createRoot(container)
                }

                reactRoot.render(<Autocomplete {...state} />)
            },
            submit: async (query, config, options) => {
                if (query.length >= config.minQueryLength) {
                    const response = await search(
                        {
                            query,
                        },
                        {
                            redirect: true,
                            track: config.nostoAnalytics ? "serp" : undefined,
                            ...options
                        }
                    )
                    // Do something with response. For example, update Search Engine Results Page products state.
                }
            },
        })
    }, [])

    return (
        <form id="search-form">
            <input type="text" id="search" placeholder="search" />
            <button type="submit" id="search-button">
                Search
            </button>
            <div id="search-results" className="ns-autocomplete"></div>
        </form>
    )
}

The Preact solution does not differ from React a lot:

import { render } from "preact/compat"
import { useEffect } from "preact/hooks"
import {
    Autocomplete,
    autocomplete,
    search,
} from "@nosto/autocomplete/preact"
import "@nosto/autocomplete/styles.css"

export function Search() {
    useEffect(() => {
        autocomplete({
            // ...
            render: function (container, state) {
                render(<Autocomplete {...state} />, container)
            },
        })
    }, [])

    return <form id="search-form">{/* ... */}</form>
}

Adding the Cart Tagging

On every page load, the cart content must be tagged. The cart contents are the 1:1 representation of the user's mini-cart.

The cart information is used by the Nosto to tailor the recommendations, dispatch abandoned cart emails and fire Facebook pixel events for retargeting purposes.

<div class="nosto_cart" style="display:none" translate="no">

    <div class="line_item">
        <span class="product_id">Canoe123</span>
        <span class="quantity">1</span>
        <span class="name">Acme Canoe</span>
        <span class="unit_price">999.00</span>
        <span class="price_currency_code">EUR</span>
    </div>

    <div class="line_item">
        <span class="product_id">Canoe245</span>
        <span class="quantity">3</span>
        <span class="name">Acme Large Canoe</span>
        <span class="unit_price">19.00</span>
        <span class="price_currency_code">EUR</span>
    </div>

</div>

Note: The product ID of the product tagging, cart tagging and order tagging must match. Failure to do so will lead to a mismatch in both attribution and statistics across the Nosto product.

Adding support for advanced use cases

Many e-commerce stores utilize SKU:s or "child" products that are sorted under the same "parent" product. To extend the above example with SKU support refer to this article

In cases where a product might have multiple prices in differing currencies, you can also add support for multi-currency. Refer to this article

Tagging the cart restore link

If the platform itself has support for persistent shopping cart or other technologies that remember the users cart contents you do not need to worry about filling out the cart when a user returns to the site. If your platform generates a restore cart link you can also send that to Nosto by adding it as a new attribute within the parent container "nosto_cart".

The following piece of code is just a rough example on how a restore cart could look like. The idea of the example is to document how this is tagged to Nosto.

<div class="restore_link">https://example.com/cart/restore?cart=4D5C3060-1334-4C63-B6FA-D9D342D88B08</div>

Troubleshooting

Once included on all pages, you can review if the site is transmitting data using the Nosto Debug Toolbar. If you can see cart contents being picked up under "Tagging" → "Cart" then the cart details are correctly set up in the source code. You can further verify your session in the Nosto admin by using the live feed under https://my.nosto.com/admin/$accountID/liveFeed to see if Nosto correctly picks up product view → product carted events.

Nosto debug toolbar cart
live-feed-product-cart

Translate attribute

The translate attribute is a HTML5 standard attribute which specifies whether the value of the element and it's Text node children should be translated. If your tagging elements are being translated by e.g. Google Translator then this is the way to opt out elements being translated by Google and possibly other vendors.

Initialization

After installation, you can import and use the library in your JavaScript or TypeScript files. Library attaches to existing search input element. Once a search input is loaded, autocomplete function can initialize event listeners for the Search Autocomplete.

❗autocomplete should be called once.❗

Property
Required
Default Value
Description

Adding support for multi-currency

In this article, you will learn how to implement multi-currency in Nosto. When the implementation is complete, you will be able to display product prices (in any feature) in different currencies.

Prior to the multi-currency implementation, ensure that the Nosto tagging is correctly in place. Some of the tagging must be slightly amended to support multi-currency.

Shopify Multi-Currency

For instructions on integrating with Shopify's multi-currency, please go .

Changes to the product tagging

The product page tagging must be amended to denote the primary currency code of the product. Typically, most retailers have a primary currency which is the default currency of the inventory.

For example, a US-based retailer who sells in Euros (EUR) and Sterling Pounds (GBP) would have US Dollar (USD) as the primary currency while Euro (EUR) and Sterling Pounds (GBP) would be secondary currencies whose exchange rates would need to be sent via an API.

An additional span tag must be placed within the product page tagging with a class name variation_id. The tag is a child element of the nosto_product element.

Note: The code in the variation_id element must remain static, regardless of the currency active on-site. This is the primary currency of your catalog. Although variation_id element often has the same currency code as in the price_currency_code element and may seem redundant, they support different use cases and both need to be tagged.

Do child-products (SKUs) support multi-currency?

Yes. Prices for all SKUs will automatically be converted using the same logic. As long as your SKUs are tagged, no additional changes are needed.

What about the prices in the cart and the order tagging?

The cart and order tagging can be left as-is but the prices must be in the customer's currently active currency. For example, a customer shopping in Swiss Francs (CHF) should have all the cart items tagged in Swiss Francs (CHF). Failure to do so will result in incorrect prices in any triggered mails such as abandoned cart or order followup.

Specifying the active currency

Once you have amended the product tagging, an additional DIV element must be added to all the other pages (including the product page itself). The tag should not be encapsulated in the nosto_product DIV tag. The information sent in the tag refers to the currency active of the customer.

For example, on the site of a US-based retailer who sells in Euros (EUR) and Sterling Pounds (GBP), if the customer changes the currency to Sterling Pounds (GBP), the nosto_variation element should show GBP. If the customer changes the currency to Euros (EUR), the nosto_variation element should show EUR.

Sending the exchange-rates

In order to send the exchange rate multipliers to Nosto, you will need to use . Below is a small snippet of what the payload looks like.

In the example above, 0.77 is the exchange rate from US Dollars (USD) to British Pounds (GBP) and 0.91 is the exchange rate from US Dollars (USD) to Euros (EUR).

The valid_until entry defines the expiration date. When the expiration date is reached, the exchange rates won't be applied anymore and prices will be hidden for all the secondary currencies to prevent displaying outdated prices.

When recommendations are served, then exchange rates are dynamically applied to the product prices to reflect the active currency.

Enabling multi-currency from the admin

Once the tagging changed have been done and the API implemented, you need to configure and enable it from your admin panel under Settings > Other > Multi-Currency. Toggle the Use Multiple Currencies and Use Exchange Rates switches on and set the variation ID of the primary currency via the input field and toggle on the exchange rates switch.

Note: Ensure that the Variation ID of the primary currency matches the value sent via the variation_id element in the product tagging.

You will also need to configure the price formatting for your primary and secondary currencies.

Reviewing your changes

Once you enabled multi-currency and made an API call, you can review the exchange rates received by Nosto by navigating to Settings > Other > Multi-currency.

You can also preview the product prices for different currencies by navigating to Tools > Products and choosing a product.

You will see one or more dropdowns that contain the prices and price calculation for the currency.

When you have reviewed your set-up, Nosto updates in real-time product prices for all the currencies and display the appropriate currency to the right target groups of users. You’re all set and ready to go live with our features.

import { useEffect } from "react"
import {
    autocomplete,
    search,
    priceDecorator,
    Autocomplete,
} from "@nosto/autocomplete/react"
import { createRoot } from "react-dom/client"
import "@nosto/autocomplete/styles.css"

let reactRoot = null

export function Search() {
    useEffect(() => {
        autocomplete({
            fetch: {
                products: {
                    fields: [
                        "name",
                        "url",
                        "imageUrl",
                        "price",
                        "listPrice",
                        "brand",
                    ],
                    size: 5,
                },
                keywords: {
                    size: 5,
                    fields: ["keyword", "_highlight.keyword"],
                    highlight: {
                        preTag: `<strong>`,
                        postTag: "</strong>",
                    },
                },
            },
            inputSelector: "#search",
            dropdownSelector: "#search-results",
            hitDecorators: [
                // adds priceText & listPrice fields based on Nosto currency formatting rules
                priceDecorator({ defaultCurrency: "USD" })
            ],
            render: function (container, state) {
                if (!reactRoot) {
                    reactRoot = createRoot(container)
                }

                reactRoot.render(<Autocomplete {...state} />)
            },
            submit: async (query, config, options) => {
                if (query.length >= config.minQueryLength) {
                    const response = await search(
                        {
                            query,
                        },
                        {
                            redirect: true,
                            track: config.nostoAnalytics ? "serp" : undefined,
                            ...options
                        }
                    )
                    // Do something with response. For example, update Search Engine Results Page products state.
                }
            },
        })
    }, [])

    return (
        <form id="search-form">
            <input type="text" id="search" placeholder="search" />
            <button type="submit" id="search-button">
                Search
            </button>
            <div id="search-results" className="ns-autocomplete"></div>
        </form>
    )
}

inputSelector

Yes

N/A

Input element to attach the autocomplete to

dropdownSelector

Yes

N/A

Dropdown element to attach the autocomplete to (empty container's selector should be provided)

render

Yes

N/A

Function to render the dropdown

fetch

Yes

N/A

Function to fetch the search state

submit

No

Search API request

Function to submit the search

minQueryLength

No

2

Minimum length of the query before searching (applied on typing in autocomplete and used in default submit implementation)

historyEnabled

No

true

Enables search history component

historySize

No

5

Max number of history items to show

nostoAnalytics

No

true

Enable Nosto Analytics

googleAnalytics

No

{ serpPath: "search", queryParamName: "query", enabled: true }

Google Analytics configuration. Set to false to disable

hitDecorators

No

N/A

Decorate each search hit before rendering

GraphQL: Onsite Sessions

Creating a session

When a new user comes to the app, you can use this method to get a new session. It will return you a customer-id that can save on the device and use for future requests. This would be ideal.

mutation {
  newSession(referer: "https://google.com?q=shoes")
}

Updating a session

When a customer logs in, you can update the existing customer with the customer-reference. This would merge the online and mobile sessions. If not needed, I would omit this for now.

Working with recommendations

On the Product Page

In order to use the GraphQL session mutation to fetch recommendations for your search page, the event, in this case, must be VIEWED_PRODUCT and you should specify the product-identifier of the current product being viewed.

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_PRODUCT
        target: "11923861519"
        ref: "front-page-slot-1"
      }
    }
  ) {
    pages {
      forProductPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400
      }, product: "11923861519") {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

On the Category Page

In order to use the GraphQL session mutation to fetch recommendations for your category page, the event, in this case, must be VIEWED_CATEGORY and you should specify a fully qualified category path of the current category. For example, if you have a category called "Dresses" under the category "Women", the FQCN would be "/Women/Dresses".

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_CATEGORY
        target: "/Shorts"
      }
    }
  ) {
    pages {
      forCategoryPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400
      }, category: "Shorts") {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

On the Search Page

In order to use the GraphQL session mutation to fetch recommendations for your search page, the event, in this case, must be SEARCHED_FOR and you should specify the search term of the query.

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: SEARCHED_FOR
        target: "black shoes"
      }
    }
  ) {
    pages {
      forSearchPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400
      }, term: "black shoes") {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

On the Cart Page

In order to use the GraphQL session mutation to fetch recommendations for your cart or checkout page, the event, in this case, must be VIEWED_PAGE and you should specify a fully qualified URL of the page as the target.

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_PAGE
        target: "https://example.com/cart"
      }
    }
  ) {
    pages {
      forCartPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400
      }, value: 100) {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

On the Front Page

In order to use the GraphQL session mutation to fetch recommendations for your home or front page, the event, in this case, must be VIEWED_PAGE and you should specify a fully qualified URL of the page as the target

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_PAGE
        target: "https://example.com"
      }
    }
  ) {
    pages {
      forFrontPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400
      }) {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

Attribution of Recommendation Results

Recommendation results can be attributed to events by setting a session event's ref to the recommendation result's resultId.

Here is an example of some recommendations for a front page. You can see recommendation result's resultId is "frontpage-nosto-1".

{
  "data": {
    "updateSession": {
      "pages": {
        "forFrontPage": [
          {
            "resultId": "frontpage-nosto-1",
            "primary": [
              {
                "productId": "9497180547",
                "name": "Cool Kicks",
                "url": "https://example.com/products/cool-kicks"
              },
              {
                "productId": "9497180163",
                "name": "Awesome Sneakers",
                "url": "https://example.com/products/awesome-sneakers"
              },
              {
                "productId": "4676165861430",
                "name": "Free gift",
                "url": "https://example.com/products/free-gift"
              },
              {
                "productId": "2188051218486",
                "name": "Furry Dice",
                "url": "https://example.com/products/furry-dice"
              },
              {
                "productId": "9497180611",
                "name": "Gnarly Shoes",
                "url": "https://example.com/products/gnarly-shoes-1"
              }
            ]
          }
        ]
      }
    }
  }
}

If a customer selects to view the Cool Kicks product, you can generate the following request. Note that the event's ref is set to "frontpage-nosto-1".

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_PRODUCT
        target: "9497180547"
        ref: "frontpage-nosto-1"
      }
    }
  ) {
    pages {
      forProductPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400
      }, product: "9497180547") {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

GraphQL from mobile applications

When making GraphQL queries from mobile applications, it's essential to define the user agent string in your HTTP headers. Ideally, the user agent should represent the mobile environment, including details such as the platform, device type, and application version. Avoid using terms like "bot" in the user agent string, as this might lead to unintended behavior or rejection of the query/session. Sending an empty user agent will also lead to be catch by the bot detection mechanism.

<div class="nosto_product" style="display: none;" translate="no">
  ...
  ...
  ...
  <!-- Variation ID for the primary currency --> 
  <span class="variation_id">USD</span>
</div>
<div class="nosto_variation" style="display: none;">USD</div>
{
  "rates":{
    "GBP":{
      "rate":0.77,
      "price_currency_code":"GBP"
    },
    "EUR":{
      "rate":0.91,
      "price_currency_code":"EUR"
    }
  },
  "valid_until":"2015-02-27T12:00:00Z"
}
here
our exchange-rates API

Use the Search & Categories API

Not all of Nosto's functionality is available for pure GraphQL API integrations. The following features require Using the JavaScript Library:

  • Analytics

  • Segmentation

  • Debug toolbar

To use A/B testing, search requests must be sent using the JavaScript library or templates.

Both Search and Categories are built on the same technical foundation and use the same API and product data. For simplicity, we will just refer to Search API in the following documentation.

Playground and API reference

Use the Search API Playground to try out search queries and browse API reference.

It provides:

  1. Search request schema - you can see field types and inspect what fields are needed for a search request.

  2. Search result schema - you can see return field types with descriptions.

  3. Send requests to the search engine and preview the response.

Authentication

In the majority of cases, authentication is not a requirement for using search APIs. However in rare case may need to:

  • Access sensitive data- all sensitive data is restricted for public access (e.g. sorting by & returning sales).

  • Return all documents - public access require to specify search query, category ID or category path to avoid returning all documents.

Note: Keep your API key secret and do not expose it to the frontend!

HTTP Header
Value

Authorization

Bearer SEARCH_KEY

Authentication Token with API_SEARCH Role is available on dashboard settings page

Making requests

When integrating Search you have the option to directly access the API, or you can use our existing Search JavaScript library that provides most of the required functionality out of the box.

API endpoint

Search use different API endpoint than other Nosto queries: https://search.nosto.com/v1/graphql

Account ID

All requests require an account ID, which can be found in the top-right corner of the Admin dashboard, under the shop name.

Request example

curl -X POST 'https://search.nosto.com/v1/graphql' \
-H 'Content-Type: application/json' \
-d @- << EOF
{
  "query": "query (\$query: String) { search (accountId: \"YOUR_ACCOUNT_ID\", query: \$query) { products { hits { name } total } } }",
  "variables": {
    "query": "green"
  }
}
EOF

Replace YOUR_ACCOUNT_ID with your account id retrieved from the Nosto dashboard.

fetch('https://search.nosto.com/v1/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: `query ($query: String) {
      search (accountId: "YOUR_ACCOUNT_ID", query: $query) {
        products {
          hits {
            name
          }
          total
        }
      }
    }`,
    variables: {
      query: "green"
    },
  }),
})
  .then((res) => res.json())
  .then((result) => console.log(result))

Replace YOUR_ACCOUNT_ID with your account id retrieved from the Nosto dashboard.

JS API includes full-featured search function with tracking support.

$ch = curl_init();

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
curl_setopt($ch, CURLOPT_URL, "https://search.nosto.com/v1/graphql");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(array(
    "query" => <<<EOT
        query (\$query: String) {
            search (accountId: "YOUR_ACCOUNT_ID", query: \$query) {
                products {
                hits {
                    name
                }
                total
                }
            }
        }
    EOT,
    "variables" => array(
        "query" => "green"
    )
)));

$result = curl_exec($ch);
var_dump($result);

Replace YOUR_ACCOUNT_ID with your account id retrieved from the Nosto dashboard.

import requests

r = requests.post(
    "https://search.nosto.com/v1/graphql",
    headers={
        "Content-Type": "application/json"
    },
    json={
        "query": """query ($query: String) {
            search (accountId: "YOUR_ACCOUNT_ID", query: $query) {
                products {
                hits {
                    name
                }
                total
                }
            }
        }""",
        "variables": {
            "query": "green"
        }
    }
)
print(r.json())

Replace YOUR_ACCOUNT_ID with your account id retrieved from the Nosto dashboard.

Response example

Upon a successful request, the API will return a 200 status code response, which includes the search data:

{
  "data": {
    "search": {
      "products": {
        "hits": [
          {
            "name": "My Product",
          }
        ],
        "total": 1
      }
    }
  }
}

Error handling

Error response

API returns a list of errors with explanations. These errors should be logged internally and not displayed to the end user. Instead, display a general message, such as Search is unavailable at the moment, please try again. This approach ensures that no sensitive information is leaked to the end user.

{
  "data": {
    ...
  },
  "errors": [
    {
      "message": "Error explanation"
    }
  ]
}

Partial response

The API may return some errors even when data is returned. This means that some parts of the response may be missing, but you should still display the available data instead of showing an error to the user. These errors should be logged internally for future reference and troubleshooting.

Session params

For features like personalised results and user segments to function effectively, the search function needs access to the user's session information from the front-end.

It's possible to get search session data using the JS API:

nostojs(api => {
    api.getSearchSessionParams().then(response => {
        console.log(response);
    });
});

The results of this function should be passed to search query sessionParams parameter. In case search is called from backend, it should pass this data to backend (e.g. using form data).

It's also possible to save session data to cookies on page load:

<script>
    nostojs(api => {
        api.getSearchSessionParams().then(response => {
            document.cookie = `nostoSearchSessionParams=${encodeURIComponent(JSON.stringify(response))};`;
        });
    })
</script>

Using variables

In the search application, you should use variables instead of hardcoded arguments to pass search data. This means that filters, sort, size, and 'from' options should be passed in the 'products' variable. For a full list of available options, please see the reference.

Query

query (
  $query: String,
  $products: InputSearchProducts,
  $sessionParams: InputSearchQuery
) {
  search(
    accountId: "YOUR_ACCOUNT_ID"
    query: $query
    products: $products,
    sessionParams: $sessionParams
  ) {
    query
    products {
      hits {
        productId
        url
        name
        imageUrl
        thumbUrl
        brand
        availability
        price
        listPrice
        customFields {
          key
          value
        }
        skus {
          name
          customFields {
            key
            value
          }
        }
      }
      facets {
        ... on SearchTermsFacet {
          id
          field
          type
          name
          data {
            value
            count
            selected
          }
        }
        ... on SearchStatsFacet {
          id
          field
          type
          name
          min
          max
        }
      }
      total
      size
      from
      fuzzy
    }
  }
}

To optimize search speed and reduce network load, select only the necessary data when performing a search query.

Variables

Variables should encompass all dynamic query data because it is the most efficient method to pass data, and data is automatically escaped to prevent injection attacks. Avoid generating dynamic queries, as they can lead to security issues if user input is not properly escaped.

{
  "query": "red",
  "products": {
    "size": 5
  },
  "sessionParams": {}
}

Analytics

To analyze user behavior you need to implement tracking. This can be achieved using our JavaScript library. You need to implement the following methods:

  • recordSearchSubmit to track search form submissions

https://my.nosto.com

Using the JavaScript Library

Search

For client-side/frontend integrations, Nosto's JavaScript library can be used to simplify the integration. It provides a programming interface to access the Search & Categories API.

For the most basic search the fields parameter should be provided to specify what product/keyword fields should be returned. Both products and keywords can be used separately and together, depending on the use case. For all parameters, see the reference.

nostojs(api => {
    api.search({
        query: 'my search',
        products: { fields: ["name"] },
        keywords: { fields: ["keyword"] }
    }).then(response => {
        console.log(response);
    });
});

The api.search function also accepts the following options:​

Option
Default
Description

redirect

false

Automatically redirect if search returns a redirect

track

null

Track search query by provided type

The function automatically loads session parameters required for personalization & segments in the background.

In order to request custom fields, add the entries "customFields.key" and "customFields.value" to the requested product fields. This changes the example above like this

// ...
        products: { fields: ["name", "customFields.key", "customFields.value"] },
// ...

Search page

For a search page in most cases the facets parameter should be provided.

Also redirect & track should be enabled to automatically track searches to Nosto analytics & redirect if API returns a redirect request.

isKeyword should be set to true if search is submitted by clicking a keyword, suggested in the autocomplete.

nostojs(api => {
    api.search({
        query: 'my search',
        products: {
            facets: ['*'],
            fields: ['name'],
            size: 10
        }
    }, {
        redirect: true,
        track: 'serp',
        isKeyword: true
    }).then(response => {
        console.log(response);
    });
});

Autocomplete

track should be enabled to automatically track searches to Nosto analytics.

nostojs(api => {
    api.search({
        query: 'my search',
        products: {
            fields: ['name'],
            size: 10
        },
        keywords: {
            fields: ['keyword'],
            size: 5
        }
    }, {
        track: 'autocomplete'
    }).then(response => {
        console.log(response);
    });
});

Category page

For category pages in most cases the facets parameter should be provided. Additionally categoryId (for Shopify) and categoryPath should be also provided.

Furthermore redirect & track should be enabled to automatically track searches to Nosto analytics & redirect if API returns a redirect request.

nostojs(api => {
    api.search({
        products: {
            categoryId: '12345',
            categoryPath: 'Pants',
            fields: ['name'],
            size: 10
        }
    }, {
        track: 'category',
        redirect: true
    }).then(response => {
        console.log(response);
    });
});

Session parameters

For some of the search features to work properly, such as personalised results and segments, the search function needs to be able to access information about the user's session from the front-end.

The search function of the JS API already includes session state automatically.

To get all session data the following snippet can be used:

nostojs(api => {
    api.getSearchSessionParams(options).then(response => {
        console.log(response);
    });
});

The results of this function should be passed to search query sessionParams parameter. In case search is called from backend, it should pass this data to backend (e.g. using form data).

The function accepts the following options:

Option
Default
Description

maxWait

2000

Maximum execution time in MS

cacheRefreshInterval

60000

Maximum cache time

Analytics

Tracking search events to analytics can be divided into three parts: search, search submit, search product click. These are user behaviours that should be tracked:

  • search submit (type = serp)

  • faceting, paginating, sorting (type = serp) or (type = category)

  • autocomplete input (type = autocomplete)

  • search results product click (type = serp), (type = autocomplete) or (type = category)

  • autocomplete keyword click (type = autocomplete)

  • category merchandising results (type = category)

JS API library provides tracking helpers for all of these cases.

Error Handling

On errors from api.search calls the error object contains a status field that has the HTTP response status which can be used to determine the cause of the error in addition to the error message. The ranges of the status codes used for errors are 400-499 and 500-599. For details on these error codes, go to HTTP response status codes

Search

User actions that lead to search results should be tracked with api.recordSearch() after search request is done:

  • search submit (type = serp) - user submits search query in autocomplete component and is landed to SERP

  • faceting, paginating, sorting (type = serp) or (type = category) - user adjusts current search results by filtering (e.g. brand), selecting search page, sorting results

  • autocomplete input (type = autocomplete) - user sees partial results while typing in search input

  • category merchandising results (type = category) - user sees specific category results when category is selected (category merchandising must be implemented)

You don't need to execute api.recordSearch()if you call api.search(query, { track: 'serp'|'autocomplete'|'category'}) function from JS API, becauseapi.search()already calls api.recordSearch() when track option is provided.

nostojs(async (api) => {

    const searchRequest = {
      query: "shoes",
      products: {
        sort: [{ field: "price", order: "asc" }],
        filter: [{ "field": "brand", "value": "Nike" }]
      }
    }
    const response = await sendGraphQlRequest(searchRequest)
    const searchResult = response.data.search

    api.recordSearch(
        type,
        searchRequest,
        searchResult,
        options
      )
})
Parameter
Description

type

Search type: serp, autocomplete, category

request

result

options (optional)

Record search options. Currently is accepts:

isKeyword: boolean - should be set when keyword in autocomplete is clicked (search is submitted via keyword)

Example:

api.recordSearch(
    "serp",
    {
        query: "shoes",
        products: {
            sort: [{ field: "price", order: "asc" }],
            filter: [{ "field": "brand", "value": "Nike" }]
        }
    },
    {
        products: {
            hits: [{ productId: "123" }, { productId: "124" }],
            fuzzy: true,
            total: 2,
            size: 2,
            from: 0
        }
    },
    {
        isKeyword: false
    }
)

Keyword click tracking

Example:

api.recordSearch(
    'autocomplete', 
    searchRequest,
    searchResult,
    { 
        isKeyword: true
    } 
)

Category results tracking

Example:

api.recordSearch(
    'category',
    {
        products: {
            categoryId: "123456",
            categoryPath: "Pants",
            sort: [{ field: "price", order: "asc" }]
            size: 24,
            from: 0,
        }
    },
    {
        products: {
            categoryId: "123456",
            categoryPath: "Pants",
            hits: [{ productId: "123" }, { productId: "124" }],
            fuzzy: true,
            total: 18,
            size: 24,
            from: 0
        }
    },
    {
        isKeyword: false
    }
)

The tracking metadata is primarily taken from the third parameter. The full request and full result objects must be provided in the api.recordSearch call instead of partials.

Search form submit

Search queries are categorised into two groups: organic and non-organic searches. In order to mark a search query as an organic search you need to call api.recordSearchSubmit(query: string). You should call it on search input submit only, before search request is sent.

nostojs(api => {
    const query = 'shoes'
    api.recordSearchSubmit(query)
})

Organic search - is a search query submitted through search input and which lead to SERP (search engine results page). Following faceting, paginating, sorting queries on organic query is also counted as organic.

Search product click

Product clicks should be tracked in autocomplete component, SERP, category page with api.recordSearchClick() by providing component (type), where click occurred, and clicked product data:

nostojs(api => {
    api.recordSearchClick(type, hit)
})
Parameter

type

Search type: serp, autocomplete, category

hit

Object containing productId and url, collected from clicked product

Example:

api.recordSearchClick(
    "autocomplete", 
    { productId: "123", url: "https://myshop.com/product123" }
)

Search add to cart additions

When shopping cart additions happen directly as part of the search, category or autocomplete results without a navigation to the product page, the api.recordSearchAddToCart() method should be called with the component (type), where addition occurred and the product data:

nostojs(api => {
    api.recordSearchAddToCart(type, hit)
})
Parameter

type

Search type: serp, autocomplete, category

hit

Object containing productId and url, collected from product

Example:

api.recordSearchAddToCart(
    "autocomplete", 
    { productId: "123", url: "https://myshop.com/product123" }
)

Event Tracking Requirements

When tracking events, adherence to the following criteria is essential for capturing detailed and valid data:

  • query parameter:

    • The query string is an essential component for event tracking.

    • If present, include products.sort to track sorting behavior.

    • If applicable, incorporate products.filter.

  • searchResults parameter:

    • products.hits array containing objects with a productId is mandatory.

    • products.total number to identify if the search has results.

    • For accurate pagination tracking, products.from and product.size must be included.

    • For identifying if the request was autocorrected include products.fuzzy.

    • For category requests, either products.categoryId or products.categoryPathis mandatory.

Tip: In case of API integration, use this example GraphQL partial query to integrate with the API and retrieve the necessary response data for precise event tracking.

query {
  search(
    accountId: "YOUR_ACCOUNT_ID"
    query: "green"
    products: { size: 10, from: 10 }
  ) {
    products {
      hits {
        productId
      }
      total
      size
      from
      fuzzy
      categoryId
      categoryPath
    }
  }
}

Search Form Submit

Bear in mind that search queries are split between organic and non-organic searches. To classify a search as organic, it is crucial to invoke api.recordSearchSubmit() upon the search input submission, before the actual search request is dispatched. This step is pivotal in ensuring the seamless tracking of organic searches through to the SERP.

Accurate Click Tracking

Tracking product clicks is fundamental for understanding user interaction. Use api.recordSearchClick() to monitor this actions correctly, specifying the type and relevant hit data.

In case of an SPA based integration the api.recordSearchClick calls should be complemented with Session API or api.createRecommendationRequest() usage to couple the search analytics events to generic Nosto events for accurate attribution.


⚠️
⚠️
💡
🚩
🔍
Full search API request
Full search API result

Implement Search results page

API Requests

Searching

For a basic search, it’s enough to provide accountId, query, and select fields that should be returned. You can control which product attributes to select through products.hits field. See all available fields.

Query

For example, if you want to return only productId and name, the query would be:

query {
  search(accountId: "YOUR_ACCOUNT_ID", query: "green") {
    products {
      hits {
        productId
        name
      }
      total
      size
      from
    }
  }
}

Playground example

Query parameters:

accountId

Nosto account ID

query

search query text

See all query parameters.

Pagination and size

Products offset parameter from is used for pagination functionality.

The default count of documents returned per page is size = 5, you can change it with products.size, and offset of products is controlled with products.from field:

Query

query {
  search(
    accountId: "YOUR_ACCOUNT_ID"
    query: "green"
    products: { size: 10, from: 10 }
  ) {
    products {
      hits {
        name
      }
      total
      size
      from
    }
  }
}

Playground example

Sorting

By default results are sorted by products relevance score.

To change the sorting, use the sort parameter, where you would specify any indexed field which should be sorted by, and order: asc for ascending and desc for descending.

By default, you should always sort by relevance. Only if the user selects a different sort method, a sorting rule should be used.

Query

query {
    search(
      accountId: "YOUR_ACCOUNT_ID"
      query: "green"
      products: {
        sort: [
          {
            field: "price"
            order: asc
          }
        ]
      }   
  ) {
      products {
        hits {
          productId
          name
          price
        }
      }
    }
  }

Playground example

Faceting

Facets help the user to find products more easily. Faceted navigation is normally found in the sidebar of a website and contains filters only relevant to the current search query. Facets are configured in the Nosto dashboard.

Terms facet
Stats facet

To use facet for a specific field you need to configure it in the Nosto dashboard first.

Terms facet

One of the facet types is type = terms. It returns list if common terms from found documents.

Query

Assume that we have configured facets for customFields.brandname and categories:

query {
  search(
    accountId: "YOUR_ACCOUNT_ID"
    query: "green"
  ) {
    products {
      facets {
        ... on SearchTermsFacet {
          id
          field
          type
          name
          data {
            value
            count
            selected
          }
        }
      }
    }
  }
}

Playground example

Response

{
  "data": {
    "search": {
      "products": {
        "facets": [
          {
            "id": "345678901abc",
            "field": "categories",
            "type": "terms",
            "name": "Categories",
            "data": [
              {
                "value": "/Shoes",
                "count": 30,
                "selected": false
              },
              {
                "value": "/Shoes/Sportswear",
                "count": 6,
                "selected": false
              }
            ]
          }
        ]
      }
    }
  }
}

Response parameters:

id

internal facet ID, used to select

field

facet field, should be used for

type

facet type, in this case terms

name

user friendly facet name configured in the

data.value

original facet value, it should be displayed in the user interface

data.count

shows how many products will be returned if you select this facet, it should be displayed in the user interface

data.selected

indicates if there is an active filter on this value

Stats facet

Stats facet returns minimum and maximum number field value from found documents. The most common usage is to render slider filter (e.g. price)/

Query

query {
    search(
      accountId: "YOUR_ACCOUNT_ID"
      query: "green"
  ) {
      products {
        facets {
          ... on SearchStatsFacet {
            id
            field
            type
            name
            min
            max
          }
        }
      }
    }
  }

GraphQL playground example

Response

{
  "data": {
    "search": {
      "products": {
        "facets": [
            {
                "id": "123456789abc",
                "field": "price",
                "type": "stats",
                "name": "Price",
                "min": 0.60,
                "max": 70.99
            }
        ],
      }
    }
  }
}

Response parameters:

name

user friendly facet name configured in the

terms

facet type, in this case stats

field

facet field, should be used for

id

internal facet ID, used to select

min

minimum field value for documents that match provided query

max

maximum field value for documents that match provided query

Filter

Filtering by terms facet, for example by Adidas, Converse brands:

Query

query {
  search(
    accountId: "YOUR_ACCOUNT_ID"
    query: "green"
    products: {
      size: 10
      filter: [{ field: "brand", value: ["Adidas", "Converse"] }]
    }
  ) {
    products {
      hits {
        productId
        name
      }
      facets {
        ... on SearchTermsFacet {
          id
          field
          type
          name
          data {
            value
            count
            selected
          }
        }
      }
    }
  }
}

GraphQL playground example

When filtering by multiple same field items, filters will be joined with OR operator and different fields with AND.

If you wish to have more facets, you should configure it in the Nosto dashboard first.

Filtering by stats field, for example by price:

query {
   search(
    accountId: "YOUR_ACCOUNT_ID"
    query: "green"
    products: {
      filter: [
        {
          field: "price",
          range: {lt: "60", gt: "50"}
        }
      ]
    }
  ) {
    products {
      hits {
        productId
        name
      }
      facets {
        ... on SearchStatsFacet {
          id
          field
          type
          name
          min
          max
        }
      }
    }
  }
}

GraphQL playground example

You can sort using these arguments: lt (less than), gt (greater than), lte (less than or equal to), gte (greater than or equal to).

Redirects

Redirects can be used to forward users to special pages depending on their search keywords. For example, users searching for shipping could be forwarded to https://example.com/shipping.html.

For API integrations GraphQL can only return the target URL. The actual browser redirect must be implemented by the merchant.

Query

query {
  search(
    accountId: "YOUR_MERCHANT_ID",
    query: "shipping"
  ) {
    redirect
    products {
      hits {
        name
      }
    }
    keywords {
      hits {
        keyword
      }
    }
  }
}

GraphQL playground example

Response

{
  "data": {
    "search": {
      "redirect": "https://example.com/shipping.html",
      "products": {
        "hits": []
      },
      "keywords": null
    }
  }
}

Price Formatting and Currency Display

You can request specific currency formatting settings for prices returned in the search results. This is done by specifying the currencyFormat parameter within the products input. The actual formatting details (like currency symbol, placement, decimal places) are then returned in the priceFormat field within the products object of the response.

Query

To select which pre-configured currency settings to retrieve, include the currencyFormat parameter within the products input. Additionally, ensure you request the priceFormat field in your query to receive these details.

query (
  $accountId: String,
  $products: InputSearchProducts,
) {
   search(
    accountId: $accountId
    products: $products
  ) {
     products {
      # This field will contain the details of the selected currency format
      priceFormat {
        currencySymbol
        placement
        decimalPlaces
        decimalSeparator
        thousandSeparator
      }
    }
  }
}

Variables Example:

{
  "accountId": "shopify-55872454679-538837015-fi",
  "products": {
    "currencyFormat": "EUR"
  }
}

Behavior and Error Handling:

  • If currencyFormat is not provided in the products input, the default currency format configured for the account will be used for the priceFormat field.

  • If currencyFormat is provided but corresponds to a currency for which no settings are configured, an error will be returned.

  • If currencyFormat is not provided and no default currency format exists for the account, an error will be returned.

Response Example:

{
  "data": {
    "search": {
      "products": {
        "priceFormat": {
          "currencySymbol": "€",
          "placement": "after",
          "decimalPlaces": 2,
          "decimalSeparator": ",",
          "thousandSeparator": " "
        }
      }
    }
  }
}

priceFormat Response Parameters:

These parameters describe how the prices should be formatted on the frontend based on the selected currencyFormat.

Name
Description

currencySymbol

The symbol for the currency (e.g., "$", "€").

placement

Indicates where the currency symbol is placed relative to the price ("before" or "after").

decimalPlaces

The number of decimal places to display for the price.

decimalSeparator

The character used to separate the decimal part of the price (e.g., ".", ",").

thousandSeparator

The character used to separate thousands in the price (e.g., ",", " ").

Session params

For features like personalised results and user segments to function effectively, the search function needs access to the user's session information from the front-end.

It's possible to get search session data using the JS API:

nostojs(api => {
    api.getSearchSessionParams().then(response => {
        console.log(response);
    });
});

The results of this function should be passed to search query sessionParams parameter. In case search is called from backend, it should pass this data to backend (e.g. using form data).

Analytics

Nosto Analytics

To analyze user behavior you need to implement tracking. This can be achieved using our JavaScript library. You need to implement the following methods with type = serp:

  • recordSearch to track search result interactions like filtering and pagination

  • recordSearchClick to track result clicks

Search engine configuration

Nosto Search engine is relevant out of the box and search API can be used without any initial setup. Nosto Dashboard can be used to further tune search engine configuration:

  • Searchable Fields - manage which fields are used for search and their priorities,

  • Facets - create facets (filtering options) for search results page,

  • Ranking and Personalization Ranking and Personalization - manage how results are ranked,

  • Synonyms, Redirects, and other search features are also managed through Nosto Dashboard (my.nosto.com).

specific facets in query
filtering
dashboard
dashboard
filtering
specific facets in query

For Headless

Nosto's GraphQL APIs can be used for simplified implementations for headless frontends. While we recommend using our JS API for use in headless environments, there are scenarios where a GraphQL based implementation might be more fruitful.

  • Using the API

  • Testing & Debugging

🚨Implementing Nosto over GraphQL will not allow you to leverage the entire Nosto suite. When Nosto is implemented over tagging and/or Javascript APIs it can modify web-pages automatically with Javascript, which means many things can work out of the box. For example inserting recommendations dynamically onto a webpage, rendering of templates, tracking of user behaviour. GraphQL instead is only an API which can be called from anywhere, so it cannot directly run Javascript or modify web-pages, instead those need to be implemented by the caller of the API. The following features are not available through GraphQL API:

  • Facebook Ads: As the pixel events aren't dispatched.

  • Content Personalization: As the GraphQL API only handles the personalization and not onsite experiences.

  • Popups: As the GraphQL API only handle the personalization and not onsite experiences.

  • AB-testing & dynamic placements: Because the current GraphQL API works with recommendation identifiers directly and not through placements.

Each customer who visits a site is uniquely identified with a session identifier. When a new customer comes to the site, a GraphQL session mutation call must be made to initiate a session. The resultant session identifier must be persisted and reused for all consecutive calls.

💡The session-duration is 30 minutes from the last activity.

We recommend using the following flow to creating and resuming sessions.

  1. Read the session-identifier from the cookie.

  2. If the session-identifier doesn't exist, initiate a new session and store the resultant session identifier in a cookie.

  3. Read the session identifier from the cookie, and leverage the mutations for the outlined page types.

Starting a new session

In order to start a new session when a session-identifier doesn't exist, you'll need to use the newSession mutation

mutation {
  newSession(referer: "https://google.com?q=shoes")
}

The newSession mutation will return a unique session-identifier that you must persist.

⚠️ How you persist the session-identifier is entirely dependant upon your implementation. For example, you can persist it into a cookie or even application storage.

Using a session

If you already have a session-identifier, you can pass that using the updateSession mutation.

The example below is a generic example of how you update a session and pass the appropriate event to Nosto but in practice, you'll be using one of the page-specific mutations as shown in the section "Implementing on pages".

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_PAGE
        target: "https://example.com"
      }
    }
  ) {
    id
  }
}

Implementing on pages

In order to use Nosto the different pages, you'll need to make the appropriate mutations for the different page types.

Every page-specific mutation requires you to pass the event for the specific page. These events are used to pass signals to Nosto's intelligence engine. Each of the page-specific mutations also allows you to fetch the recommendations for the given page type.

Sending the cart

When you mutate a session, it is imperative that you send the full cart contents.

If the current shopping cart is empty, this can be omitted.

mutation MySession {
  updateSession(by: BY_CID, id: "ad8f0d0e-1156-4df2-b385-10e03f8f8a44",
    params: {
      event: {
        type: VIEWED_PRODUCT
        target: "400"
      }
      cart: {
        items: [
          {
            productId: "100",
            skuId: "100-1",
            name: "#100",
            unitPrice: 199,
            priceCurrencyCode: "EUR",
            quantity: 1
          },
          {
            productId: "200",
            skuId: "200-1",
            name: "#200",
            unitPrice: 299,
            priceCurrencyCode: "EUR",
            quantity: 2
          }
        ]
      }
    }) {
      id
    }
  }
}

Adding to cart with attribution

If you want to add a new item to the cart, without any page changes you should pass a new parameter of skipEventsthis will prevent analytics to count new page view event while still adding product to the cart.

if the add to cart happens as a result of a click on a recommendation element, you must pass the attribution along with the event as the ref parameter.

mutation MySession {
  updateSession(by: BY_CID, id: "ad8f0d0e-1156-4df2-b385-10e03f8f8a44",
    params: {
      skipEvents: true
      event: {
        type: VIEWED_PAGE
        target: "9150249402681"
        ref: "frontpage-bestseller"
      }
      cart: {
        items: [
          {
            productId: "100",
            skuId: "100-1",
            name: "#100",
            unitPrice: 199,
            priceCurrencyCode: "EUR",
            quantity: 1
          }
        ]
      }
    }
  ) {
    id
  }
}

Sending the customer

When you mutate a session, it is imperative that you send the details of the currently logged-in customer. If no customer if currently logged in, this can be omitted.

mutation MySession {
  updateSession(by: BY_CID, id: "ad8f0d0e-1156-4df2-b385-10e03f8f8a44",
    params: {
      customer: {
        firstName: "John"
        lastName: "Doe"
        marketingPermission: true
        customerReference: "319330"
      }
      event: {
        type: VIEWED_PRODUCT
        target: "400"
      }
    }) {
      id
    }
  }
}

Sending attribution parameters

When navigating between pages, if the navigation happens as a result of a click on a recommendation element, you must pass the identifier as part of the route and on the next page load, read the attribution parameter and pass it along with the event as the ref parameter.

mutation MySession {
  updateSession(by: BY_CID, id: "ad8f0d0e-1156-4df2-b385-10e03f8f8a44",
    params: {
      event: {
        type: VIEWED_PRODUCT
        target: "400"
        ref: "frontpage-bestseller"
      }
    }) {
      id
    }
  }
}

⚠️ If you do not pass the attribution parameter, the recommendations statistics will be inaccurate but will not affect the quality of the recommendations.

Previewing the recommendations

All default the recommendation results are returned when the recommendations are enabled and the account is a live account. If you would like to preview the recommendations, all the recommendation fields accept a boolean isPreview parameter.

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_PAGE
        target: "https://example.com"
      }
    }
  ) {
    pages {
      forFrontPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400
      }) {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

⚠️ Any recommendation requests when the preview mode is enabled do not accrue towards the statistics or skew the product relations.

On your home page

Sending the event

In order to use the GraphQL session mutation to fetch recommendations for your home or front page, the event, in this case, must be VIEWED_PAGE and you should specify a fully qualified URL of the home page as the target.

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_PAGE
        target: "https://example.com"
      }
    }
  ) {
    pages {
      forFrontPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400
      }) {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

The pages query for home page also supports filtering recommendation for a specific Slot ID using the slotIds parameter in forFrontPage request, as shown below:

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_PAGE
        target: "https://example.com"
      }
    }
  ) {
    pages {
      forFrontPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400, slotIds: ["frontpage-nosto-1"]
      }) {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

Note: slotIds accepts an array of String parameters

Fetching Recommendations

The forFrontPage field will return the result of all the recommendations that are configured for the front page. When slotIds filtering is used, the result will only contain the recommendations for the specified Slot Ids.

On your Category pages

Sending the event

In order to use the GraphQL session mutation to fetch recommendations for your category page, the event, in this case, must be VIEWED_CATEGORY and you should specify a fully qualified category path of the current category. For example, if you have a category called "Dresses" under the category "Women", the FQCN would be "/Women/Dresses".

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_CATEGORY
        target: "/Shorts"
      }
    }
  ) {
    pages {
      forCategoryPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400
      }, category: "Shorts") {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

The pages query for category page also supports filtering recommendation for a specific Slot ID using the slotIds parameter in forCategoryPage request, as shown below:

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_CATEGORY
        target: "/Shorts"
      }
    }
  ) {
    pages {
      forCategoryPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400, slotIds: ["categorypage-nosto-1"]
      }) {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

Note: slotIds accepts an array of String parameters

Fetching Recommendations

The forCategoryPage field will return the result of all the recommendations that are configured for the category page. When slotIds filtering is used, the result will only contain the recommendations for the specified Slot Ids.

On your Product pages

Sending the event

In order to use the GraphQL session mutation to fetch recommendations for your search page, the event, in this case, must be VIEWED_PRODUCT and you should specify the product-identifier of the current product being viewed.

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_PRODUCT
        target: "11923861519"
        ref: "front-page-slot-1"
      }
    }
  ) {
    pages {
      forProductPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400
      }, product: "11923861519") {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

The pages query for product page also supports filtering recommendation for a specific Slot ID using the slotIds parameter in forProductPage request, as shown below:

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_PRODUCT
        target: "11923861519"
        ref: "front-page-slot-1"
      }
    }
  ) {
    pages {
      forProductPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400, slotIds: ["productpage-nosto-2"]
      }, product: "11923861519") {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

Fetching Recommendations

The forProductPage field will return the result of all the recommendations that are configured for the product page. When slotIds filtering is used, the result will only contain the recommendations for the specified Slot Ids.

On your search page

Sending the event

In order to use the GraphQL session mutation to fetch recommendations for your search page, the event, in this case, must be SEARCHED_FOR and you should specify the search term of the query.

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: SEARCHED_FOR
        target: "black shoes"
      }
    }
  ) {
    pages {
      forSearchPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400
      }, term: "black shoes") {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

The pages query for search page also supports filtering recommendation for a specific Slot ID using the slotIds parameter in forSearchPage request, as shown below:

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: SEARCHED_FOR
        target: "black shoes"
      }
    }
  ) {
    pages {
      forSearchPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400, slotIds: ["searchpage-nosto-3"]
      }, term: "black shoes") {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

Fetching Recommendations

The forSearchPage field will return the result of all the recommendations that are configured for the search page. When slotIds filtering is used, the result will only contain the recommendations for the specified Slot Ids.

On your cart/checkout page

Sending the event

In order to use the GraphQL session mutation to fetch recommendations for your cart or checkout page, the event, in this case, must be VIEWED_PAGE and you should specify a fully qualified URL of the cart page as the target.

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_PAGE
        target: "https://example.com/cart"
      }
    }
  ) {
    pages {
      forCartPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400
      }, value: 100) {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

The pages query for cart page also supports filtering recommendation for a specific Slot ID using the slotIds parameter in forCartPage request, as shown below:

mutation {
  updateSession(by: BY_CID, id: "5b1a481060b221115c4a251e",
    params: {
      event: {
        type: VIEWED_PAGE
        target: "https://example.com/cart"
      }
    }
  ) {
    pages {
      forCartPage(params: {
        isPreview: false, imageVersion:  VERSION_8_400_400, slotIds: ["cartpage-nosto-1"]
      }, value: 100) {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

Fetching Recommendations

The forCartPage field will return the result of all the recommendations that are configured for the front page. When slotIds filtering is used, the result will only contain the recommendations for the specified Slot Ids.

On the order page

Sending the event

In order to use the GraphQL session mutation to fetch recommendations for your cart or checkout page, you must use a different mutation as compared to the rest of the pages.

⚠️ The customer, in this case, is the details of the customer making the purchase.

mutation {
  placeOrder(by:BY_CID, id: "514421fce84abcb61bd45241", params: {
    customer: {
      firstName: "Mridang"
      lastName: "Agarwalla"
      email: "[email protected]"
      marketingPermission: false
    }
    order: {
      number: "25435"
      orderStatus: "paid"
      paymentProvider: "klarna"
      ref: "0010"
      purchasedItems: [
        {
          name: "Shoe"
          productId: "1"
          skuId: "11"
          priceCurrencyCode: "EUR"
          unitPrice: 22.43
          quantity: 1
        }
      ]
    }
  }) {
    id
    pages {
      forOrderPage(value: "25435", params: {
        imageVersion: VERSION_3_90_70,
        isPreview: true
      }) {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

The pages query for order page also supports filtering recommendation for a specific Slot ID using the slotIds parameter in forOrderPage request, as shown below:

mutation {
  placeOrder(by:BY_CID, id: "514421fce84abcb61bd45241", params: {
    customer: {
      firstName: "Mridang"
      lastName: "Agarwalla"
      email: "[email protected]"
      marketingPermission: false
    }
    order: {
      number: "25435"
      orderStatus: "paid"
      paymentProvider: "klarna"
      ref: "0010"
      purchasedItems: [
        {
          name: "Shoe"
          productId: "1"
          skuId: "11"
          priceCurrencyCode: "EUR"
          unitPrice: 22.43
          quantity: 1
        }
      ]
    }
  }) {
    id
    pages {
      forOrderPage(value: "25435", params: {
        imageVersion: VERSION_3_90_70,
        isPreview: true,
        slotIds: ["orderpage-test-1"]
      }) {
        divId
        resultId
        primary {
          productId
        }
      }
    }
  }
}

Fetching Recommendations

The forOrderPage field will return the result of all the recommendations that are configured for the order-confirmation page. When slotIds filtering is used, the result will only contain the recommendations for the specified Slot Ids.

Other page type fields are:

forNotFoundPage - 404 page

forLandingPage - landing page

forGeneralLayoutPage - general layout page

Implementing Search page

Configuration

To create a search application, call the init function with your configuration. This will create a new Preact application that renders on the specified contentCssSelector. It will also bind to the input element identified by the provided inputCssSelector and execute a search upon form submission.

index.js
import { init } from '@nosto/preact'

import serpComponent from './serp'

init({
    ...window.nostoTemplatesConfig,
    serpComponent,
    inputCssSelector: '#search',
    contentCssSelector: '#content',
    serpPath: '/search',
    serpPathRedirect: false,
    formCssSelector: '#search-form',
    formUnbindDelay: 1000, // 1 second
    serpUrlMapping: {
        query: 'q',
    },
    serpQuery: {
        products: {
            size: 20,
            from: 0,
        },
    },
})

Serp query parameter flavors

In the example above, we supply serp query parameters as an object. Additionally, the serpQuery parameter can also be supplied as a function. The function flavor can be used for building complex query parameters and provides access to other pre-defined configuration parameters. Section below shows an example of serpQuery supplied as a function which provides the product variation id by accessing the pre-defined variationId method from the default configuration.

index.js
import { init } from '@nosto/preact'

import serpComponent from './serp'

init({
    ...window.nostoTemplatesConfig,
    serpComponent,
    inputCssSelector: '#search',
    contentCssSelector: '#content',
    serpPath: '/search',
    serpPathRedirect: false,
    formCssSelector: '#search-form',
    formUnbindDelay: 1000, // 1 second
    serpUrlMapping: {
        query: 'q',
    },
    serpQuery() {
        return {
            products: {
                size: 20,
                from: 0,
                variationId: this.variationId()
            },
        }
    }
})

The full list of Configuration options is documented here

Search page path

When serpPath parameter is specified, the application will redirect to the specified search page after a search is conducted. Otherwise, the search will be rendered on the same page without changing the URL path.

Search page redirect

When serpPathRedirect parameter is set to true, the application after search submission will redirect the page to the search page specified in serpPath and fetch the page. Default behaviour will rewrite browser history only to the specified path, without fetching the page.

Unbinding existing search input

To prevent events from firing on an existing input, you need to provide the CSS selector of the form that the input is in to the initialization configuration. When optional fromCssSelector is passed, it will unbind the form and the elements inside from existing events. Additionally, formUnbindDelay in milliseconds as value can be passed to delay the unbinding functionality.

Serp component

The search results page component should render a full search page using the provided app state. A minimal example might look like this:

serp/index.js
import { useAppStateSelector, SerpElement } from '@nosto/preact'

export default () => {
    const { products, loading } = useAppStateSelector((state) => ({
        products: state.response.products,
        loading: state.loading,
    }))

    return (
        <div>
            {loading && <div>Loading...</div>}
            {products.total ? <div>
                {products.hits.map(hit => <SerpElement as="a" hit={hit}>
                    {hit.name}
                    {hit.price} 
                </SerpElement>)}
            </div> : <div>
                No results were found
            </div>}
        </div>
    )
}

Automatic URL Parameter Compression

When the compressUrlParameters flag is set to true, it automatically applies the URL parameter compression functions for filters, sort and pagination.

import { init } from '@nosto/preact'

import serpComponent from './serp'

init({
    ...window.nostoTemplatesConfig,
    serpComponent,
    inputCssSelector: '#search',
    contentCssSelector: '#content',
    serpPath: '/search',
    serpPathRedirect: false,
    serpUrlMapping: {
        query: 'q',
        'products.page': 'page'
    },
    compressUrlParameters: true,
    serpQuery: {
        products: {
            size: 20,
            from: 0,
        },
    },
})

@nosto/preact library has pre-built functions for changing search url format:

Description
Example

Pagination

Replaces from parameter with page number.

Before: /search?products.from=20&q=shorts After: /search?page=2&q=shorts

Sorting

Returns shorter sort parameters.

Before: /search?q=shorts&products.sort.0.field=price&products.sort.0.order=desc After: /search?q=shorts&products.sort=price~desc

Filtering

Compresses filter parameters. Multiple filter values are separated by a comma, which is encoded. This is because filter values can contain non-alphanumeric letters themselves.

Before: /search?q=shorts&products.filter.0.field=customFields.producttype&products.filter.0.value.0=Shorts&products.filter.0.value.1=Swim&products.filter.1.field=price&products.filter.1.range.0.gte=10&products.filter.1.range.0.lte=30 After: /search?q=shorts&filter.customFields.producttype=Shorts%7C%7CSwim&filter.price=10~30

Product thumbnails

Product thumbnails are supported via decorators that augment the product data returned by the Nosto Search service.

The following example shows modifications to the init call to make product thumbnails available in the result data:

import { init, thumbnailDecorator, priceDecorator } from "@nosto/preact"

init({
    ...window.nostoTemplatesConfig,
    ...
    serpQuery: {
        products: {
            fields: [
                ...
                // needed for thumbnailDecorator
                "imageHash"
            ],
            facets: ["*"],
            size: defaultConfig.serpSize,
            from: 0
        }
    },    
    hitDecorators: [
        thumbnailDecorator({ size: "9" })
    ]
})

The thumbnailDecorator takes a size argument and requires the following additional fields to be made available in the result set for accurate thumbnails:

  • imageHash for imageUrl thumbnails

  • thumbHash for thumbUrl thumbnails

  • alternateImageHashes for alternateImageUrls thumbnails

  • sku.imageHash for sku.imageUrl thumbnails

The supported sizes are

Code
Description

1

170x170 px

2

100x100 px

3

90x70 px

4

50x50 px

5

30x30 px

6

100x140 px

7

200x200 px

8

400x400 px

9

750x750 px

10

Original (Square)

11

200x200 px (Square)

12

400x400 px (Square)

13

750x750 px (Square)

The same mapping will also be attempted for SKU level data

Currency formatting

Currency formatting is implemented via the priceDecorator decorator function.

The priceDecorator utilizes the currency formatting definitions of the Nosto account to format prices into priceText and listPriceText fields, covering both product and SKU level data.

  • Include Required Fields

    • The fields required for this mapping are:

    • price will be formatted to priceText

    • listPrice will be formatted to listPriceText

    • priceCurrencyCode will be used as the currency code

  • Use the priceDecorator The priceDecorator is responsible for formatting prices into text fields using above mentioned fields.

A complete example of the Search-templates configuration:

import { init, priceDecorator } from "@nosto/preact";

init({
    ...window.nostoTemplatesConfig,
    ...
    serpQuery {
        products: {
            variationId: this.variationId(),
            fields: [
                // needed for priceDecorator
                "price", 
                "listPrice",
                "priceCurrencyCode",
            ],
            size: 20,
            from: 0
        }
    },
    hitDecorators: [
        priceDecorator()
    ]
});

Multi-Currency

To enable multi-currency functionality in search templates, follow these steps:

  • Enable Multi-Currency in Nosto Admin - Enabling multi-currency from the admin

  • Provide the variationId The variationId is used to specify the currency of the response price data - it should be included in the search query to ensure accurate price conversion.

Below is an example of how to include the variationId in your search query:

import { init } from "@nosto/preact";

init({
    ...window.nostoTemplatesConfig,
    ...
    serpQuery() {
        return {
            products: {
                variationId: this.variationId()
                ...
            }
        }
    }
});

Query parameter mapping

In addition to the compressUrlParameters flag the serpUrlMapping should be used to control the mapping from URL parameter keys to paths in the internal query object. The default looks like this:

serpUrlMapping: {
  query: "q",
  "products.filter": "filter",
  "products.page": "page",
  "products.sort": "sort"
}

The key is the internal path in the query model and the value is the query parameter name that should be used in the URL.

Features

Faceted navigation

Stats facet

The stats facet returns the minimum and maximum values of numerical fields from search results. This functionality is especially useful when creating interactive elements such as sliders and range selectors. For instance, a price slider can use these min-max values to define its adjustable range, providing a simple way for users to filter products within a specific price range. Similarly, these values are utilized in the RangeSelector to define the overall scope of range selections, allowing for the configuration of selection precision through the range size parameter.

Range Slider

Utilize the useRangeSlider hook to generate useful context for rendering range inputs. Additionally, employ the component to generate the interactive slider itself. These tools together facilitate the creation of dynamic and interactive range sliders for your application.

import { RangeSlider, useRangeSlider } from '@nosto/preact'

export default ({ facet }) => {
    const {
        min,
        max,
        range,
        updateRange
    } = useRangeSlider(facet.id)

    return  <div>
        <h2>{facet.name}</h2>
        <label>
            Min.
            <input type="number" value={range[0]} min={min} max={max} onChange={(e) => {
                const value = parseFloat(e.currentTarget.value) || undefined
                updateRange([value, range[1]])
            }}/>
        </label>
        <label>
            Max.
            <input type="number" value={range[1]} min={min} max={max} onChange={(e) => {
                const value = parseFloat(e.currentTarget.value) || undefined
                updateRange([range[0], value])
            }} />
        </label>
        <RangeSlider id={facet.id} />
    </div>
}

Range Selector

If you require an alternative method where values are selected through radio buttons rather than a slider, consider using useRangeSelector hook. This tool allows users to choose from predefined range intervals with radio buttons, offering a different interaction style.

The range size parameter in the useRangeSelector hook specifies the size of each interval in the range and determines the total number of range items displayed. Additionally, it automatically rounds the minimum value down to ensure intervals are aligned with the specified range size.

For example, if the minimum product price in the current catalog is 230, and the maximum product price is 1000, the range size of 200 will adjust the starting point to 200 and create intervals displayed under the "Price" filter as follows:

  • 200 - 400

  • 400 - 600

  • 600 - 800

  • 800 - 1000

import { useRangeSelector } from "@nosto/preact"
import { useState } from "preact/hooks"
import RangeInput from "./elements/RangeInput"
import Icon from "./elements/Icon"
import RadioButton from "./elements/RadioButton"

export default function RangeSelector({ facet }) {
    const {
        min,
        max,
        range,
        ranges,
        updateRange,
        handleMinChange,
        handleMaxChange,
        isSelected
    } = useRangeSelector(facet.id, 100)
    const [active, setActive] = useState(false)

    return (
        <li>
            <div>
                <ul>
                    {ranges.map(({ min, max, selected }, index) => {
                        return (
                            <li
                            >
                                <RadioButton
                                    key={index}
                                    value={`${min} - ${max}`}
                                    selected={selected}
                                    onChange={() => updateRange([min, max])}
                                />
                            </li>
                        )
                    })}
                    <div>
                        <div>
                            <label for={`ns-${facet.id}-min`}>
                                Min.
                            </label>
                            <RangeInput
                                id={`ns-${facet.id}-min`}
                                min={min}
                                max={max}
                                range={range}
                                value={range[0] ?? min}
                                onChange={e => handleMinChange(parseFloat(e.currentTarget.value) || min)}
                            />
                        </div>
                        <div>
                            <label for={`ns-${facet.id}-max`}>
                                Max.
                            </label>
                            <RangeInput
                                id={`ns-${facet.id}-max`}
                                min={min}
                                max={max}
                                range={range}
                                value={range[1] ?? max}
                                onChange={e => handleMaxChange(parseFloat(e.currentTarget.value) || max)}
                            />
                        </div>
                    </div>
                </ul>
            </div>
        </li>
    )
}

Terms facet

The terms facet returns field terms for all products found in the search. This feature analyzes the content of each product and extracts meaningful terms. These terms can then be used to filter or refine search results, providing users with a more accurate and targeted product search.

import { useActions } from '@nosto/preact'

export default ({ facet }) => {
    const { toggleProductFilter } = useActions()

    return <div>
        <h2>{facet.name}</h2>
        <ul>
            {facet.data?.map((value) => <li>
                <label>
                    {value.value}
                    <input
                        type="checkbox"
                        checked={value.selected}
                        onChange={(e) => {
                            e.preventDefault()
                            toggleProductFilter(
                                facet.field,
                                value.value,
                                !value.selected
                            )
                        }}
                    />
                </label>
                ({value.count})
            </li>)}
        </ul>
    </div>
}

You can use the toggleProductFilter function to toggle any filter value. This function will either add the filter value if it's not already applied or remove it if it's currently active, thus providing an efficient way to manipulate product filters in your application.

Pagination

Use the usePagination hook to generate useful context for rendering any desired pagination. Utilize the width parameter to adjust how many page options should be visible. Also, remember to scroll to the top on each page change to ensure a seamless navigation experience for users.

serp/pagination.jsx
import { usePagination, useActions } from '@nosto/preact'

export default () => {
    const pagination = usePagination({
        width: 5
    })
    const { updateSearch } = useActions()
    
    const createCallback = (from) => () => {
        updateSearch({
            products: {
                from,
            },
        })
        scrollTo(0, 0)
    }

    return (
        <ul>
            {pagination.prev && <li>
                <a
                    href="javascript:void(0)"
                    onClick={createCallback(pagination.prev.size)}
                >
                    prev
                </a>
            </li>}
            {pagination.first && <li>
                <a
                    href="javascript:void(0)"
                    onClick={createCallback(pagination.first.from)}
                >
                    {pagination.first.page}
                </a>
            </li>}
            {pagination.first && <li>...</li>}
            {pagination.pages.map((page) => <li class={page.current ? "active" : ""}>
                <a
                    href="javascript:void(0)"
                    onClick={createCallback(page.from)}
                >
                    {page.page}
                </a>
            </li>)}
            {pagination.last && <li>...</li>}
            {pagination.last && <li>
                <a
                    href="javascript:void(0)"
                    onClick={createCallback(pagination.last.from)}
                >
                    {pagination.last.page}
                </a>
            </li>}
            {pagination.next && <li>
                <a
                    href="javascript:void(0)"
                    onClick={createCallback(pagination.next.offset)}
                >
                    <span aria-hidden="true">
                        <i class="ns-icon ns-icon-arrow"></i>
                    </span>
                </a>
            </li>}
        </ul>
    )
}

Infinite Scroll

Nosto search-templates library provides a simple out-of-the-box solution to implement infinite scroll functionality. Simply wrapping your product rendering with the <InfiniteScroll> component is generally enough.

As the user scrolls the page down, the wrapper will detect it using the IntersectionObserver. If it is not supported by the user's browser, a 'load more' button will be shown instead.

serp.jsx
function Products() {
    const products = useAppStateSelector(state => state.response.products)

    return (
        <>
            {products.hits.map((hit, index) => {
                return <Product product={hit} key={hit.productId ?? index} />
            })}
        </>
    )
}

function SerpInfiniteScroll() {
    return (
        <InfiniteScroll>
            <Products />
        </InfiniteScroll>
    )
}

Persistent Search Cache

When using infinite scroll, consider enabling persistent search cache as well. When this feature is enabled, the latest search API response will be automatically cached and stored in the browser's session storage.

This improves the user experience significantly when the user navigates from a product details page back into the search results using the browser's 'back' function. The data necessary to display the products is already available, and the user will see the products immediately, without waiting for them to load again.

This feature is useful for both paginated and infinite scroll, but the benefits are significantly more visible with the latter.

import { init } from '@nosto/preact'

init({
    ...otherFields,
    persistentSearchCache: true,
})

Product actions

Since the code editor utilizes the Preact framework, it offers significant flexibility in customizing behavior or integrating the search page with existing elements on your site. For instance, you can implement actions such as 'Add to Cart', 'Wishlist', or 'Quick View'.

serp/Product.jsx
import { SerpElement } from '@nosto/preact'
import { useState } from 'preact/hooks'

export default ({ product }) => {
    const [addedToCart, setAddedToCart] = useState(false)
    
    return (
        <SerpElement
            as="a"
            hit={product}
        >
            <img src={product.imageUrl} />
            <div>
                {product.name}
            </div>
            <button
                // Allow the button to be clicked only once
                disabled={addedToCart}
                // Add the product to the cart when the button is clicked
                onClick={(event) => {
                    // Don't navigate to the product page
                    event.preventDefault()

                    // Update the button text and disable it
                    setAddedToCart(true)

                    // Add the product to the cart, this depends on the cart implementation
                    jQuery.post('/cart/add.js', {
                        quantity: 1,
                        id: product.productId,
                    })
                }}
            >
                // Show different text if product was added to the cart
                {addedToCart ? 'Added to cart' : 'Add to cart'}
            </button>
        </SerpElement>
    )
}

Handling native results

Nosto will attempt to display the original search results in case Nosto service is unavailable or can't be reached. In addition, the original products are made available for the SEO crawlers, improving the page's ranking in the search engines. To make it possible, it's recommended to hide the original search results instead of removing or blocking them.

The best approach is to add ns-content-hidden class name to the same element you are targeting with contentCssSelector or categoryCssSelector. This class name will be stripped away by Nosto automatically as soon as the script is initialized.

In addition, you should define CSS to hide the target element:

css
.ns-content-hidden {
    display: none;
    /* Or other styles as needed */
}

Analytics

Search automatically tracks to Google Analytics & Nosto Analytics when using SerpElement component.

export default ({ product }) => {
    return (
        <SerpElement as="a" hit={product}>
            {product.name}
        </SerpElement>
    )
}

Component parameters:

hit

Product object.

as

Element to render <SerpElement /> as. Recommended to use as="a". If a element is used, href attribute is added automatically.

onClick (optional)

Additional onClick callback (tracking callback is already implemented in the component).

The SerpElement component supports any other HTML attribute, e.g. class.

Fallback Functionality

The search page incorporates built-in fallback functionality, allowing users to customize the behavior in case the search service encounters issues. To activate this feature, modify the initialization configuration to include the fallback: true key-value pair.

Enabling Fallback

To enable fallback functionality, include the following code in the initialization configuration:

import { init } from '@nosto/preact'

init({
    ...window.nostoTemplatesConfig,
    fallback: true,
})

Once fallback is enabled, if the search request fails to retrieve data, the search functionality will be temporarily disabled for 10 minutes, and the original content Nosto has overridden will be restored.

The fallback: true setting only works out of the box if the path is the same for both the dedicated Nosto search page and the native search page, as well as for category pages.

If the paths differ, you must configure the serpFallback or categoryFallback function to ensure proper redirection. See: Customizing Fallback Location

Alternative Fallback Behaviour

If the behaviour described above is undesirable, the configuration supports an alternative option. Fallback mode can be set to fallback: 'legacy', in which case the user will see a page reload if the search request fails. After that, Nosto will not attempt to override the original search results or category pages for 10 minutes.

This behaviour has been the default fallback behaviour before August 20, 2024.

import { init } from '@nosto/preact'

init({
    ...window.nostoTemplatesConfig,
    fallback: 'legacy',
})

Customizing Fallback Location

Additionally, it's possible to customize the location to which users are redirected when the search functionality is unavailable. This customization involves specifying functions for both the search engine results page (SERP) and category pages.

SERP Fallback

To redirect users to a specific location when the search engine is down, define a function for serpFallback. This function accepts one parameter containing information about the current search query, including the query itself.

import { init } from '@nosto/preact'

init({
    ...window.nostoTemplatesConfig,
    fallback: true,
    serpFallback: (searchQuery) => {
        location.replace(`/search?q=${searchQuery.query}`);
    },
})

Category Fallback

Similarly, for category pages, define a function for categoryFallback. This function also accepts one parameter containing information about the current query, including the category ID or Path.

import { init } from '@nosto/preact';

init({
    ...window.nostoTemplatesConfig,
    fallback: true,
    categoryFallback: (query) => {
        location.replace(`/categories/${query.products.categoryId}`);
    },
});

By customizing these fallback locations, you can enhance the user experience by providing them with alternative navigation options if the search functionality is temporarily unavailable.

Search engine configuration

Nosto Search engine is relevant out of the box and search API can be used without any initial setup. Nosto Dashboard can be used to further tune search engine configuration:

  • Searchable Fields - manage which fields are used for search and their priorities,

  • Facets - create facets (filtering options) for search results page,

  • Ranking and Personalization Ranking and Personalization - manage how results are ranked,

  • Synonyms, Redirects, and other search features are also managed through Nosto Dashboard (my.nosto.com).