.md

catalog.fn.suggest()

Provides autocomplete suggestions for an attribute. Returns the distinct values that match the user input, with the keys of the items that have each value, for search-as-you-type and autocomplete.

Syntax

list.catalog.fn.suggest(options)

Parameters

attributestringRequired
The attribute to get suggestions for. A dotted path such as 'brand.name' resolves through the reference
valuestring | numberRequired
The search term. A bare string is a case-insensitive prefix match; wrap it in slashes ('/pattern/') for a regular expression; '' returns every value (up to max)
regexFlagsstringOptional
Flags for a '/pattern/' value, e.g. 'i' for case-insensitive. Ignored for any other value; invalid flags return no suggestions
maxnumberOptional
Maximum number of suggestions to return (default: 20)
transform('lowercase' | 'removeWhitespace' | 'trim')[]Optional
Normalize values before matching
calculateResultKeysbooleanOptional
Include item keys in results (default: true)
excludeUndefinedsbooleanOptional
Exclude undefined values (default: true)
excludeNullsbooleanOptional
Exclude null values (default: true)
relevantData'filtered' | 'all' | 'excludeQuery'Optional
Which items to suggest from: 'filtered' (default, the list's current result), 'all' (ignore the query), or 'excludeQuery' (the current query without excludeQuery)
excludeQueryPartial<DcuplQuery | DcuplQueryGroup>Optional
Condition or group (e.g. { groupKey: 'category' }) to leave out of the query when relevantData is 'excludeQuery'

Return type

Returns an array of Suggestion objects:

// Can be imported from @dcupl/common
type Suggestion = {
  value: string;     // The raw value: typed string; at runtime a number or boolean for numeric and boolean attributes, the ISO string for dates
  keys: string[];    // Item keys with this value ([] if calculateResultKeys: false)
}

Usage notes

  • A bare string matches the start of the value, ignoring case: 'lap' suggests "Laptop Pro" but not "Gaming Laptop"
  • Wrap the term in slashes ('/pro/') to match it anywhere; the pattern is case-sensitive unless you set regexFlags: 'i' (flags never go after the closing slash: '/pro/i' is not a regex, just a plain prefix). Escape regex characters in user input
  • Empty search term returns all unique values (up to max)
  • Dotted paths work: attribute: "brand.name" suggests the names of the referenced brands, with the keys of the items that reference them
  • Suggestions respect current query filters by default (relevantData: "filtered")
  • Use transform options for flexible matching (lowercase, removeWhitespace, trim)
  • Performance: Set calculateResultKeys: false when you only need values, not item references
  • Performance: Use max parameter to limit results - default is 20
  • Performance: Avoid regex patterns for simple prefix matching - prefix is faster
  • Troubleshooting: Use relevantData: "all" if you want suggestions regardless of current filters

Related methods

  • catalog.fn.facets() — Get all unique values with counts for building filters
  • catalog.query.addCondition() — Apply filters based on selected suggestion
  • catalog.query.many() — Get the items behind the result keys

Related guides/docs

Examples

Product search autocomplete

Basic autocomplete implementation with prefix matching and transform options.

const productList = dcupl.lists.create({ modelKey: 'Product' });

function handleSearchInput(inputValue: string) {
  const suggestions = productList.catalog.fn.suggest({
    attribute: 'name',
    value: inputValue,
    max: 10,
    transform: ['lowercase', 'trim'],
  });

  // Render suggestions
  const dropdown = document.getElementById('suggestions')!;
  dropdown.innerHTML = suggestions
    .map((s) => '<div class="suggestion">' + s.value + ' (' + s.keys.length + ')</div>')
    .join('');
}

// Attach to input
document.getElementById('search')!.addEventListener('input', (e) => {
  handleSearchInput((e.target as HTMLInputElement).value);
});
Category filter suggestions

Get all category values for a filter dropdown, showing counts based on current filters.

const productList = dcupl.lists.create({ modelKey: 'Product' });

// An empty search term returns every value (up to max)
const categories = productList.catalog.fn.suggest({
  attribute: 'category',
  value: '', // Empty = all values
  max: 50, // default is 20
  relevantData: 'filtered', // Respect current filters
});

// Build filter options with counts
const filterOptions = categories.map((s) => ({
  label: s.value,
  count: s.keys.length,
}));

console.log('Available Categories:');
filterOptions.forEach((opt) => {
  console.log('  ' + opt.label + ' (' + opt.count + ')');
});
Brand search with icons

Enrich suggestions with additional metadata like counts and custom icons.

const productList = dcupl.lists.create({ modelKey: 'Product' });

// Brand icon mapping
const brandIcons: Record<string, string> = {
  Nike: '👟',
  Apple: '🍎',
  Samsung: '📱',
  Sony: '🎮',
};

function searchBrands(query: string) {
  const suggestions = productList.catalog.fn.suggest({
    attribute: 'brand',
    value: query,
    max: 8,
    transform: ['lowercase'],
  });

  return suggestions.map((s) => ({
    brand: s.value,
    count: s.keys.length,
    icon: brandIcons[s.value] || '🏷️',
  }));
}

const results = searchBrands('s');
results.forEach((r) => {
  console.log(r.icon + ' ' + r.brand + ' (' + r.count + ' products)');
});
Color suggestions with mapping

Map color suggestions to hex codes or other visual representations.

const productList = dcupl.lists.create({ modelKey: 'Product' });

const colorHex: Record<string, string> = {
  red: '#FF0000',
  blue: '#0000FF',
  green: '#00FF00',
  black: '#000000',
  white: '#FFFFFF',
};

function getColorSuggestions(query: string) {
  const suggestions = productList.catalog.fn.suggest({
    attribute: 'color',
    value: query,
    transform: ['lowercase'],
  });

  return suggestions.map((s) => ({
    name: s.value,
    hex: colorHex[s.value.toLowerCase()] || '#CCCCCC',
    count: s.keys.length,
  }));
}

const colors = getColorSuggestions('bl');
colors.forEach((c) => {
  console.log(c.hex + ' ' + c.name + ' (' + c.count + ')');
});
Country suggestions with flags

Enrich country suggestions with flag emojis for visual display.

const customerList = dcupl.lists.create({ modelKey: 'Customer' });

const countryFlags: Record<string, string> = {
  'United States': '🇺🇸',
  Germany: '🇩🇪',
  Japan: '🇯🇵',
  France: '🇫🇷',
  'United Kingdom': '🇬🇧',
};

function searchCountries(query: string) {
  const suggestions = customerList.catalog.fn.suggest({
    attribute: 'country',
    value: query,
    max: 10,
    transform: ['trim'],
  });

  return suggestions.map((s) => ({
    country: s.value,
    flag: countryFlags[s.value] || '🏳️',
    customers: s.keys.length,
  }));
}

const results = searchCountries('un');
results.forEach((r) => {
  console.log(r.flag + ' ' + r.country + ' - ' + r.customers + ' customers');
});
Course title with full item lookup

Use suggestion keys to fetch complete item details for rich autocomplete.

const courseList = dcupl.lists.create({ modelKey: 'Course' });

// suggest() and query.one() are synchronous
function searchCourses(query: string) {
  const suggestions = courseList.catalog.fn.suggest({
    attribute: 'title',
    value: query,
    max: 5,
    calculateResultKeys: true,
  });

  // Look up the first matching course of each suggestion
  return suggestions.map((s) => {
    const course = courseList.catalog.query.one({ itemKey: s.keys[0] });

    return {
      title: s.value,
      instructor: course?.instructor,
      duration: course?.duration,
      rating: course?.rating,
      matchCount: s.keys.length,
    };
  });
}

const results = searchCourses('java');
results.forEach((c) => {
  console.log(c.title);
  console.log('  By: ' + c.instructor + ' | ' + c.duration + 'h | ⭐' + c.rating);
});
Hashtag suggestions

Handle special characters in input by preprocessing before suggestion.

const postList = dcupl.lists.create({ modelKey: 'Post' });

function suggestHashtags(input: string) {
  // Remove # prefix if present
  const query = input.startsWith('#') ? input.slice(1) : input;

  const suggestions = postList.catalog.fn.suggest({
    attribute: 'hashtags',
    value: query,
    max: 10,
    transform: ['lowercase', 'removeWhitespace'],
  });

  return suggestions.map((s) => ({
    tag: '#' + s.value,
    posts: s.keys.length,
  }));
}

const tags = suggestHashtags('#java');
tags.forEach((t) => {
  console.log(t.tag + ' (' + t.posts + ' posts)');
});
Campaign code suggestions (uppercase)

Prefix matching ignores case, so lowercase input finds uppercase codes without any transform.

const campaignList = dcupl.lists.create({ modelKey: 'Campaign' });

function suggestCampaignCodes(input: string) {
  // A bare string is a case-insensitive prefix match
  const suggestions = campaignList.catalog.fn.suggest({
    attribute: 'code',
    value: input,
    max: 10,
  });

  return suggestions.map((s) => ({
    code: s.value,
    usageCount: s.keys.length,
  }));
}

const codes = suggestCampaignCodes('sum');
codes.forEach((c) => {
  console.log(c.code + ' (used ' + c.usageCount + ' times)');
});

// Typing "sum" matches codes like SUMMER2024 and SUMMERSALE
Substring suggestions (regex + regexFlags)

Wrap the term in slashes to match it anywhere in the value. The pattern is case-sensitive; set regexFlags for flags.

const productList = dcupl.lists.create({ modelKey: 'Product' });

const escapeRegExp = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, (char) => '\\' + char);

function suggestContaining(input: string) {
  return productList.catalog.fn.suggest({
    attribute: 'name',
    value: `/${escapeRegExp(input.trim())}/`,
    regexFlags: 'i', // flags go here: '/phone/i' is not a regex (plain prefix text)
    max: 10,
  });
}

// '/phone/' matches "Smartphone Case"; with regexFlags 'i' also "Galaxy Phone"
const results = suggestContaining('phone');
console.log(results.map((s) => s.value));
// Basic suggestions for productName attribute
const suggestions = list.catalog.fn.suggest({
  attribute: 'productName',
  value: '/pro/', // regex for 'pro' anywhere in the value
  max: 10,
  transform: ['lowercase'],
});