๐Ÿ”ฎ Sanity Create is here. Writing is reinvented. Try now, no developer setup
Last updated April 27, 2021

Inline audio player in Sanity.io rich text

By Henrique Doro

How to use the Portable Text Editor's flexibility to insert dynamic content in the middle of paragraphs

We can host audio files in Sanity and plug them into paragraphs by adding custom types to the block.of array. You can see this in action in my Learn GROQ guide. Without further ado, here's the process:

Starting with the schema, we need to add the inlineAudio schema type to our block content's of property (documentation on this property):

export default {
  name: "contentBody",
  title: 'Body of content',
  type: 'array',
  of: [
      type: 'block',
      of: [
          name: 'inlineAudio',
          type: 'file',
          title: 'Inline audio player',
          options: {
            accept: 'audio/*',
    {type: "image"}

This will allow us to add the "Inline audio player" element to our paragraph from the insert menu:

Screenshot of the rich text editor of this guide with the insert menu open

Now editors have the ability to add inline audio players to their content! ๐ŸŽ‰๐ŸŽ‰ Let's cover how to render this in the front-end. Here's a portion of the component that renders the block content of my articles (I'm using React and the @sanity/block-content-to-react package):

import * as React from 'react'
import BlockContent from '@sanity/block-content-to-react'

import AudioPlayer from '../AudioPlayer'

const serializers = {
  types: {
    // Handler for the "inlineAudio" _type
    inlineAudio: ({ node }) => {
      // The component we use to render the actual player
      return <AudioPlayer {...node} />

const ArticleBlockContent = (props) => {
  return (

export default ArticleBlockContent

And here's the AudioPlayer component that actually renders the data into an actionable button for users:

import * as React from 'react'

const AudioPlayer = (props) => {
  // Used to store the audio element once instanciated
  const [audioEl, setAudioEl] = React.useState()

  if (!props.asset?._ref) {
    return null
  const { _ref: ref } = props.asset
  // Example:
  // From: file-ff7d1c2d7bd5ac367359d57f0319f5f458bc3c3d-m4a
  // To: https://cdn.sanity.io/files/q2j8cwsg/production/ff7sfgc2d7bd5ac367359d57f0319f5f458bc3c3d.m4a?dl

  const assetRefParts = ref.split('-') // ["file", "ff7...", "m4a"]
  const id = assetRefParts[1] // "ff7..."
  const format = assetRefParts[2] // "m4a"
  const assetUrl = `https://cdn.sanity.io/files/${process.env.NEXT_PUBLIC_SANITY_PROJECT_ID}/${process.env.NEXT_PUBLIC_SANITY_DATASET}/${id}.${format}`

  function playAudio() {
    try {
      if (!audioEl) {
        const audio = new Audio(assetUrl)
      } else {
    } catch (error) {}
  return (
    <button onClick={playAudio} aria-label="Play audio">

export default AudioPlayer

And that's it! There are many more interesting use cases of inline blocks, some of which I hope to cover in the future.

Reach me at meet@hdoro.dev or @hdorodev if you have any questions ;)

Sanity โ€“ build remarkable experiences at scale

Sanity Composable Content Cloud is the headless CMS that gives you (and your team) a content backend to drive websites and applications with modern tooling. It offers a real-time editing environment for content creators thatโ€™s easy to configure but designed to be customized with JavaScript and React when needed. With the hosted document store, you query content freely and easily integrate with any framework or data source to distribute and enrich content.

Sanity scales from weekend projects to enterprise needs and is used by companies like Puma, AT&T, Burger King, Tata, and Figma.