Thursday, February 26, 2026

Sitecore Hackathon - tips and tricks

Sitecore Hackathon

A few more weeks and it's time for the yearly Sitecore Hackathon. And even though it's hard to match the golden years with over 90 teams participating, 2026 seems to have more than 30 teams again from all over the world. It's still a well known event in the Sitecore community - organized by people with a heart for our Sitecore community so thank you Akshay and all your "community judges" for the effort.


What is this hackathon? 
A hackathon is a fast-paced, collaborative event - in this case lasting 24 hours - where teams of developers and/or domain experts create functional prototypes or software solutions to specific problems. Originating from "hack" and "marathon," these events focus on innovation, networking, and rapid problem-solving.



So basically you get 24h to deliver a project from scratch. And your team of max 3 persons will take up all roles needed to deliver the project. 

I have participated in the Sitecore hackathon for several years with various results. Going from not delivering anything at all in the end to actually really win this thing back in 2017 (oh, time flies).  As I am not participating this year due to a number of reasons I though I might share some lessons that I learned throughout the years. As I learned from the things we did right, the things we did wrong and the many hours of blood, sweat, tears and joy - it feels like post-worthy. 


Before you begin

A good preparation is key to success and that is no different in a hackathon. A few things come to mind in the preparation phase:

Environment

You do not want to be that team that lost hours because they did not have an environment ready for their development. Make sure to check the github access - as requested actually. But your "environment" goes beyond that. Things change of course but to give a few examples of things you do not want to be doing when starting a hackathon:  searching and downloading large Sitecore setup files, windows updates...   Where things like this might sound trivial, they are important. Even cleaning your desk is. 

Also important is a collaboration tool. Even if you are working solo actually. As you will be doing a "project" you do want to keep track of the task you need or want to do. You will want to prioritize them and so on. Depending on your team this can be post-its or any (free) online tool. Pick one, and make sure it is ready to go before the hackathon starts. And you might already be able to add some tasks.

When you read the requirement for a successful entry in the hackathon competition you will notice that there is more than just coding. And subconsciously I actually gave you another task up front - yes, everyone should read the requirements. You should be ready to create documentation with screenshots and video material. Be ready for this - and especially the video. Don't let that final hackathon hour be your first time to create and upload a video as you will be tired and maybe not thinking straight anymore.

Catering

People do need to eat and drink. And as a hackathon is supposed to be fun too, why not go for some nice food and beverages. Yes, sure. Absolutely a fantastic idea as this will contribute to your well-being during a long day. But don't get your hopes up for a feast, as my experience learned me that you will probably not be that hungry at all. So yes, make sure you have everything ready for some nice snacks, drinks and meals but make them rather healthy. Just think what your brain will do after a pizza, double burger, half a bottle wine and a chocolate covered ice cream...  ok, I am exaggerating here but you do get my point.

Timing 

It's gonna be a long day - 24h is a lot and as they give the final idea one hour in advance you could call it 25h.  Some teams take part of that time for a (power) nap. Or even some more sleep if it starts for you in the middle of the night. Depending on your time zone several options are possible. There is no real best practice here as this is also rather personal but just something to think about. 

Idea

One of the challenges of the hackathon is to come up with a decent idea. We will come back to this later as this is also the first task of the day - but you might already be prepared for this. You should not be spending too much time on details up front as there is always a (big) chance everything you thought of is garbage after they announce the actual hackathon idea. But it is a good idea just to be ready with some topics in the back of your head to avoid starting the day with nothing. 


The hackathon day

As I mentioned earlier the hackathon is actually creating a project in a very short time frame. Of course, this is not a full blown project but more like a proof of concept but still. You should go to most of the steps of a project. I'll cover some here and guide you with timings. Time is crucial... 

Idea

Here is the idea again. By now you know the main idea or topic for the hackathon - this can actually also be more than one. So you can start brainstorming keeping in mind the following guidelines:
  • Do not aim too big immediately. This is a proof of concept and it's better to deliver a good mvp (no, not the Sitecore mvp but a minimum viable product). 
  • Try to find an idea that you can scope into a mvp (must) and some extra features (nice to have, if you still have time). This way you can start with the mvp. When that works you will get some peace of mind and decide how to proceed. 
  • Know you team. You should be able to work together so make sure your project can have several tasks that can be done simultaneously by the people in your team. As an example: if 3 person team has only one person who know nextjs you should probably not try a project with 80% nextjs code. 
  • Visibility. It is always nice to show something. Nobody expects a fully designed solution, but something visible is always nice. And if you would need some test content, remember who is going to judge it - it might be something stupid but with test content you can get a smile and a happy judge is better than a grumpy one.
  • Has it been done already? I still remember when I was sitting in our brainstorm room killing several ideas because they were already available in the Sitecore marketplace. And of course it is not because something already exists that it cannot be a good idea but it does make it harder to stand out of course. 
Having a good idea is very important so take your time for it. I remember some years where this took up to 3 hours of our time to get it right. It should not take more than that but do not expect it to be done in half an hour and do not panic if it's not. 

Task list 

After the idea phase (or probably already during) you will start creating a task list. This enables your team also to check the feasibility of the idea. Make sure you have a distinction between the "must haves" (the mvp and the requirements that you need for a valid entry) and the "nice to haves".  This list doesn't have to be complete of course. It's something that lives during the day but it will give a good guidance on your progress and can avoid stress towards the end. 

Coding & fun

It's a hackathon so you will write some code. And try to enjoy it as well. And it is a community thing, so share the joy with some pictures that you can share.  As this is a worldwide event with people from very different time zones, it's always nice to see other teams going for lunch while you are having breakfast ☺

The final

You probably do want to finish at least something and deliver an entry - so the final hours do matter. At least if the other parts went fine but let's assume they did. I remember the organizers sending messages when the deadline came in sight - but if you start getting those and you are not prepared for the final part the stress will start building up. 
In order to deliver a clean and presentable entry you should have a cut off point. My suggestion would be 3 up to even 4h before the deadline. Once that is reached you should be able to finish coding and not pick any new feature tickets anymore. It's time to wrap up and that means quite a few things:
  • Testing. I would assume you have done some tests already during the day but now it's time to do the final round of testing. Make sure everything works.
  • Do a (final) code review.  Check the code, make sure it is clean and consistent. Clean does not mean with comments by the way - it means that people who are a bit familiar with what you are doing are able to understand without asking an AI assistant. 
  • Documentation. If you haven't done this yet (and let's be honest, you probably will not as this is not the most fun part) it's really time to get started on it. Get the documentation, the video and the installation documentation ready.
  • Testing. Not again? Yes, again. But this time, test your installation document. Test it on a clean environment just like the judges would. This will make sure they are able to install your product because if they cannot, you're out.
  • Check the requirements one last time to make sure you really have everything.  

You might think I am exaggerating again, and you might be right. But I remember those last hours as tiresome, stressful and the brain not doing everything the way it did half a day earlier...  but maybe since AI is doing half our work anyways things have changed ☺.  But whatever you do, do not underestimate the last part as it is really painful to crash just before the finish. 


TLDR

I noticed I wrote way too many text..  so in case you skipped it, here is the summary:
  • be prepared up front
  • read the requirements
  • don't eat 3 pizza's
  • don't forget to test thoroughly - including the installation
  • don't wait for the final bell to wrap up
  • and most of all: don't forget to enjoy it

Good luck !


Wednesday, November 12, 2025

Sitecore Forms and the Content Security Policy

Sitecore Forms and the Content-Security-Policy (CSP)

The situation

A site is using Sitecore XM/XP and Sitecore Forms and has implemented a Content Security Policy (CSP) header - which is a best practice. However, Sitecore Forms apparently does not really like these policies as they do tend to block stuff. We had a (rather big) content site that had made edits to their CSP - which is manageable in Sitecore so they can adapt it per site - based on a recent penetration test. After this extension (tightening) of the CSP we noticed the forms from Sitecore Forms were not working anymore. To be more precise: the form appeared fine, but the submit action was not working anymore when it included a "Redirect to Page" action. 

The error

Luckily the error was pretty clear in a browser console:
CSP error

The CSP header was blocking the script. 
The error mentions "Executing inline script violates the following Content Security Policy directive 'script-src ... Either the 'unsafe-inline' keyword, a hash ('sha256-Dcwc6bB3ob8DnpIRKtqhRwu0Wl6bkf7uLnQFk3g6bPQ='), or a nonce ('nonce-...') is required to enable inline execution. The action has been blocked."

The solution

As the code which outputs the inline script is in the Sitecore assemblies, it does not seem an option to add a nonce value. Adding unsafe-inline everywhere is also not a good option as that would lower the quality of the CSP dramatically. So we went for another option and tried to add this unsafe-inline only when there is a form on the page.

Adding unsafe-inline conditionally 

First of all we will add an indicator in the HttpContext to tell us whether there is a form on the page. This can be done in Form.cshtml (located in \Views\FormBuilder), which is the main cshtml file of Sitecore Forms. But as we are using SXA it can also be done in the SXA Forms wrapper. This is Sitecore Form Wrapper.cshtml (located in \Views\Shared) and as we already had some customization in this file to add translations (see my previous post on this topic) we added a few lines here:
var context = HttpContext.Current;
context.Items["WeHaveAForm"] = "Y";
You can name the context item whatever you want of course.

Now we need to act on this context item. Again, there are options. We already had some code that placed a CSP in the header based on a value set in Sitecore on the Site item. But if you do not, a generic solution would be to place it in the global_asax Application_EndRequest function.
var context = HttpContext.Current?.Items["WeHaveAForm"];
if (context != null && context.Equals("Y"))
{
    var csp = Response.Headers["Content-Security-Policy"];
    if (string.IsNullOrEmpty(csp))
    {
        return;
    }

    csp = csp.Replace("script-src", "script-src 'unsafe-inline'");
    var pattern = @"'nonce-[^']+'";
    csp = Regex.Replace(csp, pattern, string.Empty);
    Response.Headers.Set("Content-Security-Policy", csp);
}
As you can see we do just a little bit more here:
  1. We check if the item is present in the context and get out if it is not
  2. We check if we have a csp value - if not we don't need to do anything so we get out
  3. We add the 'unsafe-inline' part to the script-src, if it is present in the csp
  4. We remove the complete nonce if that is present
  5. We set the new value in the Content-Security-Policy header

It is important to also remove the nonce. When a CSP header includes both a nonce and unsafe-inline, the browser ignores the unsafe-inline for scripts or styles and uses the nonce to allow specific inline elements. So if we keep the nonce, the unsafe-inline addition will not do anything.

Conclusion

We fixed the redirects on the forms without adding unsafe-inline on all pages. I would assume that is the best solution we could find here. 

Monday, October 27, 2025

Sitecore Powershell Reporting

Sitecore Powershell Reporting

The Sitecore PowerShell Extensions (SPE) module has a lot of nice features. One of them is creating reports. You get quite some reports out-of-the-box with the module, but you can also create your own. 

I recently noticed however that people are still creating custom pages to create reports for the admins of the customer. Although this gives you a lot of flexibility and perhaps options to include data that resides not in Sitecore this also has some drawbacks compared to creating reports in PowerShell. 

The SPE reports are completely integrated in the Sitecore editing environment. This makes it possible to (amongst others):
  • use context items: the output in the report can be based upon the item it was requested on
  • open items in the editor: directly open the editor from the report
  • more future-proof - especially if you would move to a saas solution
Next to that - the SPE module comes with some very handy features like the Show-ListView which gives your admins a nice toolbox without any effort. 

But enough about the benefits of SPE - let's dive into the example I wanted to show here.

Redirect module

I assume many Sitecore developers have had the request to add some sort of redirect module, enabling editors to create redirects without intervention of IT-people having to change the rewrite instructions.

There used to be several modules floating around in the Sitecore community - but to be honest none of them was ever really perfect. SXA also has it's own implementation of redirects, and that had a different approach than most of the modules. Instead of having a repository of redirect items and using pipeline code to handle them, it is also an option to place the redirects in the tree where you actually want them.

This brings me to the request that led to this post: creating redirect in a non-SXA XM project in a way that they are easily handled by the admins. 

We decided to create a page template with a layout that redirect the page based on some values that can be edited inside the page (eg url, permanent redirect, ... ). You can actually go pretty far in this if you want but as that is not our main focus here let's keep it to that. The focus is here is on answering the question from the Sitecore admins:

"Where are all my redirects?"

Let's answer that question with Sitecore PowerShell. Note that we are using redirects here as an example and this can be done for any kind of data in Sitecore. Also note that out-of-the-box there already is a report to fetch all items based on a template within a folder. But that report is very generic and we wanted a fancy one. Of course, we will copy from this one to give us a head start.

We start with some functions to assist the actual reports.

Sitecore PowerShell Functions

Functions are a way to reuse parts of the code. We will use two functions for our report. The first one is to check whether the item is published to the web database. As many pieces of software, this one is just a copy. But as we in the Sitecore community are honest, we mention our sources. Kudos to Gabriel Streza for this one.

Get-IsPublished
function Get-IsPublished {
    [CmdletBinding()]
    param( 
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
        [ValidateNotNullOrEmpty()]
        [Sitecore.Data.Items.Item]$Item        
    )
   
    $WebDbItem = Get-Item web: -Id $Item.ID

    if ($null -ne $WebDbItem) {
        return $true
    }else{
        return $false
    }
}
I assume this one is fairly easy and doesn't need more explanation. 

The second function will fetch the items and show the report. This is actually the core of the whole thing. 

Show-RedirectReport
function Show-RedirectReport{ 
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, Position=0)]
        [string]$templateId,
        [Parameter(Mandatory=$true, Position=1)]
        [string]$path
    )
        
	$items = Find-Item -Index sitecore_master_index `
	   -Criteria @{Filter = "Equals"; Field = "_template"; Value = "$templateId"},
	   @{Filter = "Equals"; Field = "_language"; Value = "en"},
	   @{Filter = "StartsWith"; Field = "_fullpath"; Value = "$path" }  | Initialize-Item

        Import-Function Get-IsPublished

	if($items.Count -eq 0) {
		Show-Alert "There are no redirects here."
	} else {
	    $props = @{
		Title = "Redirect Report"
		PageSize = 25
	    }
		
	    $items |
		Show-ListView @props -Property @{Label="Name"; Expression={$_.DisplayName} },
	    	    @{Label="Updated"; Expression={$_.__Updated} },
		    @{Label="Updated by"; Expression={[Sitecore.Security.Accounts.User]::FromName($_."__Updated by", $false).Profile.FullName}},
		    @{Label="Path"; Expression={$_.ItemPath} },
		    @{Label="Target - Url"; Expression={$link = $_._.RedirectUrl
		        if ($link.IsInternal) { $link.TargetItem.Paths.Path } else { $link.Url }} },
		    @{Label="Permanent"; Expression={$_._.Permanent.Checked} },
		    @{Label="Published"; Expression={Get-IsPublished -Item $_ } }
	}
}
We will go a little deeper into this one. It has two parts.
First of all we are fetching items through the master index. The function expects a templateID and a path and we will use those to fetch all items of that template in that path (in English).

If we found some items, we are using Show-ListView to display the report. Most of the fields we are showing are quite common but a few deserve some attenion:
  • Updated by: we are using the security account here to fetch the full name of the profile instead of the Sitecore login name as that might be much more readable. Kudos to the one and only Adam Najmanowicz for this one.
  • Target: this can be an internal or external link so we use the "._" notation (short for .PSFields) to access the typed field and check what we need to show
  • Permanent: again using ._ to handle this as a checkbox
  • Published: using our Get-Published function here


The report

Once we have our two functions, the report itself is very easy. We need to collect the parameters to pass to the report function and we should be ok.
$templatePath = "master:\templates\xxx\Shared\Redirect"
$baseTemplate = Get-Item -Path $templatePath
$templateId = $baseTemplate.ID
$path = "/sitecore/content/Sites"

Import-Function Show-RedirectReport
Show-RedirectReport -templateId $templateId -path $path

Close-Window
As this report is intended to be used for one specific template we can hardcode that. We did the same for the base path for now - it's the root path for all content in this case.

Location
In our Sitecore PowerShell module we create (or go to) a folder Reports: "/sitecore/system/Modules/PowerShell/Script Library/xxx/Reports". Here we create a PowerShell Script Library that will be the folder in the reporting tools section. In that folder we place our PowerShell Script - "Redirects". That will result as shown here:




This is nice - we have our report together with the other reports. But we mentioned before that we can make it context aware. Of course, we could ask for a start item in a dialog box. But we can also use a very similar report in the context menu. 

The context menu

To add the report to the context menu and use the context item as the start path, we simply add a new script to our library.
$templatePath = "master:\templates\xxx\Shared\Redirect"
$baseTemplate = Get-Item -Path $templatePath
$templateId = $baseTemplate.ID
$contextItem = Get-Item .
$path = $contextItem.Paths.FullPath

Import-Function Show-RedirectReport
Show-RedirectReport -templateId $templateId -path $path

Close-Window

Not many differences compared to what we already had - just the path is now coming from the context item ".".

Location














We are placing the new script in the folder "Content Editor/Context Menu" in our SPE module. But note that in this case we don't only take care of the location as this location will add the script everywhere but we want to limit it a little bit. We can use the Show Rule to define where the script will be shown in the context menu. In our case, we limit it to items of 2 specific templates but you can use any rule here.

This will look like this:



The final result

I want to show a screenshot of the final result as well - especially for those people who are not so familiar with Sitecore PowerShell yet. For others it will look very familiar - but that doesn't make it less nice :) 


In conclusion I would remind you once more that if you need to bring reports to your editors or admins, think about Sitecore PowerShell. It is a really cool tool that will bring you the result you need. And on top of that you can find a lot of resources already from Sitecore community people that have shared scripts and/or snippets to give you ideas and help you write your own scripts.  And maybe this one extra post helps someone as well.  





One small final note: use this script in the desktop mode...

Thursday, September 18, 2025

EasyLingo 3.0

 EasyLingo 3.0 - update for Sitecore 10.4


Years ago I created (together with Kris - Kevin - Verheire) a module for XM/XP customers that had multiple languages in their site and wanted an easy overview of which language versions existed on every page. To be honest, I almost forgot about this module as my active customers had stopped using it but this year I was asked to consult on an upgrade project for a customer towards Sitecore 10.4 - and this customer was still using the module.

I wrote posts about the module when they got released, and the code is available on Github:

Sitecore 10.4

Apparently the latest version of the module was not compatible with Sitecore 10.4. So I had to make a few adjustments and released version 3.0 - which now again is compatible. 

Experience editor

One problem though - this release seemed to break the experience editor in a weird way. I think it is related to the way some custom javascript code was inserted , but as I am not a javascript expert I might be wrong. Anyway, I couldn't get it fixed so version 3.0.1 was released without XP editor support. 

For the customer that was no issue as they didn't use the functionality in that editor that much and time was running out so I'm afraid that for now this will be it. 

Due to circumstances this issue is still not fixed - but as the code is openly available anyone can jump  in and contribute if you want. 



I assumed (and still do) that the module was very close to the end of it's lifetime, but I was also glad to see that at least some people are actually still using it after all those years.  If you still have a customer on XM/XP with multiple languages, feel free to have a look. And fix my xp editor issue if you can 😉

Wednesday, September 17, 2025

Sitecore RTE broken - h is not a constructor

Sitecore - Telerik RTE broken

Recently we installed the Sitecore Security Bulletin SC2025-004 on a project that is deployed on Azure as a PAAS solution. The project includes SXA, so we also installed the Cumulative hotfix for SXA 10.2.0 and Sitecore XP 10.2 as was mentioned in the security bulletin (for sites running on 10.2 - there are different versions for the other Sitecore releases).

At first everything seemed fine, but then we noticed the Telerik Rich Text Editor (RTE) wasn't working anymore. Or at least, the display of it was completely wrong:



In the meantime we learned the this is not related to the patch install, that it was just a coincident although it is weird that it only happened on the environments where those patches got installed. Also worth mentioned we could not reproduce it locally (although we also installed the whole patch there).


Sitecore StackExchange

I started searching for a solution and while doing that bumped into someone on Sitecore StackExchange who seems to have faced the same issue: both the PAAS environment and the error. Anyways, after finding a fix I also posted it there so I hope he reads it someday and can fix his issue as well. You can check the post on https://sitecore.stackexchange.com/a/39965/237

The error - h is not a constructor



Our main starting point to solving the issue was the error above that we got in the browser. I found my way to a Telerik knowledge-base article that mentions the error: TypeError: h is not a constructor at Sys.Component.create.

As you probably know, the Rich Text Editor in Sitecore XM/XP is from Telerik, and although the error message stack trace was not completely the same as mine, the solution was worth a shot as it was close enough.

Solution ?

The solution they propose is adding a setting in the appSettings part of the web.config. Note that many solutions (including ours) rip this part from the actual web.config to place it in a separate file. The section should already contain some keys related to the Telerik editor. Add this: 

<configuration>
  <appSettings>
<add key="ValidationSettings:UnobtrusiveValidationMode" value="None" />

Once this was added, the Telerik editor was working fine again on our development environment. We deployed the same fix our test environment and there it didn't fix the issue...

User & role manager

So back to the research table. I found something very weird. Actually the trigger was that the user and role manager also started to act weird - displaying a white screen. This led me to the finding that the WebResource.axd calls were giving wrong results - in fact, they all gave the same result. Which of course broke several other javascript calls.

This smelled like caching so I checked with our networking guys and they told me the Front Door which was in front of the PAAS systems (and not local of course) was caching some routes. So my assumption is that is caches those axd calls without taking the full querystring into account. 

The real solution

After disabling that caching in Azure Front Door everything started working again. And hopefully it stays that way. And if not, you will read about it here...


As at least one other person encountered this I thought there might be more so it was worth sharing. If you have it as well, I hope this fixes it for you as well.



Thursday, July 3, 2025

XP editor navigation issues in Sitecore JSS

Experience Editor & Sitecore JSS

As you probably know, Sitecore's experience editor does work on headless sites created with Sitecore SXA and JSS enabling editors to work in headless sites just as they would in the non-headless ones.  We had such a headless site and bumped into a few issues in the experience editor though. 

An important note here is that this headless site is not the only one on this Sitecore instance (10.2) - there are others, non-headless and non-sxa mvc sites. 

Navigation bar

Our first issue was the navigation bar. The navigation bar allows you to navigate to a specific page through a menu structure, a bit like a breadcrumb.  To enable the navigation bar, in the ribbon, on the View tab, select Navigation bar.


Our issue was that the urls in that navigation were wrong - they were using the wrong sc_site parameter in the querystring. 

SiteResolving

First thing we had to check was the configuration of the sites. When adding sites with config patches they can be ordered. We had done this properly, but the headless site is a SXA site and that does not gets added through a config file. However, there also is a config section siteResolving in the ExperienceAccelerator section. This needs to include all your non-sxa sites so you need to patch them in.
<siteResolving patch:source="Sitecore.XA.SitesToResolveAfterSxa.config">
  <site name="exm" resolve="after"/>
  <site name="website" resolve="after"/>
  <site name="unicorn" resolve="after"/>
  <site name="x" resolve="after" patch:source="xxx.Sites.config"/>
  <site name="y" resolve="after" patch:source="xxx.Sites.config"/>
  <site name="z" resolve="after" patch:source="xxx.Sites.config"/>
</siteResolving>
This is a good and necessary step forward, but unfortunately it did not solve our problem yet. 

TargetHostName

The final step to get it working was adding a target hostname to the site definition of our headless sxa-jss site. Not an ideal solution, but it does work. 

Ok, issue number one solved - the navigation bar is working.




LinkFields

We also noticed we had a problem when using LinkFields. 

When we have a RT field in the XP editor, the links inside are transformed into something like https://.../~/link.aspx?_id=5F7338F6FCFC404EB1EF67FAE1F71F7D&_z=z. In components using integrated GraphQL to fetch data however, we notice that the LinkFields are not transformed in the same way (on the same page). We fetch the jsonValue of the fields, use the Link component from the jss sdk in our Next.js code. This work perfectly in normal mode, but in the XP editor we get links like https://.../en/Test%3Fsc_site=X. If editors click on those links they get an error. 

I asked this on Sitecore StackExchange but didn't get an answer there. So I opened a Sitecore support ticket and after a few rounds with questions and answers and logs, videos and data gathering we finally came to a solution. 

But before we come to the solution, let's take a step back to see how we got there. 

Step 1 - the ?

First thing we noticed was the %3F instead of a ? to start the querystring. Apparently there is a bug called "Internal link query string encoded in Next.js app" but we actually just fixed it in the Next app itself by replacing the character. Problem 1 solved.

Step 2 - sc_site

To get the correct site in the links I first wanted to check the data that came from the graphQL queries. To do this, I wanted to use the grapQL edge UI in edit mode and apparently that is possible. How to do that is documented on StackExchange - https://sitecore.stackexchange.com/a/39193/237 - so no need to repeat it all here. In short, if you want to fetch the data in edit mode:
  1. Fetch a token from the Identity Server using Postman (or similar tool) as explained here
  2. Add the authorization header in the http headers
  3. Add sc_mode=edit to your querystring



This way we learned that the url was definitely coming from Sitecore the wrong way. So back to Sitecore... 

Maybe now is a good time to mention that although this headless site is rather new, the other mvc sites on the same platform are not. Instead, it's an old spaghetti platform that we inherited - I guess if you have been around in this business for a while you know what I mean.

And so, eventually we (to be honest, Sitecore support) found that someone patched a setting Languages.AlwaysStripLanguage to false. 

The default value for this setting is true, and it specifies if the StripLanguage processor in the "preprocessRequest" pipeline will parse and remove languages from the url, even when the languageEmbedding attribute of the linkProvider is set to "never".  

Changing this setting back to the original value (true) fixed our issue. And didn't cause any new ones... 


Conclusion

Don't touch settings that you should not touch... 

Thursday, June 12, 2025

Solr query with n-gram

A Search story with Solr N-Gram part 2


Querying our index

In part 1 of this search story I described the setup we did to create a custom Solr index in Sitecore that had a few fields with the n-gram tokenizer. 

A small recap: we are trying to create a search on a bunch of similar Sitecore items that uses tagging but also free text search in the title and description. We want to make sure the users always get results if possible with the most relevant on top.

In this second part I will describe how we queried that index to get what we need. We are trying to use only solr - not retrieving any data from Sitecore - as we want to be ready to move this solution out of the Sitecore environment some day. This is the reason we are not using the Sitecore search layer, but instead the SolrNet library.


Query options and basics

Let's start easy with setting some query options.
var options = new QueryOptions
{
  Rows = parameters.Rows,
  StartOrCursor = new StartOrCursor.Start(parameters.Start)
};
We are just setting the parameters for paging here - number of rows and the start row.
var query = new List<ISolrQuery>()
  {
    new SolrQueryByField("_template", "bdd6ede443e889619bc01314c027b3da"),
    new SolrQueryByField("_language", language),
    new SolrQueryByField("_path", "5bbbd9fa6d764b01813f0cafd6f5de31")
  };
We start the query by setting the desired template, language and path.
We use SolrQueryInList with an IEnumerable to add the tagging parts to the query but as that is not the most relevant part here I will not go into more details. You can find all the information on querying with SolrNet in their docs on Github.


Search query

The next step and most interesting one is adding the search part to the query.
if (!string.IsNullOrEmpty(parameters.SearchTerm))
{
  var searchQuery = new List<ISolrQuery>()
  {
    new SolrQueryByField("titlestring_s", parameters.SearchTerm),
    new SolrQueryByField("descriptionstring_s", parameters.SearchTerm),
    new SolrQueryByField("titlesearch_txts", parameters.SearchTerm),
    new SolrQueryByField("descriptionsearch_txts", parameters.SearchTerm)
  };
  var search = new SolrMultipleCriteriaQuery(searchQuery, SolrMultipleCriteriaQuery.Operator.OR);
  query.Add(search);
  options.AddOrder(new SortOrder("score", Order.DESC));
  options.ExtraParams = new Dictionary<string, string>
  {
      { "defType", "edismax" },
      { "qf", "titlestring_s^9 descriptionstring_s^5 titlesearch_txts^2 descriptionsearch_txts" }
  };
}
else
{
  options.AddOrder(new SortOrder("__smallupdateddate_tdt", Order.DESC));
}

What are we doing here? First of all, we check if we actually have a search parameter. If we do not, we do not add any search query and keep the sorting as default - being the last update date in our case. 

But what if we do have a search string? We make a new solr query that combines 4 field queries. We search in the string and ngram version of the title and description. We combine the field queries with an OR operator and add the query to the global solr query. 

We then set the sorting on the score field - this is the score calculated by solr and indicating the relevancy of the result. 

Last we also add extra parameters to indicate the edismax boosting we want to use. We boost the full string matches most, and also title more than description. 

This delivers us the requirements we wanted:
  • search in title and description
  • get results as often as possible
  • show exact matches first
  • get the most relevant results on top


Wrap up

To wrap things up we combine everything and execute the query:
var q = new SolrMultipleCriteriaQuery(query, SolrMultipleCriteriaQuery.Operator.AND);
logger.LogDebug($"[Portal] Information center search: {solrQuerySerializer.Serialize(q)}");
var results = await solrDocuments.QueryAsync(q, options);
Next to gathering the results note that we can also use the provided serializer to log our queries for debugging.

As a final remark I do need to add that a search like this needs fine-tuning. That is tuning the size of the ngrams and also tuning the boost factors. Change the parameters (one at a time) and test until you get the results as you want them.

And that's it for this second and final part of this ngram search series. As mentioned in the first post, this information is not new and most of it can be found in several docs and posts but I though it would be a good idea to bring it all together. Enjoy your search ;)