COVEO SETUP WALKTHROUGH
Building a Pokédex
on Coveo
How the organization is set up, how each piece works, and what to consider at every step.
Mariam Al-Ansary
OVERVIEW
The setup, in five parts
Follow them in order. Each part depends on the one before it.
01
Foundations
Organization, search hub, API key, and how the pieces connect.
02
The source
Scope, exclusions, scraping, fields, and data hidden in tabs.
03
Query pipeline
One condition, no extra rules, and query correction.
04
Machine learning
An encoder, generated answers, passages, and suggestions.
05
Front end
An Atomic page that sends the hub and logs clicks.
Coveo Pokédex · Setup walkthrough
2
PART 1 · FOUNDATIONS
How the pieces fit together
pokemondb.net
Species pages
Web source
pokedex-V2 · 1,025 items
Scraping + extension
Type, generation, image, forms
Pipeline + ML
Pokemon Search · SE, RGA, PR, QS
Atomic page
Search and species pages
Coveo Pokédex · Setup walkthrough
3
PART 2 · THE SOURCE
Building the source
01
Scope
Which pages belong in the index?
02
Exclusions
What must stay out, even when it is linked?
03
Structure
Which values become fields and facets?
04
Hidden data
What is on the page.
PART 2 · STEP 1 · SCOPE
Define which pages belong
A Web source that starts at the species directory and only keeps species pages.
SOURCE TYPE
Web
START URL
pokemondb.net/pokedex/all
PAGE LEVELS
1
INCLUDE
^https://pokemondb\.net/pokedex/[a-z0-9-]+$
EXCLUDE
/pokedex/all · /pokedex/national
EXPAND BEFORE FILTERING
true, in the source JSON
1,025
species pages indexed
One crawled URL becomes one item in the index.
Coveo Pokédex · Setup walkthrough
5
PART 2 · STEP 2 · EXCLUSIONS
Keep out what does not belong
Species pages link to the whole site. One regex and two named exclusions keep the index to species pages.
WHAT’S INDEXED
/pokedex/bulbasaur
/pokedex/charizard
/pokedex/raichu
/pokedex/mr-mime
One path segment after /pokedex
KEPT OUT, AND WHY
/move/tackle
outside /pokedex
/type/fire
linked from the type column
/pokedex/bulbasaur/moves/9
two path segments
/pokedex/all
read for links, then dropped
/pokedex/national
excluded by name
sitemap.xml
not used as the source type
Coveo Pokédex · Setup walkthrough
6
PART 2 · STEP 3 · SCRAPING
Pull structure out of the HTML
A species configuration on top of Coveo’s default one. It cleans the body and extracts what the fields need.
REMOVED FROM THE BODY
header, nav, footer
Coveo default configuration
#dex-moves
move tables
#dex-sprites
sprite galleries
Consider: this keeps move lists out of the text the models read. URL rules, not body exclusions, keep pages out of the index.
METADATA EXTRACTED
pokemon_type
XPath on the first basics panel only, ending in /text()
pokemon_generation
XPath on the first <abbr> in “introduced in”, not “Generations 2–7”
pokemon_name
CSS h1::text
og:image
XPath on the Open Graph image, the artwork URL
Coveo Pokédex · Setup walkthrough
7
PART 2 · STEP 4 · FIELDS
Map metadata into fields
A facet filters a field. A field is filled by a mapping. A mapping reads the metadata the scraper extracted.
1 · SCRAPE
XPath on the vitals table
…th['Type']/…/td[1]//a/text()
2 · MAP
Mapping rule
%[pokemon_type:crawler]
3 · INDEX
Facet field
pokemon_type · multi-value
FIELDS IN THE INDEX
pokemon_type
pokemon_generation
pokemon_name
pokemon_img
pokemon_forms
Consider: turn on multi-value for any field that holds a list. Without it, Grass and Poison collapse into one “Grass;Poison” value. A field’s type cannot be changed later.
Coveo Pokédex · Setup walkthrough
8
PART 2 · STEP 5 · HIDDEN DATA
Capture data that is not at its own URL
Alolan Raichu, Mega Raichu X and Y, Partner Pikachu: all of them live as tabs on a single species page.
HOW THE PAGE IS BUILT
Form tabs are in-page anchors, not URLs. One crawled URL is one item, so one set of fields cannot describe every form.
HOW TO CAPTURE IT
A Python indexing extension runs pre-conversion on the original HTML and writes every form into pokemon_forms as JSON.
pokemon_forms (one form of Raichu)
{ "name": "Alolan Raichu", "types": ["Electric", "Psychic"], "abilities": "Surge Surfer",
"stats": { "HP": "60", "Attack": "85", "Speed": "110" }, "total": "485" }
Consider: Body HTML is already converted, so the tabs are gone. Read the original file (documentdata) and apply the extension pre-conversion.
Coveo Pokédex · Setup walkthrough
9
PART 2 · STEP 6 · ITERATE
Pilot first, then scale
Check selectors on a handful of pages before crawling the full site.
PILOT
pokemon-pilot-3
Five pages: Bulbasaur, Pikachu, Charmander, Pidgey, Squirtle. Depth 0. Confirms each selector returns the right value.
FIRST FULL SOURCE
pokedex
All 1,025 species. Shows how real pages behave, such as tables repeated once per form.
LIVE SOURCE
pokedex-V2
Same URL rules, the first-panel type selector, and the forms extension.
Coveo Pokédex · Setup walkthrough
10
PART 3 · QUERY PIPELINE
Keep the pipeline thin
Condition: Search Hub is Pokemon Search. No extra ranking rules, so the ML models drive relevance.
Facets filter through the advanced query, so they need no pipeline rules.
Query correction is on. It applies automatically when the corrected query is the one with results.
The legacy Did You Mean switches have no effect once Query correction is on.
⌕ pikacu
Still has hits, so the page offers Did you mean pikachu?
⌕ manchop
No hits, so it is corrected in the same response: We couldn’t find anything for manchop, did you mean machop
Coveo Pokédex · Setup walkthrough
11
PART 4 · MACHINE LEARNING
Four models on one pipeline
Scope the encoder and RGA to the source. They read the cleaned page body from step 3.
SEMANTIC ENCODER
Pokedex SE
Embeds every page body. RGA and Passage Retrieval both depend on it.
GENERATIVE ANSWERING
Pokedex RGA
Writes the answer from the retrieved chunks, and only from them.
PASSAGE RETRIEVAL
Pokemon PR
Serves the Passage Retrieval API, called on every search.
QUERY SUGGESTIONS
Pokedex QS
Suggests queries from what people searched and then clicked.
All four are associated with the Pokemon Search pipeline.
Coveo Pokédex · Setup walkthrough
12
PART 4 · MACHINE LEARNING
Answers and passages are different calls
Both stay inside the index, and both send the same search hub.
When the pages say it
RGA answers from the chunks it retrieved. Ask for a type, an ability, or a stat and it cites the Pokédex page.
When they don’t
A fight winner or an unstated comparison gets no answer. The page says so:
“The Pokédex pages don’t state an answer to that. The closest entries are below.”
POST /rest/search/v3/passages/retrieve
{
"query": "what type is charizard",
"searchHub": "Pokemon Search",
"filter": "@source==(\"pokedex-V2\")",
"maxPassages": 10
}
Same hub, filtered to pokedex-V2. One passage per species, top three shown. Scores are cosine similarity.
Coveo Pokédex · Setup walkthrough
13
PART 5 · FRONT END
Connect an Atomic page to the hub
The page only reads what the organization provides. Everything it sends carries the same search hub.
1
Scaffold npm create @coveo/ui@latest with the atomic-search template.
2
Initialize Organization ID, anonymous search key, and search hub Pokemon Search.
3
Scope Constant query @source==("pokedex-V2").
4
Request fields registerFieldsToInclude for type, generation, name, and image.
index.html
<atomic-search-interface search-hub="Pokemon Search">
<atomic-search-box></atomic-search-box>
<atomic-facet field="pokemon_type"></atomic-facet>
<atomic-facet field="pokemon_generation"
sort-criteria="alphanumericNatural"></atomic-facet>
<atomic-generated-answer></atomic-generated-answer>
<atomic-did-you-mean></atomic-did-you-mean>
<atomic-result-list display="grid">…</atomic-result-list>
</atomic-search-interface>
Consider: fields are not returned in results unless you register them; facets still work because their values come from the index. Keep atomic-result-link, which logs the clicks the models learn from.
Coveo Pokédex · Setup walkthrough
14
PART 5 · FRONT END
Beyond the search page
A species page, result shaping, and passages, all on the same hub and token.
SPECIES PAGE
A second interface on the same hub. A constant query on @clickableuri returns one item, and form tabs read pokemon_forms.
RESPONSE MIDDLEWARE
A Headless hook placed under configuration.search. It sets the title, image, and slug on each result before it renders.
PASSAGES
Passage Retrieval has no Headless controller. Call the REST API with fetch, the same token, and the same hub.
Consider: result templates render inside shadow DOM, so page CSS does not reach them. Style cards inline or through shadow parts.
Run it locally: npm run dev, then open localhost:5173.
Coveo Pokédex · Setup walkthrough
15
CHECKLIST
Things to watch for
Symptoms you may see while setting this up, and the fix for each.
SYMPTOM
FIX
A type facet lists Fire, Flying, Fire, Dragon…
Read type from the first basics panel only
Facet values show raw <a> markup
End the XPath in /text()
Grass and Poison appear as one value
Turn on multi-value before relying on the field
An extension cannot see the tabs
Read the original file, pre-conversion
ExpandBeforeFiltering has no effect
Set it in the source JSON, not in scraping
Coveo Pokédex · Setup walkthrough
16
Questions?
Mariam · Coveo Pokédex