A page builder is one of the best things you can give a content editor, and the insert menu is where it quietly falls apart. You have built ten well-structured modules, each one a different kind of section, and when the editor clicks "Add item" they are shown a list of identical-looking entries with the same default icon next to each. The schema is good. The experience of choosing from it is not. The editor has to remember what "module-two" looks like, or add it and find out.
Sanity has a clean fix for this that I now add to every page builder I build: preview images in the insert menu, so editors see what each block looks like before they add it. Here is the setup.
The code
The whole thing lives in the insertMenu option on your modules array, using the grid view:
1export const builder = defineField({
2 name: "builder",
3 title: "Builder",
4 type: "object",
5 fields: [
6 defineField({
7 name: "modules",
8 title: "Modules",
9 type: "array",
10 of: [
11 defineArrayMember({ type: "module-one" }),
12 defineArrayMember({ type: "module-two" }),
13 ],
14 options: {
15 insertMenu: {
16 views: [
17 {
18 name: "grid",
19 previewImageUrl: (schemaName) =>
20 `/preview/modules/${schemaName}.png`,
21 },
22 ],
23 },
24 },
25 }),
26 ],
27});That is all it takes to switch the insert menu from a flat list of icons to a grid of real thumbnails.
How it actually works
The important piece is previewImageUrl. It is a callback, and Sanity hands it the schema type name of each block in the array. So when it runs for your first module it receives "module-one", and for your second it receives "module-two". You return a URL, and Sanity renders the image at that URL as the block's thumbnail in the grid.
Because the function builds the path from the schema name, you do not write any per-block configuration. The naming convention does the wiring for you. A block called module-one looks for module-one.png, a block called module-two looks for module-two.png, and any new module you add later automatically looks for a file named after it. You add the block type and drop in a matching image, and it appears in the grid with no further changes to this code.
If a file is missing for a given block, nothing breaks. Sanity falls back to the default icon for that type, so a half-finished set of thumbnails degrades gracefully instead of erroring.
Where the images live
This is the part that trips people up, and the key is understanding how Sanity serves static files.
The images go in the Studio's static folder. Sanity bundles that folder with Vite and serves its contents at the root of the Studio, which is the detail that confuses people: the static segment is the name of the source folder, not part of the URL. So a file you place at static/preview/modules/module-one.png is served at /preview/modules/module-one.png, which is exactly the path the previewImageUrl callback returns.
That is why the URL in the code has no static prefix. You are not linking to /static/...; you are linking to the root, and the static folder is simply the place on disk that Sanity exposes there. Create static/preview/modules/ in your Studio project, drop in your PNGs named after each schema type, and they resolve.
If you ever see the thumbnails silently fail to appear, this mapping is almost always the cause. Either the file is sitting somewhere outside the static folder, or the URL was written with a /static prefix that does not match how the folder is actually served. Get the folder and the path lined up and the grid fills in.
A few things worth getting right
Keep the images a consistent size and aspect ratio. The grid lines them up next to each other, and a set of mismatched canvases turns a tidy picker into a mess. A consistent landscape ratio across every thumbnail is what makes the grid actually look designed rather than thrown together.
Name the files exactly after the schema types. The whole mechanism rests on that match, so a thumbnail named moduleOne.png for a block typed module-one will simply never appear. The callback is case- and character-sensitive because it is just string interpolation.
And treat the thumbnails as part of shipping a block, not a thing you do later. The moment you add a new module type, add its preview image in the same change. Otherwise you end up with a picker that is half thumbnails and half default icons, which looks worse than having none at all.
Why this is worth the effort
This is a small schema change for a disproportionate payoff. It turns the insert menu from a developer's list of type names into something an editor can actually read at a glance. For a client logging into their own site, that difference is the gap between a CMS that feels built for them and one that feels like it was handed over half-configured. Most of what makes editors trust a page builder is exactly these small moments of polish, and this is one of the cheapest ones to add.


