Getting Started with Semantic Search in Optimizely Graph

A visitor searches your site for “laptop for coding.” Your page says “machine for software engineering.” Keyword search finds nothing — the words don’t match, even though the page is exactly what they wanted.

This is the vocabulary mismatch problem. Your content team writes one way; your visitors search another way. Keyword search only finds content when both sides happen to pick the same words.

Semantic search fixes this by matching meaning instead of characters. A language model compares what the visitor meant against what your content is about — so “laptop for coding” and “machine for software engineering” land in roughly the same place, even with zero words in common.

Optimizely Graph has this built in. No vector database, no embedding pipeline, no extra infrastructure. Mark a couple of fields as searchable, change one value in your GraphQL query, and you’re running it.

The setup is genuinely that simple. Getting good results takes a bit more understanding — which is what the rest of this post covers.

Before you start: make your fields searchable

Semantic search only looks at fields marked searchable in your content type definition. If a field isn’t flagged, the model never sees it — no amount of query tuning will change that.

For this walkthrough I created a content type called CKExperienceType with two searchable fields:

  • Title — long string
  • MainBody — XHTML string

You set this in the CMS under Content Types → Properties. Select the property, then set Property Indexing Type to Searchable:

Fields with substantial text, like MainBody, are where semantic search earns its keep — there’s more meaning for the model to work with than in a short title.

One thing that catches people out: if you set the searchable flag after your content is already published, republish the pages. Indexing happens at publish time, so existing content won’t pick up the change on its own.

To confirm everything landed, query _fulltext — it returns the combined indexed text for each item.

{
  CKExperienceType(limit: 50) {
    total
    items {
      _metadata { displayName }
      _fulltext
    }
  }
}

If a page comes back with an empty or missing _fulltext, the flag didn’t take or the content hasn’t been reindexed. Fix that before going further — every query after this depends on it.

Some test content

To see semantic search do anything interesting, you need content where the meaning is clear but the wording varies. I created six short pages on deliberately different topics:

TitleWhat it’s about
Working From Home Best Practicesremote work setup, focus, video calls
Annual Leave Policy Updatetwenty-five days paid time off, HR portal
Picking the Right Machine for Software Engineering16GB memory, processor, SSD, coding
Company Retirement Savings Plansalary contributions, tax-advantaged fund
Reducing Server Response Timescaching, page loads, p95 latency
Onboarding Checklist for New Hiresfirst week, account setup, buddy system
Getting Ready to Welcome a New Team Memberequipment ready, colleague assigned, check-ins

Each page is two or three sentences in MainBody, with the title in Title.

The important detail: none of these pages use the words a visitor would actually search with. The leave policy never says “vacation.” The laptop page never says “laptop for coding” — it says “machine for software engineering.” The onboarding page never says “new starter.”

That gap is the whole point. It’s what separates a search engine that finds things from one that only finds exact wording.

Your first semantic query

Turning on semantic search is a one-value change. Here’s a normal keyword query:

{
  CKExperienceType(
    where: {
      Title: { exist: true }
      _fulltext: { match: "why is my website taking forever" }
    }
    orderBy: { _ranking: RELEVANCE }
  ) {
    total
    items {
      _score
      Title
    }
  }
}

And here’s the same query with semantic ranking:

{
  CKExperienceType(
    where: {
      Title: { exist: true }
      _fulltext: { match: "why is my website taking forever" }
    }
    orderBy: { _ranking: SEMANTIC, _semanticWeight: 20.0 }
  ) {
    total
    items {
      _score
      Title
    }
  }
}

That’s it — RELEVANCE becomes SEMANTIC, plus a _semanticWeight value. Everything else stays the same.

Why this search phrase: it’s how a real person complains about a slow site. Now look at the page that should answer it.

Reducing Server Response Times
Slow page loads frustrate users and hurt conversion rates. Caching frequently requested data, compressing images, and minimizing database round trips all contribute to a snappier experience. Monitor your p95 latency, not just the average.

Check the query against it word by word — “why,” “website,” “taking,” “forever.” Not one appears on the page. The page talks about slow page loads, latency, response times. Same meaning, entirely different vocabulary.

Two things worth knowing before you run it:

  • _ranking: SEMANTIC does nothing without a _fulltext match or contains clause. That clause supplies the query text the model compares against. Without it, there’s nothing to be semantic about.
  • _semanticWeight defaults to 0.2 if you leave it out. We’ll come back to this number later — it matters more than it looks.

Now run both and compare the results.

Keyword search misses the point entirely

Run both queries against the same content and the difference is hard to miss.

With RELEVANCE — 3 results:

#Title_score
1Company Retirement Savings Plan5.696
2Getting Ready to Welcome a New Team Member3.314
3Picking the Right Machine for Software Engineering3.314

With SEMANTIC — 7 results:

#Title_score
1Reducing Server Response Times12.963
2Company Retirement Savings Plan12.739
3Getting Ready to Welcome a New Team Member11.437
4Annual Leave Policy Update10.384
5Onboarding Checklist for New Hires9.375
6Working From Home Best Practices9.299
7Picking the Right Machine for Software Engineering3.314

Look at what keyword search did with “why is my website taking forever.”

It returned three pages — about retirement savings, onboarding a new hire, and choosing a developer machine. Not one of them has anything to do with a slow website. Meanwhile the page that actually answers the question, Reducing Server Response Times, doesn’t appear at all.

That’s not a ranking problem. Keyword search never saw the right page, because the visitor’s words — “why,” “website,” “taking,” “forever” — appear nowhere in it. The page says slow page loads, latency, response times. Correct meaning, wrong vocabulary.

Semantic search puts it first.

This is the whole value proposition in one comparison. Not “semantic search ranks things a bit better” — keyword search returned zero useful results for a perfectly reasonable question, and semantic search answered it.

Understanding Semantic Weight

You may have noticed the weight in that query was 20.0 — a long way from the documented default of 0.2. Here’s why that matters.

Optimizely Graph doesn’t replace keyword scoring with semantic scoring. It adds them together:

_score = keyword_score + (_semanticWeight × semantic_similarity)

Two numbers per document. keyword_score is traditional text matching. semantic_similarity is how closely the model thinks your query and the document mean the same thing. _semanticWeight decides how much that second number counts.

Why the default often does nothing

The two numbers sit on very different scales. Keyword scores can run into the tens; semantic similarity values are small by comparison. At a weight of 0.2, the semantic contribution is a rounding error next to a keyword gap of ten or twenty points.

So when you switch RELEVANCE to SEMANTIC and the order doesn’t budge, nothing is broken. The semantic scoring is working — it’s just being outvoted by keyword scores on a much bigger scale.

Graph only returns the combined _score, so you can’t see how much came from each side. That makes tuning empirical: change the weight, re-run, look at what moved.

Expect to experiment

There’s no universal setting to copy. The weight you need depends entirely on your content and the query.

Start at the default and increase — 1, 5, 20, higher if needed — and watch for the point where results you know are relevant start climbing. In my seven-page test set, meaningful reordering didn’t appear until the weight was well into double digits. On a larger, more varied corpus the crossover may sit much lower.

The weight is a positive float with no ceiling at 1, 10, or 100, so don’t be shy about going high while testing.

📌 Tired of guessing at the weight? Part 2 shows how to run one query at two weights and recover the exact keyword and semantic scores behind _score — so you can pick a weight from evidence instead of feel: Tuning Semantic Search in Optimizely Graph by Splitting the Score

Final Thoughts

Semantic search in Optimizely Graph is a two-minute switch. Mark your fields searchable, add _ranking: SEMANTIC, done — vector search over your CMS content with nothing extra to run or maintain.

But look again at what actually happened above. Keyword search didn’t rank the right page badly. It never saw it. Three irrelevant results came back, and the one page that answered the question was invisible — because the visitor said “website taking forever” and the page said “latency.”

That’s the gap semantic search closes, and it’s bigger than a sorting tweak.

Three things worth carrying forward:

Always run a control. Same query, RELEVANCE vs SEMANTIC. Without it you can’t tell whether semantic ranking is doing anything or keyword matching is quietly running the show.

Search like a visitor, not like a developer. “Why is my website taking forever,” not “server response time optimization.” Semantic search only earns its place when the words genuinely don’t line up.

Plan to tune. The default weight rarely moves anything. Expect several passes before results land where you want them.

For the oldest problem in site search — people asking for things in words your content doesn’t use — this is the first built-in answer that actually works.

Happy Optimizing!!!

One thought on “Getting Started with Semantic Search in Optimizely Graph

Leave a comment