Difference between S.editor() and S.document() and how to disable deletion and unpublishing of certain document types in Sanity.io

8 replies
Last updated: Feb 8, 2022
Maybe a noobie question, but what is the difference between
S.editor()
and
S.document()
I have created a settings document parent type with nested settings menu. But I want to disable the possibility to make it visible as a singular type instead of a document which can be repeated like posts, articles blogs etc. How can I prevent these pages from being deleted, unpublished?
I have a custom "*documentActions*" but it's kinda ugly, wondering if you build the menu with ListItems if there is a property out of the box to disable deletion, unpublish, for these settings pages.

My code.. but wondering if there is a cleaner way
πŸ˜„?
import userStore from "part:@sanity/base/user";

import defaultResolve, {
  UnpublishAction,
  DuplicateAction,
  DeleteAction,
} from "part:@sanity/base/document-actions";

const documentActions = (props) => {
  return props.type === "navigation" ||
    props.type === "general" ||
    props.type === "featured" ||
    props.type === "promo" ||
    props.type === "cookie" ||
    props.type === "friends"
    ? [
        ...defaultResolve(props).filter(
          (action) =>
            action !== UnpublishAction &&
            action !== DuplicateAction &&
            action !== DeleteAction
        ),
      ]
    : [...defaultResolve(props)];
};

export default documentActions;

AI Update

Looking at your code, I can see you're working with Sanity V2 (using the old part: imports). The good news is that your approach to filtering document actions is actually the standard way to handle this!

S.editor() vs S.document()

S.editor() is simply a deprecated alias for S.document() - they do the exact same thing. In older versions of Sanity, S.editor() was used, but it was renamed to S.document() for clarity. You should use S.document() in your code, though S.editor() will still work as it points to the same method.

Both methods create a document view in the Structure Builder. The difference you're looking for is actually between list items (S.listItem()) and document views (S.document()), where list items are the navigation elements and document views are what displays when you open a document.

Preventing Deletion/Unpublish for Singleton Documents

Your current approach with custom document actions is actually the correct and cleanest way to prevent deletion and unpublishing. There isn't a built-in property in the Structure Builder's S.listItem() or S.document() that disables these actions - you need to filter the document actions as you're doing.

However, here's a slightly cleaner version for V2:

import defaultResolve, {
  UnpublishAction,
  DuplicateAction,
  DeleteAction,
} from "part:@sanity/base/document-actions";

const SINGLETON_TYPES = [
  "navigation",
  "general", 
  "featured",
  "promo",
  "cookie",
  "friends"
];

const documentActions = (props) => {
  if (SINGLETON_TYPES.includes(props.type)) {
    return defaultResolve(props).filter(
      (action) => ![UnpublishAction, DuplicateAction, DeleteAction].includes(action)
    );
  }
  return defaultResolve(props);
};

export default documentActions;

Hiding from "Create New Document" Menu

To prevent these singleton documents from appearing in the "Create New Document" menu, you can also filter the templates in your sanity.json:

{
  "parts": [
    {
      "name": "part:@sanity/base/new-document-structure",
      "path": "./newDocumentStructure.js"
    }
  ]
}

Then create newDocumentStructure.js:

import S from '@sanity/base/structure-builder'

const SINGLETON_TYPES = ['navigation', 'general', 'featured', 'promo', 'cookie', 'friends'];

export default [
  ...S.defaultInitialValueTemplateItems().filter(
    (template) => !SINGLETON_TYPES.includes(template.spec.id)
  )
]

Modern Approach (Studio V3+)

If you ever upgrade to Studio V3, the approach is similar but with updated imports. The singleton document guide shows the modern implementation, which uses the same filtering concept but with the newer API structure in sanity.config.js:

// For V3+
schema: {
  templates: (templates) =>
    templates.filter(({ schemaType }) => !singletonTypes.has(schemaType))
},
document: {
  actions: (input, context) =>
    singletonTypes.has(context.schemaType)
      ? input.filter(({ action }) => action && singletonActions.has(action))
      : input
}

Your current solution is solid - sometimes the "ugly" way is just the way it needs to be done! πŸ˜„ The document actions approach is the standard pattern for controlling which actions are available for different document types.

Hey
user M
πŸ˜„ !
I have this now, Will refactor later on. Where should the second code part go?


import defaultResolve, {
  PublishAction,
} from "part:@sanity/base/document-actions";

const singleEdits = [
  "navigation",
  "general",
  "featured",
  "promo",
  "cookie",
  "friends",
];

export default function resolveDocumentActions(props) {
  if (singleEdits.includes(props.type)) {
    return defaultResolve(props).filter((action) => action == PublishAction);
  }

  return [...defaultResolve(props)];
}

Ah sorry, that your new document menu. You can see how to set that up here .
user M
The first part seems to be enough. It’s only possible to Publish action and not create any new document inside the settings and sub settings menu items.
Not sure, but when you apply
$.editor()
instead of
$.document()
it behaves like a single item.
Take a look:


import React from "react";

import S from "@sanity/base/structure-builder";

import { Gear, Cookie, List, Star, Sliders, FlagBanner } from "phosphor-react";

import { icon } from "../lib/helpers";

const { color, weight, size } = icon;

const generalMenu = S.listItem()
  .title("General")
  .icon(() => <Gear color={color} weight={weight} size={size} />)
  .child(
    S.editor()
      .schemaType("general")
      .documentId("generalSettings")
      .title("General Settings")
  );

const navMenu = S.listItem()
  .title("Navigation")
  .icon(() => <List color={color} weight={weight} size={size} />)
  .child(
    S.editor()
      .schemaType("navigation")
      .documentId("navigationSettings")
      .title("Navigation Settings")
  );

const featuredMenu = S.listItem()
  .title("Featured Articles")
  .icon(() => <Star color={color} weight={weight} size={size} />)
  .child(
    S.editor()
      .schemaType("featured")
      .documentId("featuredSettings")
      .title("Featured Articles")
  );

const cookieMenu = S.listItem()
  .title("Cookie Consent")
  .icon(() => <Cookie color={color} weight={weight} size={size} />)
  .child(
    S.editor()
      .schemaType("cookie")
      .documentId("cookieSettings")
      .title("Cookie Consent Settings")
  );

const promoBarMenu = S.listItem()
  .title("Promo Bar")
  .icon(() => <FlagBanner color={color} weight={weight} size={size} />)
  .child(
    S.editor()
      .schemaType("promo")
      .documentId("promoBarSettings")
      .title("Promo Bar Settings")
  );

export const settingsMenu = S.listItem()
  .title("Settings")
  .child(
    S.list()
      .title("Settings")
      .items([
        generalMenu,
        S.divider(),
        navMenu,
        S.divider(),
        featuredMenu,
        S.divider(),
        cookieMenu,
        S.divider(),
        promoBarMenu,
      ])
  )
  .icon(() => <Sliders color={color} weight={weight} size={size} />);
documentActions.js

I’v included the DiscardChanges
πŸ˜„ is it good like so?
import defaultResolve, {
  DiscardChangesAction,
  PublishAction,
} from "part:@sanity/base/document-actions";

console.table(defaultResolve);

const singleEdits = [
  "navigation",
  "general",
  "featured",
  "promo",
  "cookie",
  "friends",
];

export default function resolveDocumentActions(props) {
  if (singleEdits.includes(props.type)) {
    return defaultResolve(props).filter(
      (action) => action === PublishAction || action === DiscardChangesAction
    );
  }

  return [...defaultResolve(props)];
}

Yep, that should work! Not sure on the
.editor
and
.document
methods though. I always just use
.document
since it has more documentation in the reference.
user M
Oke 😊, finally works like a charm the only problem now, I need the β€œ*`DiscardChangesAction`*” it’s working for general, promo, cookie. But not for navigation and featured which are both array fields?

import defaultResolve, {
  DiscardChangesAction,
  PublishAction,
} from "part:@sanity/base/document-actions";

const singleSettings = [
  "general",
  "navigation",
  "featured",
  "promo",
  "cookie",
  "friends",
];

const resolveDocumentActions = (props) => {
  if (singleSettings.includes(props.type)) {
    console.log(props);
    return defaultResolve(props).filter(
      (action) => action === PublishAction || action === DiscardChangesAction
    );
  }

  return [...defaultResolve(props)];
};

export default resolveDocumentActions;
I would think that
navigation
and
featured
aren't the actual types that get passed in, so
props.type
is something else. Can you check by `console.log`ging
props.type
?
user M
Figured it out never published anything to make the discard button visible thanksπŸ™Œ
user M
Figured it out never published anything to make the discard button visible thanksπŸ™Œ

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?