Step 2: USE_FUNCTION Backend

The USE_FUNCTION action extends Yext Functions to Search by allowing developers to activate a TypeScript function and forward the return value to the Search API. We’ll use the example of fetching stock prices.

Write the Function

Follow the Getting Started with Yext Functions guide to create your function. Here’s what the function might look like:

export interface QueryPayload {
  input: string,            // The query string itself
  context?: Object,         // Context object passed from the front-end
  locale: string,           // The locale (language) of the search
  experienceKey: string,    // The experience key
  verticalKey?: string,     // Only present on vertical search
  referrerPageUrl?: string  // The page the user came from
  latLong?: LatLong         // The user’s location bias, if provided
}

export interface LatLong {
  lat: number,
  lng: number,
}

export const fetchStockData = async (query: QueryPayload) => {
  const { input } = query;
  const tickerRegex = /[A-Z]{4}/;
  const isTicker = tickerRegex.test(input);
  if (input && isTicker) {
    const ticker = input.match(tickerRegex)![0];
    const date = new Date();
    date.setDate(date.getDate() - 1);
    // Find the most recent week day before today
    const day = date.getDay();
    const diff = date.getDate() - day + (day === 0 ? -6 : 1);
    date.setDate(diff);
    const lastWeekdayString = date.toISOString().split('T')[0];
    date.setDate(date.getDate() - 15);
    const prevString = date.toISOString().split('T')[0];
    const dailyUrl = `https://api.polygon.io/v1/open-close/${ticker}/${lastWeekdayString}?adjusted=true&apiKey=${process.env.POLYGON_API_KEY}`
    const graphUrl = `https://api.polygon.io/v2/aggs/ticker/${ticker}/range/1/day/${prevString}/${lastWeekdayString}?apiKey=${process.env.POLYGON_API_KEY}`
    const tickerUrl = `https://api.polygon.io/v3/reference/tickers/${ticker}?apiKey=${process.env.POLYGON_API_KEY}`
    const dailyRes = await fetch(dailyUrl).then(res => res.json());
    const graphRes = await fetch(graphUrl).then(res => res.json());
    const tickerRes = await fetch(tickerUrl).then(res => res.json());
    return {
      graph: graphRes,
      daily: dailyRes,
      ticker: tickerRes,
    }
  } else {
    return {
      status: "No Stock Data Found"
    }
  }
};

For this example to work, you’ll need a Polygon API key env variable.

You’ll notice that the TS function takes one argument of the type QueryPayload. This is a special interface that contains all of the data that the Search API has about each search, such as the page the user came from or the search query they entered. Making these additional inputs available to the function gives the function’s author important context about the search.

For example, a developer might want to attach user data to the context object on the front-end and then use that data within their function to look up information specific to the user. Or they might want to change the behavior of their function based on the locale of the query.

Test the Function

You’ll want to test the function you wrote above to ensure it runs properly. Follow the Local Function Development with Deno guide to learn how.

In our stock price example above, we used the code here to test our function.

Write the Query Rule

Write a query rule in the Search backend JSON editor with the action USE_FUNCTION. For our stock price example above, if the search term contains a four letter stock symbol, we want to use the function we wrote above. The query rule would look like:

{
   "rules": [
      {
         "criteria": {
            "searchTermMatchesRegex": "[A-Z]{4}]"
         },
         "actions": [
            {
               "actionType": "USE_FUNCTION",
               "plugin": "stockDataPlugin",
               "function": "fetchStockData",
               "key": "stockData"
            }
         ]
      }
   ]
}

Here’s what each of these inputs does:

  • plugin: Plugins are collections of TypeScript files that are defined as CaC resources in a Yext account. Each plugin has a unique ID and can contain many functions.
  • function: The individual TypeScript function that is triggered by this query rule. In our example, you see the fetchStockData function is defined above.
  • key: This is a new property that is required for any action that returns data in the API response. This is necessary because the front-end needs to know which action the data came from so that it can react appropriately.

Timeout

Particularly if the function is making network calls, it will be hard to predict how long its invocation might take. Since we don’t want it slowing down the entire response, developers can add an optional timeout, defined in milliseconds, to the rule, like so:

{
   "rules": [
      {
         "criteria": { ... },
         "actions": [
            {
               "actionType": "USE_FUNCTION",
               "plugin": "stockDataPlugin",
               "function": "fetchStockData",
               "key": "stockData",
               "timeout": 250,
            }
         ]
      }
   ]
}

If the function invocation takes any longer than this, the request proceeds without it and adds an indication to the API response that the rule has exceeded the timeout.

Async Functions

When using functions, a developer may not necessarily want to forward the result of the function back to the front-end. For example, maybe a customer wants to programmatically create a Zendesk ticket every time a customer expresses an angry sentiment.

For this, they can make their action asynchronous by adding "async": true to their rule like so:

{
   "rules": [
      {
         "criteria": { ... },
         "actions": [
            {
               "actionType": "USE_FUNCTION",
               "plugin": "stockDataPlugin",
               "function": "fetchStockData",
               "key": "stockData",
               "async": true,
            }
         ]
      }
   ]
}

With asynchronous query rules, nothing is forwarded to the front-end, and the function is not subject to the normal timeout.