How to add custom email link with icon to Sanity block content editor?

18 replies
Last updated: Nov 29, 2025
Hey everyone, I'm brand new here and brand new to web dev too. 🙂 I just deployed my first website (ericrodgers.io ) using the sanity/gatsby portfolio template. I'm trying to figure out how to customize some block content to create a link that opens email. Ideally I would like that new feature to have an email icon in the block content menu bar. So I guess this would behave like the built in "link" feature, but it would have its own icon, it would validate for email, and it would prepend the anchor tag's href with "mailto: " and maybe even append it with ?subject &/or &body default text... In the block content editor I want to be able to highlight some text that says "get in touch", click an email icon in the block content menu next to the decorator and link options, and then enter my email address, so that on the front end gatsby side of my site anyone who is visiting my page can click the "get in touch" text and it will open their email and populate the "to:" field with my address. I hope this makes sense! Thanks for your help!
AI Update

Great to see you taking your first steps into web dev and already customizing your Sanity setup! What you're looking to do is create a custom annotation for email links in Portable Text. Here's how to make it work:

Setting Up the Schema

In your bioPortableText.js schema, you'll want to add a custom annotation to the marks.annotations array. Based on what you've shared, you're on the right track! Here's a clean example:

import {MdEmail} from 'react-icons/md';

export default {
  name: 'bioPortableText',
  type: 'array',
  title: 'Excerpt',
  of: [
    {
      title: 'Block',
      type: 'block',
      marks: {
        annotations: [
          // Your existing link annotation
          {
            name: 'link',
            type: 'object',
            title: 'URL',
            fields: [
              {
                title: 'URL',
                name: 'href',
                type: 'url'
              }
            ]
          },
          // Your custom email annotation
          {
            name: 'emailLink',  // Avoid 'email' as it's a reserved keyword
            type: 'object',
            title: 'Email',
            icon: MdEmail,
            fields: [
              {
                title: 'Email address',
                name: 'email',
                type: 'string',
                validation: Rule => Rule.regex(
                  /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
                  {
                    name: 'email',
                    invert: false
                  }
                ).error('Please enter a valid email address')
              }
            ]
          }
        ]
      }
    }
  ]
}

Important note: As mentioned in this community thread, email is a reserved keyword in Sanity, so naming your annotation something like emailLink or emailAnnotation will help avoid conflicts.

Rendering with @portabletext/react

For rendering on your Gatsby frontend, you'll use custom mark components. Since you're working with Gatsby, you might be using the older @sanity/block-content-to-react, but I'd recommend migrating to @portabletext/react for better support.

Here's how to set up your serializers:

import {PortableText} from '@portabletext/react'

const components = {
  marks: {
    emailLink: ({value, children}) => {
      const {email} = value
      const mailto = `mailto:${email}?subject=Hi!&body=I love your website!`
      return (
        <a href={mailto} rel="noopener noreferrer">
          {children}
        </a>
      )
    },
    link: ({value, children}) => {
      const {href, blank} = value
      return blank ? (
        <a href={href} target="_blank" rel="noopener noreferrer">
          {children}
        </a>
      ) : (
        <a href={href}>{children}</a>
      )
    }
  }
}

// Then in your component:
<PortableText value={bioContent} components={components} />

Note that I removed target="_blank" from the email link - email links don't need it and it can cause page reloads as you experienced.

Understanding Annotations

Annotations in Portable Text are powerful because they let you attach structured data to text spans. They're stored in the markDefs array of each block, with each text span referencing the annotation by its _key. This is what makes Portable Text so flexible - you can add any custom data structure to your text, not just simple formatting.

Email Validation

For email validation, Sanity has a built-in email field type you could use instead of string, but the regex approach you're using works great too. Just make sure your regex pattern is solid - the one I've provided above is simpler but catches most cases.

Additional Resources

The key thing to remember: the name you give your annotation in the schema must match the property name in your marks object when rendering. So if you name it emailLink in the schema, it needs to be emailLink in your components object.

Hope this helps you get that email link working! Feel free to share your progress. 🚀

Show original thread
18 replies

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?