Skip to main content

Extending the Plugin

For most use cases, the built-in boost types are sufficient. This guide covers creating custom boost types for specialized requirements.

Architecture Overview

The plugin uses a simple class hierarchy:

1
BoostConfiguration (abstract) — defines how boosts are organized
2
└── CustomBoostConfiguration — user-configurable list of boosts
3
4
Boost (abstract)
5
├── IndexBoost (abstract) — for field-based boosts
6
│ ├── ExactMatchBoost
7
│ ├── PartialMatchBoost
8
│ ├── StartsWithBoost
9
│ ├── NewestBoost
10
│ └── OldestBoost
11
└── TypeBoost (abstract) — for content type-based boosts
12
├── ContentTypeBoost
13
└── SemanticMatchBoost

There are two main extension points:

  • BoostConfiguration — Controls how boosts are organized and applied (e.g., curated presets vs. user-defined lists)
  • Boost — Individual boost rules that modify query relevance

Creating a Custom Boost

Option 1: Extend IndexBoost

Use IndexBoost when your boost operates on an indexed field. This gives you the field selector UI automatically.

1
import com.brightspot.cms.search.boost.IndexBoost;
2
import com.psddev.cms.tool.Search;
3
import com.psddev.dari.db.Query;
4
import com.psddev.dari.db.Recordable;
5
import org.apache.commons.lang3.StringUtils;
6
7
/**
8
* Boosts results where the selected field contains the query as a substring, using case-insensitive matching.
9
*/
10
@Recordable.DisplayName("Contains Match")
11
public class ContainsMatchBoost extends IndexBoost {
12
13
@Override
14
public void updateQuery(Search search, Query<?> query) {
15
String queryString = search.getQueryString();
16
if (StringUtils.isBlank(queryString)) {
17
return;
18
}
19
20
String index = getIndex();
21
if (StringUtils.isBlank(index)) {
22
return;
23
}
24
25
// Boost results where the field contains the query string
26
query.sortRelevant(getBoost(), index + " matchesAll ?", queryString.toLowerCase());
27
}
28
}

Key points:

  • Extend IndexBoost to inherit the field selector UI
  • Use getIndex() to get the selected field name
  • Use getBoost() to get the calculated boost factor from the weight
  • Override getIndexFieldType() to filter available fields (default is "text", use "date" for date fields)

Option 2: Extend TypeBoost

Use TypeBoost when your boost operates on a content type. This gives you the content type selector UI.

1
import com.brightspot.cms.search.boost.TypeBoost;
2
import com.psddev.cms.tool.Search;
3
import com.psddev.dari.db.ObjectType;
4
import com.psddev.dari.db.Query;
5
import com.psddev.dari.db.Recordable;
6
7
/**
8
* Boosts results of the selected content type only when the search query is longer than a specified minimum length.
9
*/
10
@Recordable.DisplayName("Long Query Type Boost")
11
public class LongQueryTypeBoost extends TypeBoost {
12
13
@Recordable.Minimum(1)
14
private int minimumQueryLength = 3;
15
16
public int getMinimumQueryLength() {
17
return minimumQueryLength;
18
}
19
20
public void setMinimumQueryLength(int minimumQueryLength) {
21
this.minimumQueryLength = minimumQueryLength;
22
}
23
24
@Override
25
public void updateQuery(Search search, Query<?> query) {
26
String queryString = search.getQueryString();
27
28
// Only apply boost for queries meeting minimum length
29
if (queryString == null || queryString.length() < minimumQueryLength) {
30
return;
31
}
32
33
ObjectType objectType = getContentObjectType();
34
if (objectType == null) {
35
return;
36
}
37
38
query.sortRelevant(getBoost(), "_type = ?", objectType.findConcreteTypes());
39
}
40
}

Key points:

  • Extend TypeBoost to inherit the content type selector UI
  • Use getContentObjectType() to get the selected ObjectType
  • Add custom fields with standard Dari annotations

Option 3: Extend Boost Directly

For completely custom logic that doesn't fit the field or type patterns, extend Boost directly.

1
import com.brightspot.cms.search.boost.Boost;
2
import com.psddev.cms.tool.Search;
3
import com.psddev.dari.db.Query;
4
import com.psddev.dari.db.Recordable;
5
6
/**
7
* Boosts results that are marked as "featured" in the CMS. This boost applies regardless of the search query.
8
*/
9
@Recordable.DisplayName("Featured Content")
10
public class FeaturedContentBoost extends Boost {
11
12
@Override
13
public void updateQuery(Search search, Query<?> query) {
14
// Boost any content where the 'featured' field is true
15
query.sortRelevant(getBoost(), "featured = ?", true);
16
}
17
18
@Override
19
public String getLabel() {
20
return "Featured Content (" + getWeight() + ")";
21
}
22
}

Key points:

  • You're responsible for all configuration fields and UI
  • Override getLabel() to provide a meaningful display name
  • The boost automatically appears in the boost type dropdown

Creating a Custom BoostConfiguration

While CustomBoostConfiguration lets users build their own boost lists, you can extend BoostConfiguration to create curated presets with hardcoded boost rules. This is useful for:

  • Providing optimized configurations for specific use cases (e.g., "News Site", "E-Commerce")
  • Enforcing consistent boost rules across sites
  • Simplifying configuration for non-technical users

Example: Curated News Configuration

1
import com.brightspot.cms.search.boost.BoostConfiguration;
2
import com.psddev.cms.tool.Search;
3
import com.psddev.dari.db.Query;
4
import com.psddev.dari.db.Recordable;
5
6
/**
7
* Pre-configured boost settings optimized for news sites. Prioritizes recent content and exact headline matches.
8
*/
9
@Recordable.DisplayName("News Site (Optimized)")
10
public class NewsBoostConfiguration extends BoostConfiguration {
11
12
@Minimum(0)
13
@Maximum(100)
14
private int weightMultiplier = 50;
15
16
@Override
17
public void updateQuery(Search search, Query<?> query) {
18
String queryString = search.getQueryString();
19
double baseBoost = calculateBoost(weightMultiplier);
20
21
// Strong boost for exact headline matches
22
query.sortRelevant(baseBoost * 2, "headline = ?", queryString);
23
24
// Moderate boost for partial headline matches
25
for (String term : queryString.split("\\s+")) {
26
query.sortRelevant(baseBoost, "headline matches ?", "*" + term + "*");
27
}
28
29
// Boost recent content (within last 7 days gets highest boost)
30
query.sortNewest(baseBoost * 1.5, "cms.content.publishDate");
31
}
32
33
private double calculateBoost(int weight) {
34
return Math.pow(10, weight / 10.0);
35
}
36
37
public int getWeightMultiplier() {
38
return weightMultiplier;
39
}
40
41
public void setWeightMultiplier(int weightMultiplier) {
42
this.weightMultiplier = weightMultiplier;
43
}
44
}

Key points:

  • Extend BoostConfiguration directly
  • Override updateQuery() to apply your hardcoded boost logic
  • Use @Recordable.DisplayName to give it a friendly name in the dropdown
  • Optionally add configurable fields to allow some customization (like the weight multiplier in the example)

When to Use Custom BoostConfiguration

Use CaseApproach
Users need full control over boost rulesUse built-in CustomBoostConfiguration
Provide optimized presets users can selectCreate custom BoostConfiguration subclasses
Mix of preset + customizationCreate custom BoostConfiguration with configurable fields
Enforce specific boost rules programmaticallyCreate custom BoostConfiguration with hardcoded logic

Key APIs

Query Methods

The Query class provides several methods for applying boosts:

1
// Boost by a condition
2
query.sortRelevant(boostFactor, "field = ?", value);
3
4
// Boost by date (newer first)
5
query.sortNewest(boostFactor, "dateField");
6
7
// Boost by date (older first)
8
query.sortOldest(boostFactor, "dateField");

Search Context

The Search object provides access to:

1
search.getQueryString() // The raw search query entered by the user

Weight Conversion

The getBoost() method converts the 0-100 weight to a logarithmic scale:

  • Weight 0 = boost factor 1.0
  • Weight 50 = boost factor ~1,000
  • Weight 100 = boost factor ~1 billion

Registration

Custom boosts and configurations are automatically discovered and appear in the CMS UI. Just ensure your class:

  1. Extends Boost, IndexBoost, TypeBoost, or BoostConfiguration
  2. Has the @Recordable.DisplayName annotation for a friendly name