Add inline variable/shortcode in rich text editor

6 replies
Last updated: Jan 25, 2022
Hi! Is it possible to add an inline variable/short-code in a rich text editor?
AI Update

Yes, absolutely! You can add inline variables/shortcodes in Sanity's rich text editor using custom annotations in Portable Text. Annotations let you embed structured data directly within text spans, which is perfect for creating dynamic variables or shortcodes.

Creating a Custom Variable Annotation

In your schema, define a custom annotation type within your block content field. Here's an example for a "variable" shortcode:

defineField({
  name: 'content',
  type: 'array',
  of: [
    {
      type: 'block',
      marks: {
        annotations: [
          {
            name: 'variable',
            type: 'object',
            title: 'Variable',
            icon: () => '{{}}',
            fields: [
              {
                name: 'key',
                type: 'string',
                title: 'Variable Key',
                description: 'The variable identifier (e.g., "firstName", "companyName")',
              },
              {
                name: 'fallback',
                type: 'string',
                title: 'Fallback Text',
                description: 'Text to display if variable is not available',
              }
            ]
          }
        ]
      }
    }
  ]
})

How It Works in the Editor

Editors can highlight any text in the rich text editor and apply your custom annotation (it'll appear in the toolbar). When they select it, they can configure the variable key and fallback text in a popup form. The annotated text will be visually marked in the editor.

Rendering on the Frontend

When rendering your Portable Text, you'll need a custom serializer to handle your variable annotations. Here's an example using React:

import {PortableText} from '@portabletext/react'

const components = {
  marks: {
    variable: ({value, children}) => {
      // Look up the actual variable value from your data
      const variableValue = lookupVariable(value.key) || value.fallback
      return <span className="variable">{variableValue}</span>
    }
  }
}

<PortableText value={content} components={components} />

This approach gives you full flexibility - you can create variables for personalization, dynamic content, or any other use case where you need to inject values into rich text. The annotations are stored as structured data in the markDefs array, making them queryable and reusable across different platforms.

You can read more about implementing custom annotations in the Ultimate Guide for Customising Portable Text.

Show original thread
6 replies
Hi, Ash and thank you for the reply! ๐Ÿ˜ƒ
The annotations looks to be a perfect fit here
๐Ÿ˜„
Ideally it would come from an external API. My idea is that we are going to generate a sales offer on a webhook, then someone on the sales team can go in to the generated sanity page and customize it to their needs.

These variables should in ideally not be editable, but possible to move around. (not sure if this is possible with the annotations)

I think this would be a rather unconventional way in Sanity, but being able to add some types of variables like this would be really cool.

When you ask where the values comes from, do you have any additional input on ideas here?
Thanks for the info! If you know all of the values when you create the document in the webhook handler, you can store them in the document itself. You can make these fields hidden and/or read-only (https://www.sanity.io/docs/schema-types#hidden-57ac9e4a350a ) to prevent them being changed.
Then, when you render the block content in the frontend, you could substitute each annotation with the corresponding field from the document.

This will allow the user to insert a token in any position in the block text (even multiple times), and have it dynamically converted to the value in the frontend. By storing all of the values in the Sanity document itself, you don't have to worry about stitching multiple APIs together when you render the page.

Is that viable for your use case?
Thank you both for the good pointers here! I will have to fiddle around to see what would work the best for us. I saw this video https://youtu.be/dt5K6gHGpr0?t=567 of you
user Y
where you explain the inline blocks. I did not know those existed, but these looks perfect for the job! If it is possible to combine the inline block with the read-only fields as
user E
suggests, I think that might be the best solution to my use case ๐Ÿ˜„ Thanks a ton and keep up the good work you two! ๐Ÿ’ฏ
I created an hidden array(offerVariables) in my document where I plan to add these selectable variables when creating the document from the hook.Then I created an inline-block with type reference. The "reference to" is a type of "offerVariable"(this is just a string).

My only problem now is that the inline-block reference dropdown is not populated. This is probably because I have not fully understood how to reference to the current document's array "OfferVariables". - Is this even possible?

How can I make the inline-block reference dropdown hold a list of items from an array inside the currently editing document?
๐Ÿ˜…I'm a newbie to Sanity, so please correct me where I'm wrong in my thinking.

Thank you!
PS: offerVariables is not hidden in the image below due to debugging.
After further investigation, I figured out that you can only reference documents. This would not work in my use case due to an insane amount of documents as each variable would be a document(?)
I found a workaround where I created a custom input component to be used instead of a reference in the inline-block.
The custom input component then displays a select that gets populated via the document itself:
https://www.sanity.io/docs/custom-input-widgets#1f69be67cfe0
Works like a charm!
Thank you guys a lot for pushing me the right way
๐Ÿ™Œ
That is awesome, thank you for sharing your solution!

Sanity โ€“ Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?