<p>If I understand you correctly, you want to "highlight" your search query in the results. Your own Twig filter would be the cleanest way to do this, but you don't <em>need</em> to write one. Beware of putting too much logic in your templates, but this would also work nicely, using the native <code>replace</code> filter:</p>

<pre><code>{# Get the search query and break it into individual terms #}
{% set query = craft.request.getParam( 'q' ) %}
{% set searchTerms = query | trim | split( ' ' ) %}

{# Build an associated array w/ terms and the replacement string #}
{% set replace = {} %}
{% for term in searchTerms %}
{% set replace = replace | merge( { (term) : '&lt;strong&gt;' ~ term ~ '&lt;/strong&gt;' } ) %}
{% endfor %}

{# Loop through your search results, replacing the terms for all neccessary fields..#}
{% for entry in entries %}
    &lt;li&gt;
        &lt;a href="{{ entry.url }}"&gt;{{ entry.title | replace( replace ) | raw }}&lt;/a&gt;
        &lt;p&gt;{{ entry.bodyText | replace( replace ) | raw }}
    &lt;/li&gt;
{% endfor %}
</code></pre>

<p>With that, all your search terms will be wrapped in <strong></strong> tags – or whatever else you'd want to highlight them.</p>

<p><em>EDIT</em>: Reading over, I realize I <em>did</em> misunderstand the question – you need to extract parts of the content matching the search query, not highlight the terms in titles, excerpts or whatever. The commenter above me is correct; you'd need a custom filter to accomplish your goal (without going insane). Apologies for the wrong answer; I'll leave it up, though.</p>

CC BY-SA 3.0
2015-02-27T17:53:10.060Z
Thanks anyway. My question probably could have been clearer.
Bruce Johnson
CC BY-SA 3.0
2017-04-20T12:16:34.487Z
Thanks for your answer. I just want to add that if a term is part of an URL for instance, the link will break.
Mario
CC BY-SA 3.0
2015-02-26T14:54:48.367Z
2015-02-26T15:22:47.040Z
2015-02-26T15:22:47.040Z
Mats Mikkel Rummelhoff
<p>I know this is an old question but i stumbled upon it while searching for an answer so others might find it as well and I wanted to share my solution which is not perfect but works really good for all sorts of fields.</p>
<p>What I wanted was to display the context why the entry was returned when searching for a certain keyphrase. So What I do is, I iterated over all fields of an entry using <code>$entry-&gt;getFieldValues()</code> and then based on what type the field is I chose the next step:</p>
<ul>
<li>Is it a text or redactor field -&gt; search for the searchQuery in the string and if it is found append it to the excerpt</li>
<li>Is it an array -&gt; recursively call the function to iterate over all fields of the array</li>
<li>Is it of type matrixblock or supertable -&gt; recursively call the function to iterate over all fields</li>
</ul>
<p>This solution obviously finds more than the search function uses to decided if the entry is part of the result set because I didn't find a way to check if a field is part of the searchindex but it results in a nice excerpt to give users context for their search result.</p>
<p>Full TwigExtension:</p>
<pre class="lang-php prettyprint-override"><code>&lt;?php

namespace modules\extensions;

use Twig\Extension\AbstractExtension;

class SearchResultExcerpt extends AbstractExtension
{
    public function getFunctions(): array
    {
        return [
            new \Twig\TwigFunction(&quot;getSearchResultExcerpt&quot;, function ($entry, $searchString) {
                return $this-&gt;getBlockExcerpt($entry, $searchString);
            }),
        ];
    }

    private function getBlockExcerpt($block, $searchString): string
    {
        $excerpt = &quot;&quot;;
        $fields = is_array($block) ? $block : $block-&gt;getFieldValues();
        foreach ($fields as $field) {
            $fieldExcerpt = &quot;&quot;;

            if (is_string($field)) {
                $text = strip_tags($field);
                $matches = $this-&gt;findMatches($text, $searchString);
                if (strlen($matches) &gt; 0) {
                    $fieldExcerpt .= '&lt;span class=&quot;' . array_search($field, $fields) . '&quot;&gt;' . $matches . &quot;&lt;/span&gt;&quot;;
                }
            } elseif (is_array($field)) {
                foreach ($field as $value) {
                    $fieldExcerpt .= $this-&gt;getBlockExcerpt($value, $searchString);
                }
            } elseif (is_object($field) &amp;&amp; str_contains(get_class($field), &quot;redactor&quot;)) {
                $text = strip_tags($field-&gt;getParsedContent());
                $matches = $this-&gt;findMatches($text, $searchString);
                if (strlen($matches)) {
                    $fieldExcerpt .= '&lt;span class=&quot;text-field&quot;&gt;' . $matches . &quot;&lt;/span&gt;&quot;;
                }
            } elseif (
                is_object($field) &amp;&amp;
                (str_contains(get_class($field), &quot;MatrixBlockQuery&quot;) ||
                    str_contains(get_class($field), &quot;SuperTableBlockQuery&quot;))
            ) {
                foreach ($field-&gt;all() as $block) {
                    $fieldExcerpt .= $this-&gt;getBlockExcerpt($block, $searchString);
                }
            }
            if (strlen($fieldExcerpt) &gt; 0) {
                $excerpt .= &quot;&lt;div class='content-field'&gt;&quot; . $fieldExcerpt . &quot;&lt;/div&gt;&quot;;
            }
        }

        return $excerpt;
    }

    private function findMatches($text, $searchString): string
    {
        $text = &quot;\n&quot; . $text . &quot;\n&quot;;
        $words = join(&quot;|&quot;, explode(&quot; &quot;, preg_quote($searchString)));
        preg_match_all(&quot;#((?&lt;=\s).{0,30}(&quot; . $words . &quot;).{0,30}(?=\s))#i&quot;, $text, $matches, PREG_SET_ORDER);
        $results = [];
        foreach ($matches as $line) {
            $results[] = htmlspecialchars($line[0], 0, &quot;UTF-8&quot;);
        }

        $result = join(&quot; [...] &quot;, $results);
        return preg_replace(&quot;#&quot; . $words . &quot;#iu&quot;, &quot;&lt;span class=\&quot;highlight\&quot;&gt;\$0&lt;/span&gt;&quot;, $result);
    }
}

</code></pre>

CC BY-SA 4.0
2022-11-09T07:06:33.587Z
2022-11-09T07:06:33.587Z
Josh
<p>I think <a href="https://cameronspear.com/blog/craft-cms-twig-filter-extension/">creating your own Twig filter</a> is your best bet. 
So, create a filter that takes a string, searches that for another string and returns an extract. It could do this by using some regular expression hocus pocus, I guess.</p>

<pre><code>{{ entry.bodyText | extract('your search term', 3, 3) }}
</code></pre>

<p>would be how you'd use it (assuming, for example, that your filter accepts the three arguments <code>searchTerm</code>, <code>wordsBefore</code> and <code>wordsAfter</code>).</p>

CC BY-SA 3.0
2015-02-27T02:53:45.367Z
To add to what Paul mentioned, there's some [snippets in this SE thread](http://stackoverflow.com/questions/1292121/how-to-generate-the-snippet-like-generated-by-google-with-php-and-mysql) that might be useful when crafting an excerpt/highlighting plugin.
RitterKnight
CC BY-SA 3.0
2015-02-27T17:52:16.443Z
Thanks, both. I figured this would be the way to go but I just wanted to make sure I wasn't overlooking an easier solution.
Bruce Johnson
CC BY-SA 3.0
2015-02-25T22:11:19.080Z
2015-02-25T22:11:19.080Z
Paul
<p>Say someone searches for "John Smith". Using the <a href="http://buildwithcraft.com/docs/templating/search-form" rel="noreferrer">search results template on the Craft website</a>, my search results are then a list of titles for entries that include the term "John Smith". For example:</p>

<ul>
<li>Dave Taylor wins award</li>
<li>New building built</li>
</ul>

<p>The user experience is quite poor as there is no indication that these articles are in any way related to "John Smith". What I'm trying to do is output the following:</p>

<ul>
<li>Dave Taylor wins award</li>
</ul>

<p>...with the help of his brother <strong>John Smith</strong>,...</p>

<ul>
<li>New building built</li>
</ul>

<p>...architect <strong>John Smith</strong> was instrumental...</p>

<p>i.e. something similar to Google search. Any ideas how I could go about this?</p>

<search>
How do I get extracts in search results?
# How do I get extracts in search results?

(Asked by: username_0 on 2015-02-24)

Say someone searches for "John Smith". Using the [search results template on the Craft website](http://buildwithcraft.com/docs/templating/search-form), my search results are then a list of titles for entries that include the term "John Smith". For example:

* Dave Taylor wins award
* New building built

The user experience is quite poor as there is no indication that these articles are in any way related to "John Smith". What I'm trying to do is output the following:

* Dave Taylor wins award

...with the help of his brother **John Smith**,...

* New building built

...architect **John Smith** was instrumental...

i.e. something similar to Google search. Any ideas how I could go about this?

**Tags:** search


---

## 3 answers

### Answer by username_4 on 2015-02-25. Score: 7 | Accepted answer

I think creating your own Twig filter is your best bet. 
So, create a filter that takes a string, searches that for another string and returns an extract. It could do this by using some regular expression hocus pocus, I guess.
{{ entry.bodyText | extract('your search term', 3, 3) }}

would be how you'd use it (assuming, for example, that your filter accepts the three arguments searchTerm, wordsBefore and wordsAfter).

> **Comments:**
> **username_5**: To add to what username_4 mentioned, there's some [snippets in this SE thread](http://stackoverflow.com/questions/1292121/how-to-generate-the-snippet-like-generated-by-google-with-php-and-mysql) that might be useful when crafting an excerpt/highlighting plugin.  
> **username_0**: Thanks, both. I figured this would be the way to go but I just wanted to make sure I wasn't overlooking an easier solution.  

---

### Answer by username_1 on 2015-02-26. Score: 4

If I understand you correctly, you want to "highlight" your search query in the results. Your own Twig filter would be the cleanest way to do this, but you don't need to write one. Beware of putting too much logic in your templates, but this would also work nicely, using the native replace filter:
{# Get the search query and break it into individual terms #}
{% set query = craft.request.getParam( 'q' ) %}
{% set searchTerms = query | trim | split( ' ' ) %}

{# Build an associated array w/ terms and the replacement string #}
{% set replace = {} %}
{% for term in searchTerms %}
{% set replace = replace | merge( { (term) : '' ~ term ~ '' } ) %}
{% endfor %}

{# Loop through your search results, replacing the terms for all neccessary fields..#}
{% for entry in entries %}
    
        {{ entry.title | replace( replace ) | raw }}
        {{ entry.bodyText | replace( replace ) | raw }}
    
{% endfor %}

With that, all your search terms will be wrapped in  tags – or whatever else you'd want to highlight them.
EDIT: Reading over, I realize I did misunderstand the question – you need to extract parts of the content matching the search query, not highlight the terms in titles, excerpts or whatever. The commenter above me is correct; you'd need a custom filter to accomplish your goal (without going insane). Apologies for the wrong answer; I'll leave it up, though.

> **Comments:**
> **username_0**: Thanks anyway. My question probably could have been clearer.  
> **username_2**: Thanks for your answer. I just want to add that if a term is part of an URL for instance, the link will break.  

---

### Answer by username_3 on 2022-11-09. Score: 3

I know this is an old question but i stumbled upon it while searching for an answer so others might find it as well and I wanted to share my solution which is not perfect but works really good for all sorts of fields.
What I wanted was to display the context why the entry was returned when searching for a certain keyphrase. So What I do is, I iterated over all fields of an entry using $entry->getFieldValues() and then based on what type the field is I chose the next step:

Is it a text or redactor field -> search for the searchQuery in the string and if it is found append it to the excerpt
Is it an array -> recursively call the function to iterate over all fields of the array
Is it of type matrixblock or supertable -> recursively call the function to iterate over all fields

This solution obviously finds more than the search function uses to decided if the entry is part of the result set because I didn't find a way to check if a field is part of the searchindex but it results in a nice excerpt to give users context for their search result.
Full TwigExtension:
getBlockExcerpt($entry, $searchString);
            }),
        ];
    }

    private function getBlockExcerpt($block, $searchString): string
    {
        $excerpt = "";
        $fields = is_array($block) ? $block : $block->getFieldValues();
        foreach ($fields as $field) {
            $fieldExcerpt = "";

            if (is_string($field)) {
                $text = strip_tags($field);
                $matches = $this->findMatches($text, $searchString);
                if (strlen($matches) > 0) {
                    $fieldExcerpt .= '' . $matches . "";
                }
            } elseif (is_array($field)) {
                foreach ($field as $value) {
                    $fieldExcerpt .= $this->getBlockExcerpt($value, $searchString);
                }
            } elseif (is_object($field) && str_contains(get_class($field), "redactor")) {
                $text = strip_tags($field->getParsedContent());
                $matches = $this->findMatches($text, $searchString);
                if (strlen($matches)) {
                    $fieldExcerpt .= '' . $matches . "";
                }
            } elseif (
                is_object($field) &&
                (str_contains(get_class($field), "MatrixBlockQuery") ||
                    str_contains(get_class($field), "SuperTableBlockQuery"))
            ) {
                foreach ($field->all() as $block) {
                    $fieldExcerpt .= $this->getBlockExcerpt($block, $searchString);
                }
            }
            if (strlen($fieldExcerpt) > 0) {
                $excerpt .= "" . $fieldExcerpt . "";
            }
        }

        return $excerpt;
    }

    private function findMatches($text, $searchString): string
    {
        $text = "\n" . $text . "\n";
        $words = join("|", explode(" ", preg_quote($searchString)));
        preg_match_all("#((?<=\s).{0,30}(" . $words . ").{0,30}(?=\s))#i", $text, $matches, PREG_SET_ORDER);
        $results = [];
        foreach ($matches as $line) {
            $results[] = htmlspecialchars($line[0], 0, "UTF-8");
        }

        $result = join(" [...] ", $results);
        return preg_replace("#" . $words . "#iu", "\$0", $result);
    }
}


---

