Skip to main content

Outline

At a glance
  • Intent mapping: Translating raw keywords into structured GraphQL queries via the _fullText field.
  • Polymorphic results: Using GraphQL fragments to handle multiple content types within a single result set.
  • Data handling: Choosing between typed .NET contracts for safety or untyped JSON for dynamic "shadow" content.
  • Performance: Optimizing via field projection, cursor-based pagination, and query variable injection.

Building effective search experiences in Optimizely CMS 13 requires a sophisticated approach to mapping varied user intents into structured GraphQL queries. Unlike traditional keyword-matching systems, Optimizely Graph allows technical teams to construct queries that account for context, content relationships, and delivery speed. This module explores the technical workflow of capturing a search request, translating it through the GraphQL schema, and processing the resulting JSON into a usable frontend format.

1. Capturing User Intent via Full-Text Search

The first phase of the search lifecycle is the translation of raw user input (search terms) into a query argument that Optimizely Graph can interpret. The system provides a specialized _fullText field across indexed content, which serves as the primary entry point for keyword-based search.

Constructing the Query

A technical search implementation must handle both specific property filtering and broad keyword matching. GraphQL allows these to be combined within a single where clause to prioritize relevance.

query SearchWithIntent($searchTerm: String!, $market: String!) { Content( where: { _fullText: { contains: $searchTerm }, Market: { eq: $market } }, limit: 10 ) { items { _Metadata { types } Title Url } } }

Technical Considerations:

  • Case Insensitivity: Queries against the contains operator are case-insensitive by default in the Optimizely Graph engine.
  • Semantic Relevance: Beyond simple string matching, the schema can be extended to leverage semantic search arguments, which interpret the "meaning" of the query rather than just the characters.

2. Structuring Results with Reusable Fragments

In a search-first architecture, a single query often returns multiple content types (e.g., Pages, Products, and Media). Managing the unique fields of these distinct types within a single result set requires the use of GraphQL Fragments.

Fragments allow developers to define shared field sets once and "apply" them selectively based on the content type returned. This is essential for maintaining clean code and a consistent frontend interface.

# Reusable fragment for common page properties fragment BasicInfo on ContentItem { Title Url _Metadata { types } } query GlobalSearch($searchTerm: String!) { Content(where: { _fullText: { contains: $searchTerm } }) { total items { ...BasicInfo ... on ArticlePage { Author PublishDate } ... on ProductPage { Price Sku } } } }

3. Handling API Responses and Deserialization

Upon executing a query, Optimizely Graph returns a JSON response. The technical challenge lies in converting this raw JSON into a format that the application’s template engine or frontend framework can consume.

Typed Result Processing (.NET)

For C#-based implementations, the Optimizely SDK provides Global Contracts—pre-generated .NET classes that mirror the Graph schema. Deserializing into these types ensures compile-time safety and access to IntelliSense.

// Example of processing search results in an ASP.NET controller public async Task<IEnumerable<ISearchResult>> GetResults(string term) { var query = ConstructGraphQLQuery(term); var response = await _graphClient.SendQueryAsync<SearchResponse>(query); if (response.Errors == null && response.Data?.Content?.Items != null) { // Result items are automatically mapped to defined POCO classes return response.Data.Content.Items; } return Enumerable.Empty<ISearchResult>(); }

Untyped Result Processing (JSON)

In decoupled or highly dynamic environments, developers may opt for Untyped Search. This returns results as raw JsonElement objects. This approach is preferred when the application must handle "Shadow Content" types where local C# class definitions do not exist. Developers then utilize dynamic parsing or JSON paths to extract the required data points.

4. Technical Performance Best Practices

To ensure that translating intent into queries doesn't compromise system performance, developers should implement the following optimizations:

  • Projection Over Expansion: Avoid requesting the entire content object. Only query the specific fields required for the search result card (e.g., Title, Image URL, Snippet).
  • Pagination Implementation: Utilize cursor-based pagination for large result sets. This ensures consistent query performance even when navigating to deep result pages.
  • Variable Injection: Always use GraphQL variables ($searchTerm) rather than string concatenation. This allows the Optimizely Graph engine to cache the "Query Template," reducing overhead for repeated patterns.

Conclusion

The technical bridge between a user's search query and a high-performance results page is built on the precision of GraphQL. By leveraging Full-Text operators for intent capture, Fragments for polymorphic results, and Typed Deserialization for reliable data handling, developers can build search experiences that are both scalable and architecturally sound, providing relevant, structured data across any channel integrated with the Optimizely Graph hub.