Guide

How to embed an Instagram post in Portable Text

In this guide you'll learn how to add an Instagram post as structured content and create a preview for it in the editor for Portable Text.

Rune Botten

Rune is a solutions engineer at Sanity

If you’ve used rich text with Sanity you might be familiar with the following definition of a typical body field on a document, and if not, this is a good chance to learn what it is:

// schemas/documents/post.js

export default {
  type: "document",
  title: "Blog post",
  name: "post",
  fields: [
    {
      type: "string",
      name: "title"
    },
{
title: "Body",
name: "body",
type: "array",
of: [{ type: "block" }]
}
] };

This code example defines a document named post with a rich text field named body, specified as an array of block objects which represent the text blocks themselves. But we are of course not limited to block! Often times you'll include {type: "image"} in this array or your own custom objects to allow more things than just text. Let’s make a custom object to hold a reference to an Instagram post, then provide a preview for that post in the rich text editor.

Why not just paste in an <object>, <iframe>, special templating tag or similar, you might be asking yourself? Because if we only save the data (url) and what it means (instagram post), we have full flexibility later when we present it. For a web site that probably means rendering it as an iframe, object or embed tag. For other presentations like in a native mobile app or even printed media this is probably not the right choice. You might not have any other presentations than web right now, but who knows the future? Also, you probably don't want a redesign of your website to involve a redesign of all your content as well.

We'll start by creating a new file called instagramPost.js and defining an object that can hold the URL for the Instagram post. To help the editors out, lets also include a helpful text on how they can obtain the right URLs.

// schemas/objects/instagramPost.js

export default {
  type: "object",
  name: "instagramPost",
  title: "Instagram Post",
  fields: [
    {
      name: "url",
      type: "url",
      description: "Visit an Instagram post in a browser and copy the URL."
    }
  ]
};

Before we can reference this new object in documents or from other objects we need to let Sanity Studio know about it. We do this by including it in the array of schema types in schema/schema.js

// schemas/schema.js

import createSchema from "part:@sanity/base/schema-creator";
import schemaTypes from "all:part:@sanity/base/schema-type";

import post from "./documents/post";
import instagramPost from "./objects/instagramPost";
export default createSchema({ name: "default", types: schemaTypes.concat([ /* Your types here! */ post,
instagramPost
]) });

Now we can add it to the array of accepted content types where we define our body field in schemas/post.js

// schemas/documents/post.js

export default {
  type: "document",
  title: "Blog post",
  name: "post",
  fields: [
    {
      type: "string",
      name: "title"
    },
    {
      title: "Body",
      name: "body",
      type: "array",
      of: [
        { type: "block" },
{ type: "instagramPost" }
] } ] };

The editor for Portable Text Editing now looks like this:

Great! We're letting editors add and change Instagram URLs, right in the text body, but displaying the raw object data value isn’t very user friendly. We can fix that by creating a preview for the instagramPost object. Sanity Studio is extensible in many ways, and one of them is the ability to provide a React component for previewing your custom content in the editor.

To get a head start, we'll use an open source React component for embedding Instagram posts called react-instagram-embed. Install this in the folder of your Sanity Studio (npm i react-instagram-embed --save) and be sure to restart the development server (sanity start).

First we create a React component that receives the URL property from the object it should preview. In this example we put the component in a file called instagramPost.js inside a folder called previews. You can structure your code differently if you want, as long as you use the correct file paths in your code.

// schemas/previews/instagramPost.js

import React from "react";
import InstagramEmbed from "react-instagram-embed";

const InstagramPreview = ({ value }) => {
  const { url } = value;
  if (!url) {
    return <p>Missing URL for Instagram post</p>;
  }

  return (
    <InstagramEmbed
      url={url}
      maxWidth={480}
      containerTagName="div"
      injectScript
    />
  );
};

export default InstagramPreview;

Then we import the preview component in our object definition for instagramPost and tell Sanity Studio to use the exported React component for the preview of its value:

// schemas/objects/instagramPost.js
import InstagramPreview from "../previews/instagramPost";
export default { type: "object", name: "instagramPost", title: "Instagram Post", fields: [ { name: "url", type: "url", description: "Visit an Instagram post in a browser and copy the URL" } ],
preview: {
select: { url: "url" },
component: InstagramPreview
}
};

And with that the editorial experience now looks like this

Very nice! Now you know how to create custom objects, allow them to be inserted amongst text blocks in the Sanity Studio rich text editor and also how to create nice previews for the data they store.

I see that there is an npm module called react-youtube. Maybe you can create your own YouTube embed for Portable Text using what you learned in this guide?