Hide "Create new" button in filtered Sanity desk structure list

9 replies
Last updated: Oct 22, 2021
Hey all, I have a desk structure where documents are filtered and grouped by date into three distinct lists: upcoming, past and unfiltered.
Is there a way to get rid of the
Create new … button in the top-right corner of a filtered list (see screenshot)? In my case, filtered events, it doesn’t make much sense to create a past event.
I know about document actions, initial value templates and menu items. They all seem to either extend or bypass this specific create action. Happy for any pointers on how to hide this button!

Here’s the relevant part of the desk structure:

      S.listItem()
        .title("Events")
        .schemaType("event")
        .child(
          S.list()
            .title("Event Lists")
            .items([
              S.listItem()
                .title("Upcoming events")
                .child(
                  S.documentTypeList("event")
                    .title("Upcoming events")
                    .filter("date >= now()")
                ),
              S.listItem()
                .title("Past events")
                .child(
                  S.documentList()
                    .title("Past events")
                    .filter("_type == 'event' && date < now()")
                ),
              S.listItem()
                .title("All events")
                .child(S.documentTypeList("event").title("Events")),
            ])
        ),
      S.divider(),
AI Update

Good news! While there's no built-in method to hide the "Create new" button in filtered document lists, there's a working solution that other community members have used successfully.

The issue you're facing is common – when you create filtered lists (like "Past events"), the create button still appears even though creating a document in that context doesn't make sense. Unfortunately, the Structure Builder API doesn't expose a direct way to hide this button through configuration.

However, there's a clever workaround that involves overriding the serializer of the document list. A community member shared this solution that works by filtering out the create menu item from the serialized configuration:

// customDocumentList.js
export const customDocumentList = (spec, context) => {
  const _documentList = spec || context.documentList()
  
  return {
    ..._documentList,
    __serialize: (...args) => {
      const { menuItems, ...rest } = _documentList.__serialize(...args)
      // Filter out the create menu item
      const filteredMenuItems = menuItems.filter(
        ({ intent }) => intent?.type !== 'create'
      )
      return { menuItems: filteredMenuItems, ...rest }
    }
  }
}

Then use it in your structure:

import { customDocumentList } from './customDocumentList'

S.listItem()
  .title("Past events")
  .child(
    customDocumentList(
      S.documentList()
        .title("Past events")
        .filter("_type == 'event' && date < now()")
    )
  )

Important notes:

  1. This is technically a "hack" since it relies on internal implementation details (__serialize)
  2. Add the optional chaining operator (intent?.type) to handle menu items that don't have an intent property
  3. Be aware this could break if Sanity changes the internal structure in future updates
  4. Consider adding a check for createMenuIdx === -1 if you want to handle cases where there's no create menu

Alternative approaches you might consider:

  • Use document actions to hide/disable the create action based on context
  • Configure initial value templates with initialValueTemplates([]) to remove all template options (though this affects the entire list, not just the button)
  • Use new document options to control which document types appear in global create menus

The serializer override is currently the most targeted solution for hiding the create button on specific filtered lists while keeping it available elsewhere in your structure. Just keep in mind it's working with internal APIs that aren't officially documented, so treat it as a temporary workaround until Sanity potentially adds official support for this use case.

Show original thread
9 replies
following
That create button as far as I know is not customizable within the list configuration, but a while back I wrote this ugly hack:

https://gist.github.com/d4rekanguok/477548814fc6c59104c158840a3ab966
Basically these lists are serialized into config objects, so this custom documentList has the serializer overloaded so that the create button is removed from its config
Hey Derek, thanks for the hint! Wow, that is the best kind of ugly 😄 How did you figure that out?! Looks like that’s exactly what I need. Will try it out asap.
Now I am curious what else is possible with customized serializers …
It works! Thanks again,
user G
!
Thanks Daniel, glad it works! The studio is opensourced so it’s easy to take a peek when writing forbidden code 🤫
Looking at this now, we should add a check for
createMenuIdx === -1
in case there’s no create menu, and maybe some other checks in case Sanity change the config structure in the future
Haha, yeh, I find myself overwhelmed with Sanity’s source code.
And yes, one could handle some edge cases. I modified your code snippet to use
documentTypeList
and some menu items do not have
intent
specified:
[
  {
    "title": "Create new Event",
    "icon": {},
    "showAsAction": {
      "whenCollapsed": true
    },
    "intent": {
      "type": "create",
      "params": {
        "type": "event",
        "template": "event"
      }
    }
  },
  {
    "group": "sorting",
    "title": "Sort by Title",
    "icon": {},
    "action": "setSortOrder",
    "params": {
      "by": [
        {
          "field": "title",
          "direction": "asc"
        }
      ],
      "extendedProjection": "title"
    }
  },
  {
    "group": "sorting",
    "title": "Sort by Last edited",
    "icon": {},
    "action": "setSortOrder",
    "params": {
      "by": [
        {
          "field": "_updatedAt",
          "direction": "desc"
        }
      ],
      "extendedProjection": ""
    }
  },
  {
    "group": "sorting",
    "title": "Sort by Created",
    "icon": {},
    "action": "setSortOrder",
    "params": {
      "by": [
        {
          "field": "_createdAt",
          "direction": "desc"
        }
      ],
      "extendedProjection": ""
    }
  },
  {
    "group": "layout",
    "title": "Compact view",
    "icon": {},
    "action": "setLayout",
    "params": {
      "layout": "default"
    }
  },
  {
    "group": "layout",
    "title": "Detailed view",
    "icon": {},
    "action": "setLayout",
    "params": {
      "layout": "detail"
    }
  }
]
So maybe add a
?
in
intent?.type === "create"
.
Ah, also, I could save some lines of code by using filter (don’t know if that has any drawbacks because the array is not modified in place):
// …
const { menuItems: items, ...rest } = _documentList.__serialize(...args)
const menuItems = items.filter(({ intent }) => intent.type === 'create')
return { menuItems, ...rest }
// …
ah, that’s better! Now it’s a good looking hack 😛

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?