# Sanity

## Introduction

In this guide you will learn how to integrate Shopstory visual builder with Sanity CMS. All the code snippets are taken from our example project which you can find here: <https://github.com/shopstory-app/shopstory-examples> (`examples/sanity-next-13`). We highly recommend installing the example project on an empty Sanity project to play around.

{% hint style="info" %}
**Important!** Our examples repository is a monorepo that works correctly only with `pnpm`. Please read `README.md` before starting.
{% endhint %}

## Getting started

### Installation

The first step is installing Shopstory - Sanity integration package in your project.

```
npm install @shopstory/sanity
```

### Add Shopstory plugin

Shopstory is just a field in your schema. You can think of Shopstory as something similar to rich text - just way more advanced. You can add Shopstory anywhere within your content model.&#x20;

It all starts with adding `shopstory` plugin to the `sanity.config.ts`:

```typescript
import { defineConfig } from "sanity";
import { shopstory } from "@shopstory/sanity";
import { mediaAssetSource } from "sanity-plugin-media";

const SITE_URL = "http://localhost:3000" // root URL of your site

export default defineConfig({
  // ...

  plugins: [
    // ...
    shopstory({
      accessToken: "your-shopstory-access-token",
      canvasUrl: `${SITE_URL}/shopstory-canvas`,
      locales: [
        {
          code: "en",
          isDefault: true,
        },
        {
          code: "de",
          fallback: "en",
        },
      ],
      assetSource: mediaAssetSource,
    }),
  ],
});
```

Let's look at `shopstory` parameters:

1. `accessToken` - Shopstory access token required for authentication.
2. `canvasUrl` - the URL to the Shopstory canvas page you created in previous chapter. In the example code above we're hardcoding `SITE_URL` constant value to `http://localhost:3000`. In real-world scenario you will probably want it to come from environment variable (local and production URLs might differ).
3. `locales` - the list of locales accepted by Shopstory. The `fallback` parameter tells which locale should be displayed if there is no translation for the current one. In the example above if `de` is missing the translation then user will see `en`. `isDefault` determines the root locale which is applied when fallback can't be found. Only one locale can be the default locale.
4. `assetSource` - this properly tells Shopstory which Sanity asset source should be used when selecting media from Shopstory. In this example we use `mediaAssetSource` from the one and only [`https://www.sanity.io/plugins/sanity-plugin-media`](https://www.sanity.io/plugins/sanity-plugin-media)

### Add Shopstory field to your schema

Let's create a new document type that represents a piece of Shopstory content. We'll name it `shopstoryBlock` and it's available in `src/schemas/shopstory-block.ts`:

```typescript
import { defineType, defineField } from "sanity";

export default defineType({
  name: "shopstoryBlock",
  title: "Shopstory Block",
  type: "document",
  fields: [
    defineField({
      name: "title",
      title: "Title",
      type: "string",
    }),
    defineField({
      name: "content",
      title: "Content",
      type: "shopstory",
    }),
  ],
});

```

That's all we need - just add `shopstory` field to your content model.

### Add Sanity plugin to the Shopstory config

We already added Shopstory plugin to Sanity, but we must also add Sanity plugin to Shopstory. Let's edit `shopstory/config.ts`:

```typescript
import type { Config } from "@shopstory/core";
import { sanityPlugin } from "@shopstory/sanity";
import sanityConfig from "../sanity.config";

export const shopstoryConfig: Config = {
  // ...
  plugins: [
    sanityPlugin({
      dataset: sanityConfig.dataset,
      projectId: sanityConfig.projectId,
      token: process.env.NEXT_PUBLIC_SANITY_API_TOKEN,
    }),
  ],
};
```

Just populate `dataset`, `projectId` and `token` with your Sanity credentials.

## Let's build some content

It's time to build something visually. Let's create a new `shopstoryBlock` document and add some sections visually:

{% embed url="<https://player.vimeo.com/video/814202902>" %}
Building content with Shopstory inside of Sanity CMS
{% endembed %}

## Displaying content

The last step is to render Shopstory content in your project. For the demo purpose we'll create a page that renders Shopstory Block entry by id - `pages/shopstory-block/[entryId].tsx`:

```typescript
import { Metadata, RenderableContent, ShopstoryClient } from "@shopstory/core";
import { Shopstory, ShopstoryMetadataProvider } from "@shopstory/react";
import type { GetStaticPaths, GetStaticProps, NextPage } from "next";
import { createClient } from "next-sanity";
import sanityConfig from "../../sanity/sanity.config";
import { shopstoryConfig } from "../../shopstory/config";
import { DemoShopstoryProvider } from "../../shopstory/provider";

type ShopstoryBlockPageProps = {
  renderableContent: RenderableContent;
  meta: Metadata;
};

const ShopstoryBlockPage: NextPage<ShopstoryBlockPageProps> = (props) => {
  return (
    <DemoShopstoryProvider>
      <ShopstoryMetadataProvider meta={props.meta}>
        <Shopstory content={props.renderableContent} />
      </ShopstoryMetadataProvider>
    </DemoShopstoryProvider>
  );
};

export const getStaticPaths: GetStaticPaths = () => {
  return { paths: [], fallback: "blocking" };
};

export const getStaticProps: GetStaticProps<
  ShopstoryBlockPageProps,
  { entryId: string }
> = async (context) => {
  let { params, preview, locale = "en" } = context;

  if (!params) {
    return { notFound: true };
  }

  const rawContent = await fetchShopstoryContentJSONFromCMS(
    params.entryId,
    locale,
    !!preview
  );

  const shopstoryClient = new ShopstoryClient(shopstoryConfig, {
    locale,
    sanity: { preview },
  });
  const renderableContent = shopstoryClient.add(rawContent);
  const meta = await shopstoryClient.build();

  return {
    props: { renderableContent, meta },
    revalidate: 10,
  };
};

async function fetchShopstoryContentJSONFromCMS(
  entryId: string,
  locale: string,
  preview: boolean
): Promise<any> {
  const sanityClient = createClient({
    apiVersion: "2023-03-30",
    dataset: sanityConfig.dataset,
    projectId: sanityConfig.projectId,
    useCdn: false,
    token: process.env.NEXT_PUBLIC_SANITY_API_TOKEN,
  });

  const entryIdQuery =
    preview && !entryId.startsWith("drafts.") ? `drafts.${entryId}` : entryId;

  const documents = await sanityClient.fetch(
    `*[_id == "${entryIdQuery}"]{"content": content.${locale}}`
  );

  return documents[0].content;
}

export default ShopstoryBlockPage;

```

Most of the code is CMS-agnostic and relates to how Shopstory SDK works (`ShopstoryClient`, `ShopstoryMetadataProvider`, `Shopstory`, etc). This will be explained in the next chapter: [Displaying Content](#displaying-content). &#x20;

However, there are 2 Sanity-specific pieces of code here.

First one is the body of the `fetchShopstoryContentJSONFromCMS` function. As you can see it's a standard GROQ call to the Sanity API. In this example we fetch the field named `shopstory` from our newly created `shopstoryBlock` document.

The second Sanity-specific piece of code is here:

```typescript
 const shopstoryClient = new ShopstoryClient(shopstoryConfig, {
    locale,
    sanity: { preview }, // Sanity-specific code
  });
```

Here we tell the Shopstory-Sanity Plugin if the Sanity resources linked in Shopstory content (media, documents or files) should be fetched as drafts (`preview: true`) or as published documents  (`preview: false`).

{% hint style="info" %}
`getStaticPaths` implementation is dummy just for the sake of demo simplicity.&#x20;
{% endhint %}

## Further reading

Now it's time to read [Displaying Content](https://docs.shopstory.app/getting-started/displaying-content) chapter and understand how Shopstory SDK works.
