Creating a Custom SXA Media Token in Sitecore XM Cloud

In Sitecore XM Cloud you organize media assets per site in the Media Library. You do that in your Media item under the Headless Site. SXA provides helpful tokens like $siteMedia, but they resolve to virtual media folders under the content tree, which breaks when you want to append subfolders (e.g. $siteMedia/Documents/*).

I recently needed to solve this problem in a multisite solution: allow editors to select media items (like PDFs) from a per-site folder in the Media Library using a field source like:

query:$siteMediaRoot/Documents/*

The Problem

Using query:$siteMedia/Documents/* in a field source results in Sitecore falling back to the entire Media Library, or showing no items. That’s because $siteMedia resolves to a virtual folder, and appending paths like /Documents doesn’t work.

I needed a token that:

  • Resolves to the actual media library root for the current SXA site (not the virtual one)
  • Supports appending subfolders like /Documents
  • Works across multiple sites with a shared component

Custom $siteMediaRoot Token

I created a custom SXA token called $siteMediaRoot by adding a processor to the resolveTokens pipeline. This token resolves to the real media folder path (e.g. /sitecore/media library/SiteA) so I can safely use subfolders. This code requires that there is a folder under Media library that has the same name as the Site. Then you just reference that to the Media item under your site.

Token Processor Code

public class ResolveSiteMediaRootToken : ResolveTokensProcessor
{
    private const string Token = "$siteMediaRoot";
    private readonly IMultisiteContext _multisiteContext;

    public ResolveMediaSiteRootToken() : this(ServiceLocator.ServiceProvider.GetService<IMultisiteContext>()) {}

    public ResolveSiteMediaRootToken(IMultisiteContext multisiteContext)
    {
        _multisiteContext = multisiteContext;
    }

    public override void Process(ResolveTokensArgs args)
    {
        if (!args.Query.Contains(Token)) return;

        var mediaRootItem = GetSiteMediaRoot(args.ContextItem);
        if (mediaRootItem == null) return;

        args.Query = ReplaceTokenWithItemPath(args.Query, Token, () => mediaRootItem, args.EscapeSpaces);
    }

    private Item GetSiteMediaRoot(Item contextItem)
    {
        var virtualRoot = _multisiteContext.GetSiteMediaItem(contextItem);
        if (virtualRoot == null) return null;

        var siteItem = _multisiteContext.GetSiteItem(contextItem);
        var siteName = siteItem?.Name;
        if (string.IsNullOrEmpty(siteName)) return virtualRoot;

        var matched = virtualRoot.GetVirtualChildren()
            .FirstOrDefault(c => c.Name.Equals(siteName, StringComparison.OrdinalIgnoreCase));

        return matched ?? virtualRoot;
    }
}

Pipeline Patch

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <resolveTokens>
        <processor type="YourNamespace.ResolveMediaSiteRootToken, YourAssembly"   patch:before="processor[@type='Sitecore.XA.Foundation.TokenResolution.Pipelines.ResolveTokens.EscapeQueryTokens, Sitecore.XA.Foundation.TokenResolution']" />
      </resolveTokens>
    </pipelines>
  </sitecore>
</configuration>

NuGet Setup

In your root packages.props (or Directory.Packages.props), add:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <PlatformVersion>1.*</PlatformVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Update="Sitecore.XmCloud.XA.Foundation.Multisite" Version="$(PlatformVersion)" />
    <PackageReference Update="Sitecore.XmCloud.XA.Foundation.TokenResolution" Version="$(PlatformVersion)" />
  </ItemGroup>
</Project>

Then in your .csproj file:

<PackageReference Include="Sitecore.XmCloud.XA.Foundation.Multisite" />
<PackageReference Include="Sitecore.XmCloud.XA.Foundation.TokenResolution" />

Final Usage

Now I can use the following in a Multilist or Treelist field source:

query:$mediaSiteRoot/Documents/*

And it dynamically resolves to:

/sitecore/media library/SiteA/Documents/*

Leave a comment