ワンクリックで
new-module
Create a new Sanity module named $ARGUMENTS following the established pattern for this project. Work through each step in order.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Create a new Sanity module named $ARGUMENTS following the established pattern for this project. Work through each step in order.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | new-module |
| description | Create a new Sanity module named $ARGUMENTS following the established pattern for this project. Work through each step in order. |
If the user has not already specified the fields for this module, ask before proceeding:
What fields should this module have?
When the user provides field names, infer the type from the name before asking. Only ask for clarification when the type is genuinely ambiguous.
Common inferences:
| Field name pattern | Inferred type |
|---|---|
intro, content, body, description | array of block (PortableText) |
pretitle, title, subtitle, label, name, value, suffix | string |
ctas | array of type cta |
icon | image |
image, photo, thumbnail, cover | image |
theme, layout, variant, size, color | string with options.list (ask for allowed values) |
items, cards, steps, features, accordions, stats | array of objects (ask for sub-fields) |
logos, people, quotes | array of references (ask for reference type) |
Once fields are confirmed (and any ambiguous types resolved), proceed with the steps below.
File: src/sanity/schemaTypes/modules/<module-name>.ts
Use defineModule instead of defineType. It prepends the attributes (module-attributes) field, appends an Options group, wires preview.select.hidden for the Studio list badge, and sets the shared modulePreview component—do not add those by hand.
import { defineArrayMember, defineField } from 'sanity'
import { FooIcon } from '@sanity/icons/Foo'
import { count, getBlockText } from '@/lib/utils'
import defineModule from '@/sanity/schemaTypes/fragments/define-module'
export default defineModule({
name: 'my-module',
title: 'My module',
type: 'object',
icon: FooIcon,
groups: [
{ name: 'content', default: true },
// { name: 'asset' }, // add if module has a dedicated image tab
// Do NOT add { name: 'options' } — defineModule appends it
],
fields: [
// Add your fields here — examples:
defineField({
name: 'intro',
type: 'array',
of: [{ type: 'block' }],
group: 'content',
}),
defineField({
name: 'ctas',
type: 'array',
of: [{ type: 'cta' }],
group: 'content',
}),
// Nested array of objects:
defineField({
name: 'items',
type: 'array',
of: [
defineArrayMember({
name: 'item',
type: 'object',
fields: [
defineField({ name: 'title', type: 'string' }),
defineField({
name: 'body',
type: 'array',
of: [{ type: 'block' }],
}),
defineField({ name: 'ctas', type: 'array', of: [{ type: 'cta' }] }),
],
preview: {
select: { title: 'title', body: 'body' },
prepare: ({ title, body }) => ({
title: title || getBlockText(body),
subtitle: 'Item',
}),
},
}),
],
group: 'content',
}),
],
preview: {
select: { intro: 'intro', items: 'items' },
prepare: ({ intro, items }) => ({
title: getBlockText(intro) || count(items, 'item'),
subtitle: 'My module',
}),
},
})
Notes:
content, optional asset, layout, html/css as needed). defineModule always appends { name: 'options' } and places attributes there. Omit groups entirely if everything is ungrouped (still get Options + attributes).attributes field or components.preview—defineModule injects them.preview.prepare: Return only title / subtitle / media as usual; hidden for the list badge is merged in automatically.getBlockText for PortableText preview, count for array lengths — both from @/lib/utilsfieldsets with options: { columns: 2 } to visually group related inline fields in StudioFile: src/sanity/schemaTypes/index.ts
Import at the top under // modules (alphabetical order):
import myModule from './modules/my-module'
Add to schema.types array under // modules (alphabetical order):
myModule,
File: src/sanity/schemaTypes/fragments/modules.ts
Add to the of array (alphabetical order):
{ type: 'my-module' },
Optionally add to an insertMenu.groups entry if it belongs to a logical group (e.g. hero, blog).
File: src/sanity/lib/queries.ts → MODULES_QUERY
Only add an entry here if the module has nested CTAs with links, reference fields, or other joins. Skip this step if the module has only simple scalar/block fields.
_type == 'my-module' => {
items[]{
...,
ctas[]{
...,
link{ ${LINK_QUERY} }
}
}
},
Reference field pattern (e.g. logo, person, quote):
_type == 'my-module' => {
items[]{
...,
_type == 'reference' => @->
}
},
Top-level CTAs on the module itself are already covered by the top-level ctas[] clause in MODULES_QUERY — no need to repeat them inside the conditional block.
bun run typegen
This regenerates src/sanity/types.ts. The new module type will be exported as a named type in PascalCase matching the schema name (e.g. my-module → MyModule). Fix any errors before proceeding.
First, ask:
Should the JSX be left empty (placeholder only) or scaffolded with basic Tailwind layout?
{/* content area */} comment inside the section; just render confirmed fields with minimal wrappers<dl> for stats, <details> for accordions, prose header for intro, CTA row for ctas). Base the structure on similar existing modules in src/ui/modules/ where applicable.Create the file at src/ui/modules/<module-name>.tsx:
import { PortableText, stegaClean } from 'next-sanity'
import type { MyModule } from '@/sanity/types'
import { Module } from '.'
export default function ({ intro, items, ctas, ...props }: MyModule) {
return (
<Module className="section space-y-8" {...props}>
{intro && (
<header className="prose mx-auto max-w-4xl text-center">
<PortableText value={intro} />
</header>
)}
{/* content area */}
</Module>
)
}
After creating the file, ask:
Does this module have any client-side interactivity? (e.g. state, effects, event handlers, browser APIs)
If yes: convert to the subdirectory layout so a sibling client.tsx with 'use client' can live alongside it:
src/ui/modules/<module-name>.tsx → src/ui/modules/<module-name>/index.tsxModule import from '.' to '..'Notes:
@/sanity/types using the PascalCase name from typegen...props — it carries _key, _type, and attributes for <Module><Module> sets id, data-module, hidden, and injects scoped CSS on the root element (default <section>; use as="nav" etc. when needed)stegaClean() around any string field used in conditional logic (e.g. theme, layout)PortableText from next-sanity for array of block fieldsFile: src/ui/modules/index.tsx
import MyModule from './my-module' // single-file layout
// import MyModule from './my-module/index' // subdirectory layout (equivalent, explicit)
MODULES_MAP (alphabetical order):'my-module': MyModule,
bun run typegen exits with no errorsbun run build passes)Generate the verbatim Markdown for a `page` or `blog.post` document's `markdown` field (served at `<slug>.md` and listed in `/llms.txt`), converting Portable Text / modules into Markdown and resolving image references to Sanity CDN URLs. Use when the user asks to generate, refresh, or fill in the markdown field for a page or blog post.
Build out a complete SanityPress website — all pages, content, navigation, and CSS — entirely through the Sanity MCP connector, using only existing modules. No code files are edited. Use when the user wants a full site created for a business/brand from a brief.