How to add custom email link with icon to Sanity block content editor?
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
- Ultimate guide for customizing Portable Text - comprehensive walkthrough
- Portable Text documentation - official docs on rendering
- @portabletext/react documentation - for the React rendering library
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 thread18 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.