Upgrade from v2 to v3

This is for:

Developer

The Coveo Platform platform is constantly evolving. Headless v3 introduces changes and innovations that align with the latest evolution of the Coveo Platform.

Important
The following are breaking changes from Headless v2 to v3

Migration to Coveo Event Protocol

Coveo Event Protocol becomes the new default protocol. In other words, the Engine configration.analytics.analyticsMode default value is now next rather than legacy.

If you’re using the default value in v3 and aren’t ready to migrate to the new protocol, set the value to legacy to keep using the Coveo UA protocol.

Important

Only Coveo for Commerce supports EP at the moment. For every other kind of implementation, set analyticsMode to legacy when upgrading to v3.

const engine = buildSearchEngine({
    configuration: {
        // ...rest of configuration
        analytics: {analyticsMode: 'legacy'},
    }
})

Removal of analyticsClientMiddleware function

As part of the migration to support Coveo Event Protocol (EP), Headless won’t support the analyticsClientMiddleware property when EP is enabled.

Headless Version 2

const engine = buildSearchEngine({
    configuration: {
        // ...rest of configuration
        analytics: {
            analyticsClientMiddleware: (eventName: string, payload: Record<string, unknown>) => {
                // ...
            }
        }
    }
})

There’s no alternative when using EP, as EP is meant to be more streamlined, which results in cleaner data and more powerful machine learning models. When using the legacy Coveo UA protocol, you can continue using analyticsClientMiddleware.

Headless Version 3

const engine = buildSearchEngine({
    configuration: {
        // ...rest of configuration
        analytics: {
            analyticsMode: 'legacy',
            analyticsClientMiddleware: (eventName: string, payload: any) => {
                // ...
            }
        }
    }
})

Organization endpoints

Organization endpoints is a feature that improves separation of concerns, resiliency, and makes multi-region and data residency deployments smoother. Starting with Headless v3, the usage of organization endpoints will be enforced automatically, as opposed to optional in v2.

Headless Version 2

import {buildSearchEngine} from '@coveo/headless';

const engine = buildSearchEngine({
    configuration: {
        // ...
        organizationId: '<ORGANIZATION_ID',
        organizationEndpoints: getOrganizationEndpoints('<ORGANIZATION_ID')
    }
})

Headless Version 3

import {buildSearchEngine} from '@coveo/headless';

const engine = buildSearchEngine({
    configuration: {
        // ...
        organizationId: '<ORGANIZATION_ID',
    }
})

For HIPAA organizations, rather than specifying hipaa argument in the getOrganizationEndpoints function, set the environment property to hipaa in your engine configuration.

Headless Version 2

import {buildSearchEngine} from '@coveo/headless';

const engine = buildSearchEngine({
    configuration: {
        // ...
        organizationId: '<ORGANIZATION_ID',
        organizationEndpoints: getOrganizationEndpoints('<ORGANIZATION_ID', 'hipaa')
    }
})

Headless Version 3

import {buildSearchEngine} from '@coveo/headless';

const engine = buildSearchEngine({
    configuration: {
        // ...
        organizationId: '<ORGANIZATION_ID',
        environment: 'hipaa',
    }
})

For most implementations, this is the extent of the changes. If you were using the organizationEndpoints property to send requests to your own proxy that would then relay requests to Coveo APIs, however, more changes will be required. Headless v3 introduces the search.proxyBaseUrl, analytics.proxyBaseUrl, and commerce.proxyBaseUrl engine configuration options for such cases.

Headless Version 2

import {buildSearchEngine} from '@coveo/headless';

const engine = buildSearchEngine({
    configuration: {
        // ...
        organizationId: 'my-org-id',
        organizationEndpoints:
            {
                ...getOrganizationEndpoints('my-org-id'),
                search: 'https://myproxy.com/search',
            }
    }
})

Headless Version 3

import {buildSearchEngine} from '@coveo/headless';

const engine = buildSearchEngine({
    configuration: {
        // ...
        organizationId: 'my-org-id',
        search: {
            proxyBaseUrl: 'https://myproxy.com/search',
        },
    }
})

If you were using the getOrganizationEndpoints function for some other purpose, you can use the new getOrganizationEndpoint, getAdministrationOrganizationEndpoint, getSearchApiBaseUrl or getAnalyticsNextApiBaseUrl functions instead.

Headless Version 2

const organizationEndpoints: getOrganizationEndpoints('<ORGANIZATION_ID>');
const searchEndpoint = organizationEndpoints.search;
// ...

Headless Version 3

const searchEndpoint = getSearchApiBaseUrl('<ORGANIZATION_ID>');
// ...

platformUrl property

The platformUrl property, which was previously deprecated, has been removed from all engine configuration options. This property was originally used to specify Organization endpoints, but the organizationEndpoints property has since replaced it. As organization endpoints are now mandatory, the platformUrl property is no longer needed.

Node version support

The minimum version of Node.js supported by Headless is now 20.

This is strictly for clients bundling and building their front-end application through a Node based toolchain and including Headless as a project dependency.

Exports

Headless exports multiple entry points each meant to support different use cases and environments. To improve the inter-operability of Headless with different industry standard tools, and to guide those tools to properly use the appropriate entry points depending on the use case, Headless will start enforcing and specifying export fields in package.json. This means that implementation relying on non-public modules of the package will stop functioning.

Headless Version 3

// This will be blocked entirely
import {nonPubliclyDocumentedFunction} from '@coveo/headless/dist/nonPublicFile.js';

// Will throw an error, or won't compile
nonPubliclyDocumentedFunction();

Also, it means you need to set moduleResolution": "bundler" in your tsconfig.json file to access secondary entry points such as @coveo/headless/commerce or @coveo/headless/ssr. See TypeScript module resolution for more information.

moduleResolution in tsconfig.json when installing via npm

If you use Typescript, note that the node10/node module resolution is no longer supported. The classic module resolution, which was never supported, remains unsupported.

abab dependency

The deprecated abab npm package has been removed from Headless dependencies. If you were using it through Headless, you’ll need to include it in your project dependencies directly.

TestUtils

In v2, some internal TestUtils functions were exported, but weren’t meant for public use. Those functions are no longer exported in v3.

createAction, createAsyncThunk and createReducer

Those Redux Toolkit functions are no longer re-exported by Headless. If you’re using them, import then directly from Redux.

Modified behaviors

DidYouMean queryCorrectionMode

The queryCorrectionMode default value is now next rather than legacy. This means that the default behavior of the didYouMean controller will be to use a query suggestions model for query correction rather than the legacy index mechanism.

GeneratedAnswer sendFeedback

The feedback parameter of the sendFeedback method was changed. In v2, it could have either the GeneratedAnswerFeedback or GeneratedAnswerFeedbackV2 type, which were defined as follows:

Headless Version 2

export type GeneratedAnswerFeedback =
  | 'irrelevant'
  | 'notAccurate'
  | 'outOfDate'
  | 'harmful';

export type GeneratedAnswerFeedbackOption = 'yes' | 'unknown' | 'no';

export type GeneratedAnswerFeedbackV2 = {
  helpful: boolean;
  documented: GeneratedAnswerFeedbackOption;
  correctTopic: GeneratedAnswerFeedbackOption;
  hallucinationFree: GeneratedAnswerFeedbackOption;
  readable: GeneratedAnswerFeedbackOption;
  details?: string;
  documentUrl?: string;
};

In v3, only the GeneratedAnswerFeedback type is accepted, which was the previous GeneratedAnswerFeedback2.

Headless Version 3

export type GeneratedAnswerFeedbackOption = 'yes' | 'unknown' | 'no';

export type GeneratedAnswerFeedback = {
  helpful: boolean;
  documented: GeneratedAnswerFeedbackOption;
  correctTopic: GeneratedAnswerFeedbackOption;
  hallucinationFree: GeneratedAnswerFeedbackOption;
  readable: GeneratedAnswerFeedbackOption;
  details?: string;
  documentUrl?: string;
};

You must therefore adjust your code to use the new GeneratedAnswerFeedback type.

Headless Version 2

const feedback: GeneratedAnswerFeedback = 'irrelevant';
generatedAnswer.sendFeedback(feedback);

Headless Version 3

const feedback: GeneratedAnswerFeedback = {
    readable: 'unknown',
    correctTopic: 'unknown',
    documented: 'yes',
    hallucinationFree: 'no',
    helpful: false,
};
generatedAnswer.sendFeedback(feedback);

The undocumented logGeneratedAnswerFeedback action was also modified the same way, accepting only GeneratedAnswerFeedbackV2 input, which was renamed to GeneratedAnswerFeedback.

The undocumented logGeneratedAnswerDetailedFeedback action was removed in v3.

Types

The undocumented SearchBoxSuggestionsEvent type now takes in a search box controller and bindings as input.

Headless Version 2

const event: SearchBoxSuggestionsEvent,

Headless Version 3

const event: SearchBoxSuggestionsEvent<SearchBoxController, AnyBindings>,

Removals

Relevance Generative Answering (RGA) rephrase option

Many actions, properties, types and methods related to the Relevance Generative Answering rephrase option were removed or modified in v3, since the option itself is no longer supported.

  • All rephrase methods were removed from all *-generated-answer controllers.

  • The undocumented logRephraseGeneratedAnswer analytics action was removed.

  • The undocumented rephraseGeneratedAnswer search action cause was removed.

  • The GeneratedResponseFormat.answerStyle was removed from the GeneratedAnswer state.

  • The GeneratedAnswerStyle type was removed.

  • The undocumented updateResponseFormat action now only accepts the contentFormat option, and not the answer style.

Sub-packages

Both product-recommendations and product-listing sub-packages are removed in v3. Use the new commerce package instead. See Headless for commerce.

Properties and methods

The following were removed in v3:

  • buildCaseAssistQuickview, which was a duplicate export of buildQuickview has been removed.

  • buildCaseAssistInteractiveResult, which was a duplicate export of buildInteractiveResult has been removed.

  • browserPostLogHook, which used to be exposed in the engine configuration options, has been removed. It wasn’t doing anything.

  • The quickview onlyContentURL initialization option has been removed from Quickview and CaseAssistQuickview controllers because it was always set to true.

    Relatedly, the content state attribute has been removed from those controllers, as it was always empty.

  • The undocumented showMoreSmartSnippetSuggestion and showLessSmartSnippetSuggestion search page events have been removed.

  • CategoryFacet state properties parents and values have been removed. Use valuesAsTrees and selectedValueAncestry instead.

    While values was a flat list of all values, valuesAsTrees contains the root facet values, whose children, if any, are accessible via valuesAsTrees[i].children[j].

    selectedValueAncestry is similar to parents, but it also includes the selected value itself.

    Depending on your implementation, the changes may or may not be transparent.

    Headless Version 2

    private renderParents() {
        return (
            this.state.hasActiveValues && (
            <div>
                Filtering by: {this.renderClearButton()}
                {this.state.parents.map((parentValue, i) => {
                const isSelectedValue = i === this.state.parents.length - 1;
    
                return (
                    <span key={this.getUniqueKeyForValue(parentValue)}>
                    &rarr;
                    {!isSelectedValue ? (
                        <button
                        onClick={() => this.controller.toggleSelect(parentValue)}
                        >
                        {parentValue.value}
                        </button>
                    ) : (
                        <span>{parentValue.value}</span>
                    )}
                    </span>
                );
                })}
            </div>
            )
        );
    }
    
    private renderActiveValues() {
        return (
            <ul>
            {this.state.values.map((value) => (
                <li key={this.getUniqueKeyForValue(value)}>
                <button onClick={() => this.controller.toggleSelect(value)}>
                    {value.value} ({value.numberOfResults}{' '}
                    {value.numberOfResults === 1 ? 'result' : 'results'})
                </button>
                </li>
            ))}
            </ul>
        );
    }

    Headless Version 3

    private renderParents() {
        return (
            this.state.hasActiveValues && (
            <div>
                Filtering by: {this.renderClearButton()}
                {this.state.valuesAsTrees.map((parentValue, i) => {
                const isSelectedValue = i === this.state.valuesAsTrees.length - 1;
    
                return (
                    <span key={this.getUniqueKeyForValue(parentValue)}>
                    &rarr;
                    {!isSelectedValue ? (
                        <button
                        onClick={() => this.controller.toggleSelect(parentValue)}
                        >
                        {parentValue.value}
                        </button>
                    ) : (
                        <span>{parentValue.value}</span>
                    )}
                    </span>
                );
                })}
            </div>
            )
        );
    }
    
    private renderActiveValues() {
        return (
            <ul>
            {this.state.selectedValueAncestry.map((value) => (
                <li key={this.getUniqueKeyForValue(value)}>
                <button onClick={() => this.controller.toggleSelect(value)}>
                    {value.value} ({value.numberOfResults}{' '}
                    {value.numberOfResults === 1 ? 'result' : 'results'})
                </button>
                </li>
            ))}
            </ul>
        );
    }

Actions

logExpandToFullUI parameters

The LogExpandToFullUI action no longer uses the caseId and caseNumber parameters. They are rather fetched automatically from the Headless engine state.

The fullSearchComponentName and triggeredBy parameters have been removed. They had no effect.

InsightCaseContextSection actions

These undocumented actions have been removed.

Types

The undocumented CommerceSearchBoxSuggestionsEvent type has been removed in V3. If you were using this type, switch to the undocumented SearchBoxSuggestionsEvent type, which now accepts a search box controller and bindings as input.

Headless Version 2

const event: SearchBoxSuggestionsEvent,

Headless Version 3

const event: SearchBoxSuggestionsEvent<SearchBoxController, AnyBindings>,