Skip to content

Handling Redirects

When redirects are enabled, the Unified Search response may include an action object instructing the frontend to navigate the user to a specific page instead of displaying search results.

Detecting a redirect

Check for the presence of action.redirect in the search response. When present, redirectType states which kind of redirect fired, and exactly one of filters, relativeUrl, and absoluteUrl carries the payload:

redirectType Populated member Target
Filter filters A page derived from the product catalog — a PLP or a PDP
RelativeUrl relativeUrl A page on the shop itself, such as /campaign/black-friday
AbsoluteUrl absoluteUrl A page outside the shop, such as https://external-customer-support/faq

Switch on redirectType rather than probing for a populated member, and treat an unrecognized value as "no redirect" so that a redirect kind introduced in a future release cannot break the storefront.

The contract change is non-breaking

filters is still populated for exactly the redirect kinds that existing integrations already handle, and redirectType may be ignored, so no integration has to change to keep working as before. filters is absent only for a URL redirect, which fires only after an administrator has configured a URL phrase mapping and switched urlRedirectsEnabled on. Add support for the URL kinds before enabling the setting for a segment.

Response with a field-value redirect

{
    "action": {
        "redirect": {
            "redirectType": "Filter",
            "filters": {
                "CategoryIds": "42"
            }
        }
    },
    "originalPhrase": "running shoes",
    "usedPhrase": "running shoes",
    "products": [],
    "totalProducts": 0
}

Response with a relative URL redirect

{
    "action": {
        "redirect": {
            "redirectType": "RelativeUrl",
            "relativeUrl": "/openingHours"
        }
    },
    "originalPhrase": "opening hours",
    "usedPhrase": "opening hours",
    "products": [],
    "totalProducts": 0
}

Response with an absolute URL redirect

{
    "action": {
        "redirect": {
            "redirectType": "AbsoluteUrl",
            "absoluteUrl": "https://mypage.com/stuff"
        }
    },
    "originalPhrase": "customer service",
    "usedPhrase": "customer service",
    "products": [],
    "totalProducts": 0
}

Filter keys

When redirectType is Filter, the filters dictionary maps field names to target identifiers. The key tells you the type of redirect:

Filter key Redirect type Target page
CategoryIds Category Product listing page (PLP) filtered by category
ProductId Product name Product detail page (PDP)
SkuId SKU ID Product detail page (PDP)
SkuNo SKU number Product detail page (PDP)
(custom attribute fieldId) Custom attribute PLP filtered by the attribute value

Implementation

The general pattern for handling redirects is:

  1. Perform the search request
  2. Check if action.redirect exists in the response
  3. If present, branch on redirectType: map the filters to a URL in your application, route to the relative URL, or navigate to the absolute URL
Vanilla JavaScript
async function handleSearch(phrase) {
    const response = await fetch('/api/search', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            segmentId: 'b2c-dk-en',
            scopeId: 'full-search',
            phrase: phrase
        })
    });

    const result = await response.json();

    // Check for redirect action
    if (result.action?.redirect && handleRedirect(result.action.redirect)) {
        return;
    }

    // No redirect — display search results normally
    displayResults(result);
}

function handleRedirect(redirect) {
    switch (redirect.redirectType) {
        case 'Filter':
            // A page in the catalog — resolve the filters against your own routing
            navigate(mapFiltersToUrl(redirect.filters));
            return true;
        case 'RelativeUrl':
            // A page on the shop — stay inside the storefront
            navigate(redirect.relativeUrl);
            return true;
        case 'AbsoluteUrl':
            // A page outside the shop — leave the storefront
            window.location.assign(redirect.absoluteUrl);
            return true;
        default:
            // An unknown redirect kind — render the results normally
            return false;
    }
}

function navigate(url) {
    // Use your framework's client-side router where one is available
    window.location.href = url;
}

function mapFiltersToUrl(filters) {
    if (filters.CategoryIds) {
        return `/category/${filters.CategoryIds}`;
    }
    if (filters.ProductId || filters.SkuId || filters.SkuNo) {
        const productId = filters.ProductId || filters.SkuId || filters.SkuNo;
        return `/product/${productId}`;
    }
    // Custom attribute — build a filtered PLP URL
    const [attribute, value] = Object.entries(filters)[0];
    return `/products?${attribute}=${encodeURIComponent(value)}`;
}

Note

The mapFiltersToUrl and navigate functions are specific to your frontend's URL structure and routing. Adapt them to match your application. The relative and absolute distinction is carried in the contract precisely so that the client does not have to parse the URL to decide how to navigate.

Redirect behavior depends on both the search type and the kind of redirect:

Search type Field-value redirect URL redirect
Full search Returns only the action object — no products, content, or other results. The frontend should navigate immediately. Returns only the action object. The frontend should navigate immediately.
Quick search Returns limited results and no action object. Products are fetched using the redirect filters, so they already match the redirect target. Returns only the action object — no products, no content, and zero totals, because nothing in the index corresponds to a URL.

For full search, always check for redirects before rendering results. For a field-value redirect in quick search, no handling is required — the products in the dropdown are already a preview of the redirect target.

Quick search can now return a redirect action

Quick search previously never returned action.redirect; it applied the redirect filters internally and returned products instead. A URL redirect has nothing to apply against the index, so it is returned as an action in quick search as well. An integration that only inspected the action on full search must now handle it in quick search too, and must not interpret the empty quick search response as "no results".

Handle it differently from full search. The user is still typing in quick search, so navigating away unprompted is disruptive — present the destination in the dropdown as something the user can click, and leave the navigation itself to full search or to that click. Reusing a full-search handler such as handleRedirect above for quick search would navigate on a keystroke.

Quick search with a field-value redirect

When quick search detects a field-value redirect, it follows the redirect internally and returns matching products, without an action object:

Quick search response
1
2
3
4
5
6
7
8
9
{
    "products": [
        { "id": "prod-1", "name": "Trail Runner Pro" },
        { "id": "prod-2", "name": "Road Runner Lite" }
    ],
    "totalProducts": 87,
    "originalPhrase": "running shoes",
    "usedPhrase": "running shoes"
}

Quick search with a URL redirect

Quick search response
{
    "action": {
        "redirect": {
            "redirectType": "RelativeUrl",
            "relativeUrl": "/openingHours"
        }
    },
    "products": [],
    "totalProducts": 0,
    "originalPhrase": "opening hours",
    "usedPhrase": "opening hours"
}

When redirects do not trigger

Redirects are skipped when:

  • No search phrase is provided
  • The user has applied filters or facets (not a pristine search)
  • The search phrase is in the excluded phrases list
  • The search phrase does not match any configured redirect source or phrase mapping
  • urlRedirectsEnabled is off in the published redirect settings — a URL phrase mapping is then ignored, and the phrase falls through to the automatic redirect resolution

The following apply to field-value redirects only, since they resolve against the product index:

  • The matched entity is ambiguous (e.g., a category name exists in multiple category paths)
  • No products exist for the matched entity
  • For PDP redirects: more than one unique product matches

Admin API reference

Redirect settings are managed per segment and go through the publication flow.

Route migration

All redirect settings endpoints have moved from /redirect-settings to /redirects/settings. If your integration uses the old routes, update them to the new paths below.

Get current settings

GET /segment/{segmentId}/redirects/settings
Response
{
    "categoryEnabled": true,
    "productNameEnabled": false,
    "skuIdEnabled": true,
    "skuNoEnabled": false,
    "urlRedirectsEnabled": true,
    "customAttributes": [
        {
            "fieldName": "brand",
            "displayName": "Brand"
        }
    ]
}

Update settings

POST /segment/{segmentId}/redirects/settings
Request body
1
2
3
4
5
6
7
8
{
    "categoryEnabled": true,
    "productNameEnabled": true,
    "skuIdEnabled": true,
    "skuNoEnabled": false,
    "urlRedirectsEnabled": true,
    "customAttributes": ["brand"]
}

Response: 204 No Content

Field Type Default Description
urlRedirectsEnabled boolean false Allows phrase mappings to redirect to a relative or absolute URL. Unlike the other toggles it does not enable an automatic resolution against the index — a URL can only ever come from an explicit phrase mapping. Turning it off does not delete existing URL mappings.

Note

After updating redirect settings, you must publish the segment for changes to take effect in live search.

Get available custom attributes

GET /segment/{segmentId}/redirects/settings/available-attributes

Returns all string-type product attributes that can be used for redirect configuration:

Response
[
    {
        "fieldName": "brand",
        "displayName": "Brand"
    },
    {
        "fieldName": "material",
        "displayName": "Material"
    }
]

Redirect settings error responses

Status Condition
400 Bad Request Duplicate custom attributes in request, or custom attributes not found in product fields
404 Not Found Segment does not exist
429 Too Many Requests Rate limit exceeded

Excluded phrases API

Excluded phrases prevent specific search phrases from triggering redirects. See Excluded search phrases for the concept overview.

Create an excluded phrase

POST /segment/{segmentId}/redirects/excluded-phrases
Request body
1
2
3
{
    "searchPhrase": "sale"
}
Response — 201 Created
1
2
3
4
{
    "excludedPhraseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "searchPhrase": "sale"
}
Status Condition
400 Bad Request Search phrase is empty or already exists (case-insensitive)
404 Not Found Segment does not exist

Get an excluded phrase

GET /segment/{segmentId}/redirects/excluded-phrases/{excludedPhraseId}
Response — 200 OK
1
2
3
4
{
    "excludedPhraseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "searchPhrase": "sale"
}

Delete an excluded phrase

DELETE /segment/{segmentId}/redirects/excluded-phrases/{excludedPhraseId}

Response: 204 No Content

Search excluded phrases

POST /segment/{segmentId}/redirects/excluded-phrases/search
Request body
1
2
3
4
5
6
{
    "query": "sa",
    "orderBy": "SearchPhraseAscending",
    "maxResults": 25,
    "offset": 0
}
Field Type Default Description
query string Optional filter by phrase substring
orderBy string SearchPhraseAscending SearchPhraseAscending or SearchPhraseDescending
maxResults integer 25 Number of results per page (1–500)
offset integer 0 Number of results to skip
Response — 200 OK
{
    "excludedPhrases": [
        {
            "excludedPhraseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "searchPhrase": "sale"
        },
        {
            "excludedPhraseId": "9dc45f64-5717-4562-b3fc-2c963f66afa6",
            "searchPhrase": "sample"
        }
    ],
    "totalHits": 2
}

Phrase mappings API

Phrase mappings allow you to manually map a search phrase to a specific redirect target. See Phrase mappings for the concept overview.

A mapping targets either a field value or a URL, stated by redirectType:

Field Type Default Description
searchPhrase string The phrase that triggers the redirect (case-insensitive)
redirectType string FieldValue FieldValue, RelativeUrl, or AbsoluteUrl
fieldName string The redirect field. Required when redirectType is FieldValue, and must not be set otherwise.
fieldValueId string The value within that field. Required when redirectType is FieldValue, and must not be set otherwise.
url string The target URL. Required when redirectType is RelativeUrl or AbsoluteUrl, and must not be set otherwise.

Create a phrase mapping

POST /segment/{segmentId}/redirects/phrase-mappings
Request body — field value target
1
2
3
4
5
6
{
    "searchPhrase": "sneakers",
    "redirectType": "FieldValue",
    "fieldName": "CategoryIds",
    "fieldValueId": "42"
}
Response — 201 Created
1
2
3
4
5
6
7
{
    "phraseMappingId": "7ab85f64-5717-4562-b3fc-2c963f66afa6",
    "searchPhrase": "sneakers",
    "redirectType": "FieldValue",
    "fieldName": "CategoryIds",
    "fieldValueId": "42"
}
Request body — relative URL target
1
2
3
4
5
{
    "searchPhrase": "opening hours",
    "redirectType": "RelativeUrl",
    "url": "/openingHours"
}
Response — 201 Created
1
2
3
4
5
6
{
    "phraseMappingId": "5cd85f64-5717-4562-b3fc-2c963f66afa6",
    "searchPhrase": "opening hours",
    "redirectType": "RelativeUrl",
    "url": "/openingHours"
}

fieldName and fieldValueId are omitted for a URL mapping, and url is omitted for a field value mapping.

Status Condition
400 Bad Request The payload does not match redirectType: a field target is missing fieldName or fieldValueId, a URL target is missing url, or a target member belonging to the other kind is set
400 Bad Request fieldName is not a valid redirect field for the segment
400 Bad Request redirectType is RelativeUrl or AbsoluteUrl while urlRedirectsEnabled is off for the segment
400 Bad Request The URL format is invalid: a relative URL must start with a single /, and an absolute URL must be a well-formed http or https URL including a host. Neither may contain whitespace or control characters.
404 Not Found Segment does not exist
409 Conflict Search phrase already exists (case-insensitive)

Note

The URL toggle is enforced by the API, not only by the admin UI. Enable urlRedirectsEnabled through Update settings before creating a URL mapping.

Get a phrase mapping

GET /segment/{segmentId}/redirects/phrase-mappings/{phraseMappingId}
Response — 200 OK
1
2
3
4
5
6
7
{
    "phraseMappingId": "7ab85f64-5717-4562-b3fc-2c963f66afa6",
    "searchPhrase": "sneakers",
    "redirectType": "FieldValue",
    "fieldName": "CategoryIds",
    "fieldValueId": "42"
}

Delete a phrase mapping

DELETE /segment/{segmentId}/redirects/phrase-mappings/{phraseMappingId}

Response: 204 No Content

Search phrase mappings

POST /segment/{segmentId}/redirects/phrase-mappings/search
Request body
1
2
3
4
5
6
{
    "query": "sneak",
    "orderBy": "SearchPhraseAscending",
    "maxResults": 25,
    "offset": 0
}
Field Type Default Description
query string Optional filter by phrase substring
orderBy string SearchPhraseAscending SearchPhraseAscending or SearchPhraseDescending
maxResults integer 25 Number of results per page (0–500)
offset integer 0 Number of results to skip
Response — 200 OK
{
    "phraseMappings": [
        {
            "phraseMappingId": "5cd85f64-5717-4562-b3fc-2c963f66afa6",
            "searchPhrase": "opening hours",
            "redirectType": "RelativeUrl",
            "url": "/openingHours"
        },
        {
            "phraseMappingId": "7ab85f64-5717-4562-b3fc-2c963f66afa6",
            "searchPhrase": "sneakers",
            "redirectType": "FieldValue",
            "fieldName": "CategoryIds",
            "fieldValueId": "42"
        }
    ],
    "totalHits": 2
}

Phrase mapping fields API

These endpoints help discover which redirect fields and values are available for phrase mappings. They apply to FieldValue mappings only — a URL mapping has no field, and its target is supplied directly as url.

Get available fields

Returns redirect fields that can be used as phrase mapping targets. Only category and enabled custom string attributes are returned — product name, SKU ID, and SKU number are not supported for phrase mappings.

POST /segment/{segmentId}/redirects/phrase-mappings/fields/available
Response — 200 OK
[
    {
        "fieldName": "CategoryIds",
        "displayName": "Category",
        "fieldValueType": "Category"
    },
    {
        "fieldName": "brand",
        "displayName": "Brand",
        "fieldValueType": "Values"
    }
]
Field Type Description
fieldName string The field identifier to use in phrase mappings
displayName string Human-readable field name
fieldValueType string Category or Values — indicates the type of values this field contains

Resolve field display names

Resolves field name and value ID pairs to their display names. Useful for showing human-readable labels in the admin UI.

POST /segment/{segmentId}/redirects/phrase-mappings/fields/resolve
Request body
1
2
3
4
5
6
[
    {
        "fieldName": "CategoryIds",
        "fieldValueId": "42"
    }
]
Response — 200 OK
1
2
3
4
5
6
7
8
[
    {
        "fieldName": "CategoryIds",
        "fieldValueId": "42",
        "fieldDisplayName": "Category",
        "fieldValueDisplayName": "Running Shoes"
    }
]

Search field values

Searches for available values within a specific redirect field. For category fields, this returns categories with breadcrumb-style details.

POST /segment/{segmentId}/redirects/phrase-mappings/fields/{fieldName}/search
Request body
1
2
3
4
5
{
    "phrase": "running",
    "offset": 0,
    "maxResults": 50
}
Response — 200 OK
{
    "fieldName": "CategoryIds",
    "fieldValues": [
        {
            "fieldValueId": "42",
            "name": "Running Shoes",
            "details": "Sports > Footwear > Running Shoes"
        },
        {
            "fieldValueId": "78",
            "name": "Running Apparel",
            "details": "Sports > Clothing > Running Apparel"
        }
    ],
    "totalHits": 2
}
Field Type Description
fieldValueId string The value ID to use in phrase mappings
name string Display name of the value
details string Additional context (e.g., category breadcrumb path). May be null.

Migration from external redirects

If you currently handle redirects outside of Ecommerce Search (e.g., in a middleware or frontend layer):

  1. Enable redirect settings for the relevant segment via the Admin API
  2. Publish the segment to activate the settings
  3. Verify using the Playground in the admin UI to test specific search phrases
  4. Update your search handler to check for action.redirect in responses
  5. Remove external redirect logic once ECS-native redirects are confirmed working

Troubleshooting

Issue Cause Solution
Redirect not triggering Settings not published Publish the segment after changing redirect settings
Redirect not triggering Search has filters applied Redirects only fire on pristine searches (no user-applied filters)
Wrong category redirect Ambiguous category name Ensure category names are unique across category paths
No PDP redirect Multiple products match PDP redirects require exactly one unique product match
Redirect for wrong attribute Attribute not configured Verify the custom attribute is listed in redirect settings
Redirect triggers for excluded phrase Settings not published Publish the segment after adding excluded phrases
Phrase mapping not working Settings not published Publish the segment after creating phrase mappings
Phrase mapping rejected Duplicate search phrase Each search phrase can only have one mapping (case-insensitive)
URL redirect not triggering urlRedirectsEnabled is off, or the segment is not published Enable the setting and publish the segment
URL mapping rejected on create urlRedirectsEnabled is off Enable the setting before creating the mapping — the API enforces the toggle
Quick search shows no products for a URL redirect Expected behavior A URL has no products in the index; read the target from the action object and offer it in the dropdown instead of rendering the empty result set