Sanity: Serialize custom objects with .NET is a headless CMS. Since a headless CMS acts as an API for our raw data, it makes it easy to distribute the data to multiple channels such as mobile, web and wearables.

A way for Sanity to distribute text across all channels is by using Portable Text. The text editor comes with a decent amount of functionality by default, but we might want to add more complex objects in our text and render these objects in our front end.

In this guide, we will create and add an object category to the Portable Text toolbar. We will then create a custom serializer to build an HTML string in a .NET web application.

Creating the object in Sanity

First, we will create an object called category.

// category.js

export default {
  name: 'category',
  title: 'Category',
  type: 'document',
  fields: [
      name: 'title',
      title: 'Title',
      type: 'string',
      name: 'description',
      title: 'Description',
      type: 'text'

To add this to our existing Portable Text component, put the new types after the block type in the array.


export default {
  title: 'Block Content',
  name: 'blockContent',
  type: 'array',
  of: [
      title: 'Block',
      type: 'block',
      styles: [
        {title: 'Normal', value: 'normal'},
        {title: 'H1', value: 'h1'},
        {title: 'H2', value: 'h2'},
        {title: 'Quote', value: 'blockquote'},
      lists: [{title: 'Bullet', value: 'bullet'}],
      marks: {
        decorators: [
          { "title": "Strong", "value": "strong" },
          { "title": "Emphasis", "value": "em" },
          { "title": "Code", "value": "code" },
    //Adding our custom object type to the portable text toolbar
      title: 'Category',
      type: 'category'

Inside Sanity's Portable Text editor we now have an option to insert a category object.

Creating a category object in our Portable Text editor

Inspecting data from Portable Text

After publishing our page, we can inspect the data for our current page. The data retrieved from our rich text comes as an array. "This is a normal text" is at position 0, and our category object comes as position 1. As we can see, the plain text has a _type: block while our custom object has a _type: category.

Using the inspect functionality, we can find out what we need to retrieve in our front end.

Fetching and outputting data from Blazor

We can now use Sanity LINQ to fetch data from our Sanity Studio. Sanity LINQ also comes with a HTML builder that can parse the portable text and return an HTML string.

//Fetching our post from Sanity using Sanity.Linq
post = await SanityService.SanityDataContext.DocumentSet<Post>()
.Where(p => p.Slug.Current == current)

if (post?.Body?.Any() == true)
    // Build HTML using Sanity.Linq html builder
    post.HtmlBody = await  SanityService.SanityDataContext.HtmlBuilder.BuildAsync(post.Body);
Fetch "Post" from Sanity Studio and parse the body object into an HTML string
@if (!string.IsNullOrWhiteSpace(current) && post != null)
Rendering our HTML string in Blazor

If we try to open our page now, we get an error:

Error: System.Exception: No serializer for type 'category' could be found. Consider providing a custom serializer or setting HtmlBuilderOptions.IgnoreAllUnknownTypes.

 To fix this we need to create a serializer that we can pass to the HTML builder.

Creating our serializer

When adding a serializer we need to supply the type of object the serializer should expect. In this case, it is category. The second parameter is a delegate Func<JToken, SanityOptions, Task> serializer.

//Creating a simple method for serializing.
public Task<string> SerializeCategoryBlock(JToken input, SanityOptions sanity)
    return Task.FromResult("Hello from custom serializer!");

public SanityService()
    _sanityOptions = new SanityOptions
            ProjectId = "<project id>",
            Dataset = "<dataset>",
            Token = "<token>",
            UseCdn = false

        SanityDataContext = new SanityDataContext(_sanityOptions);
        //Adding our serializer
        SanityDataContext.AddHtmlSerializer("category", SerializeCategoryBlock);

Refreshing the page, we can now see our newly rendered category object.

So this is nice and all, but we need to get the information we want from our object for this to be useful.

Getting data from JToken in our serializer

If we look inside Sanity Studio and inspect our body object we can see the category type. This is the data we get inside our JToken in the serializer.

So for this particular object, it is fairly easy to get the data we need.

public Task<string> SerializeCategoryBlock(JToken input, SanityOptions sanity)
    var title = input["title"].ToString();
    var description = input["description"]?.ToString();
    return Task.FromResult($"<h2>{title}</h2><p>{description}</p>");

And there we have it! Our custom object is now rendered in HTML using Sanity LINQ's HTML builder.