Direct Answers | Yext Hitchhikers Platform

light bulb
Advanced Search Tier Feature
Direct answers are only available on the Advanced Search tier. Check out the Search Tiers reference doc to learn more about the features that are available in each tier.

The DirectAnswer component shows a Direct Answer to a query. When we are confident that a search has one answer, we will surface a Direct Answer box at the top of the results.

Direct Answer for query about Yext office phone number

How Direct Answer Formatting Works by Default

As we went over in the Search Configuration unit on Direct Answers, Yext Search has two types of Direct Answers: Featured Snippet and Field Value. Each of these has its own cardType as you can see below since they each surface different types of content.

In the Theme, we have the following card files:

Field Value Direct Answers (surfacing from structured data in Content)

  • directanswers-card > allfields-standard > component.js
  • directanswers-card > allfields-standard > template.hbs

Featured Snippet Direct Answers (surfacing from unstructured data in Content)

  • directanswers-card > documentsearch-standard > component.js
  • directanswers-card > documentsearch-standard > template.hbs

Note that for the Field Value Direct Answers, in Jambo, we provide you with some default styling by field type. In the file, we will outline *all field types* and their corresponding default formatting and direct answer card layout. This means the files are long and can be a bit intimidating – we recommend using ctrl-find to find what you’re looking for more easily.

Overriding Direct Answer Formatting

We’ve applied formatting and settings that we think works best so that you don’t have to do the heavy-lifting. That said, sometimes you just want something different and we provide that flexibility too.

To modify the settings of Direct Answers, you would add the DirectAnswer component in ComponentSettings for your Search page and change the cardType to your forked card.

"DirectAnswer": {
      "types": {
        "FEATURED_SNIPPET": {
          "cardType": "documentsearch-standard"
        },
        "FIELD_VALUE": {
          "cardType": "allfields-standard"
        }
      }
    },

To modify the *styling* of Direct Answers, you would use Jambo Commands > Add Direct Answer Card. This will fork the default card in the theme and create a new set of files in a new top-level directory as you learned in the Customize Result Cards unit.

In general, we don’t recommend forking the default formatting unless you really need to.

The most common use case for forking the documentsearch-standard card is adding a CTA. Like with result cards, you can adjust the data mappings of a CTA button to be either hardcoded or entity field values.

When referencing an entity field value on a regular result card, we map to the entity’s desired field using the syntax profile.fieldName. Due to differences in the content returned from the API for Featured Snippets, the syntax for mapping to a field changes to relatedItemData.fieldValues.fieldName.

Below is an example of a CTA that has been added to the component.js file for a forked Featured Snippet Direct Answer Card and mapped to the Primary CTA field.

CTA: {
  label: relatedItemData.fieldValues.c_primaryCTA ? relatedItemData.fieldValues.c_primaryCTA.label : null, // The CTA's label
  url: Formatter.generateCTAFieldTypeLink(relatedItemData.fieldValues.c_primaryCTA), // The URL a user will be directed to when clicking
  target: linkTarget, // Where the new URL will be opened
  eventType: 'CTA_CLICK', // Type of Analytics event fired when clicking the CTA
  eventOptions: this.addDefaultEventOptions({ fieldName: 'snippet' }) // The event options for CTA click analytics
}

Field Value Direct Answers

When you fork the allfields-standard card, as mentioned, you can choose to specify the formatting by field type (default) OR by specific field. We make this possible by using a *switch function* in the javascript.

component.js file

switch (answer.fieldType) {
  case 'url':
  case 'complex_url':
  case 'ios_app_url':
  case 'android_app_url':
  case 'facebook_url':
    if (isArray) {
      arrayValue = answer.value.map((value) => ({
          url: value,
          label: value
        }
      ));
    } else {
      regularValue = {
        url: answer.value,
        label: answer.value
      };
    }
    value = isArray ? arrayValue : regularValue;
    break;
  case 'email':
    if (isArray) {
      arrayValue = answer.value.map((value) => ({
          url: `mailto:${value}`,
          label: value,
        }
      ));
    } else {
      regularValue = {
        url: `mailto:${answer.value}`,
        label: answer.value,
      };
    }
    value = isArray ? arrayValue : regularValue;
    break;
  case 'instagram_handle':
    if (isArray) {
      arrayValue = answer.value.map((value) => ({
          url: `https://instagram.com/${value}`,
          label: `@${value}`,
        }
      ));
    } else {
      regularValue = {
        url: `https://instagram.com/${answer.value}`,
        label: `@${answer.value}`,
      };
    }
    value = isArray ? arrayValue : regularValue;
    break;
  case 'twitter_handle':
    if (isArray) {
      arrayValue = answer.value.map((value) => ({
          url: `https://twitter.com/${value}`,
          label: `@${value}`,
        }
      ));
    } else {
      regularValue = {
        url: `https://twitter.com/${answer.value}`,
        label: `@${answer.value}`,
      };
    }
    value = isArray ? arrayValue : regularValue;
    break;
  case 'phone':
    if (isArray) {
      arrayValue = answer.value.map((value) => ({
          url: Formatter.phoneLink({mainPhone: value}),
          label: Formatter.nationalizedPhoneDisplay({mainPhone: value}),
        }
      ));
    } else {
      regularValue = {
        url: Formatter.phoneLink({mainPhone: answer.value}),
        label: Formatter.nationalizedPhoneDisplay({mainPhone: answer.value}),
      };
    }
    value = isArray ? arrayValue : regularValue;
    break;
  case 'address':
    if (isArray) {
      arrayValue = answer.value.map((value) => Formatter.address({address: value}));
    } else {
      regularValue = Formatter.address({address: answer.value});
    }
    value = isArray ? arrayValue : regularValue;
    break;
  case 'hours':
    if (isArray) {
      arrayValue = answer.value.map((value) => `<div>${Formatter.openStatus({hours: value})}</div>`);
    } else {
      regularValue = `<div>${Formatter.openStatus({hours: answer.value})}</div>`;
    }
    value = isArray ? arrayValue : regularValue;
    break;
  case 'decimal':
    if (isArray) {
      arrayValue = answer.value.map((value) => value.toLocaleString());
    } else {
      regularValue = answer.value.toLocaleString();
    }
    value = isArray ? arrayValue : regularValue;
    break;
  case 'rich_text':
    if (isArray) {
      arrayValue = answer.value.map((value) => ANSWERS.formatRichText(value));
    } else {
      regularValue = ANSWERS.formatRichText(answer.value);
    }
    value = isArray ? arrayValue : regularValue;
  case 'single_line_text':
  case 'multi_line_text':
  default:
    value = answer.value;
    break;
}

To add a new field type, add another case statement above the ‘default’ case, formatted like the below:

case 'fieldtype':
  // functions go here
  value = "value you want to return"
  break;

To update a specific field, you will navigate to the bottom of the file and uncomment the section. Below is an example of specific formatting for the mainPhone field:

switch (answer.fieldApiName) {
  case 'mainPhone': // The Field API name
    if (isArray) {
      arrayValue = answer.value.map((value) => ({
          url: Formatter.phoneLink({mainPhone: value}),
          label: Formatter.nationalizedPhoneDisplay({mainPhone: value})
        }
      ));
    } else {
      regularValue = {
        url: Formatter.phoneLink({mainPhone: answer.value}),
        label: Formatter.nationalizedPhoneDisplay({mainPhone: answer.value})
      };
    }
    value = isArray ? arrayValue : regularValue;
    break;
}
Feedback