Skip to content

Content Modeling

Source: omni-cms-composable-cms/schemaTypes/

📐 ADR: ADR-0002 — Using Sanity CMS for site configuration — Decision: Shell config (header, footer, navigation) lives in Sanity, not in static JSON files.

schemaTypes/
├── index.ts # All registered types
├── sanityConstants.ts # Enums, config, supported countries
├── documents/ # Page types & configuration documents
├── reusableDocuments/ # Reusable component documents
├── objects/ # Object types (embedded in documents)
├── fields/ # Shared field definitions
├── helpers/ # Schema helpers (localized fields, reference fields)
├── validations/ # Custom validation rules
├── components/ # Custom Studio UI components
└── hooks/ # Document action hooks

Top-level content documents that map to frontend URLs.

Type NameEnumDescription
homePagePageTypes.HOME_PAGEHome page (one per country)
promotionsPagePageTypes.PROMOTIONS_PAGEPromotions landing page
flexibleContentPagePageTypes.FLEXIBLE_CONTENT_PAGEGeneric content pages with flexible components
inspirationalLandingPageTypes.INSPIRATIONAL_LANDINGInspiration hub page
inspirationalCategoryPageTypes.INSPIRATIONAL_CATEGORYInspiration category (parent of articles)
inspirationalBlogPageTypes.INSPIRATIONAL_ARTICLEInspiration article (blog post)

Most pages use createFlexiblePage which provides:

  • country — which country this page belongs to
  • title — translated field (per language)
  • slug — translated field (per language)
  • selectComponent — the flexible component array (references to reusable documents)
  • seoMetaData — SEO metadata (title, description, canonical, robots)
  • openGraph — Open Graph metadata
flexibleContentPage.ts
export const flexibleContentPage = createFlexiblePage({
name: PageTypes.FLEXIBLE_CONTENT_PAGE,
title: "Flexible Content Page",
flexibleComponents: [
defineArrayMember(createReferenceField({ name: "carousel", types: [ReusableDocumentTypes.CAROUSEL] })),
defineArrayMember(createReferenceField({ name: "heroBlock", types: [ReusableDocumentTypes.HERO_BLOCK] })),
defineArrayMember(createReferenceField({ name: "richText", types: [ReusableDocumentTypes.RICH_TEXT] })),
// ... more component types
],
});

Standalone documents that editors create once and reference from multiple pages.

Type NameEnumDescription
carouselCAROUSELProduct/content carousel
categoryCarouselCATEGORY_CAROUSELCategory navigation carousel
heroBlockHERO_BLOCKFull-width hero banner with image/video
bannerArrayBANNER_ARRAYArray of banner cards
bannerCardBANNER_CARDSingle banner card
photoIMAGEImage with metadata
embeddedImageEMBEDDED_IMAGEEmbedded image block
embeddedVideoEMBEDDED_VIDEOEmbedded video block
videoVIDEOVideo component
richTextRICH_TEXTRich text block (Portable Text)
tableElementTABLETable component
imageContentBlockIMAGE_CONTENT_BLOCKImage + text layout
callToActionBlockCALL_TO_ACTION_BLOCKCTA block
buttonBUTTONButton component
infoInlineINFO_INLINEInline info message
featuredArticleFEATURED_INSPIRATIONAL_ARTICLEFeatured article highlight
productArrayPRODUCT_ARRAYProduct grid (connects to PODS)
voucherArrayVOUCHER_ARRAYVoucher display
structuredDataSTRUCTURED_DATAJSON-LD structured data
questionaryQUESTIONARYQuiz/questionnaire
seoLinksBlockSEO_LINKS_BLOCKSEO internal links
retailMediaBannerRETAIL_MEDIA_BANNERRetail media ad placement
globalMessageBannerGLOBAL_MESSAGE_BANNERSite-wide message banner

One document per country, managing MFE behavior.

Type NameDescription
shellConfigurationHeader, footer, navigation, social links, awards
searchSuggestionsConfigurationSearch autocomplete suggestions
headgroupConfigurationPDP headgroup display rules
productLevelConfigProduct-level display config (singleton)
pdpRecommendationCarouselsPDP recommendation carousel config
marketingNewsletterConfigurationNewsletter signup config
categoryTreeCategory navigation tree (one per country)

Contains everything for the Web Shell: header top navigation, main links, footer newsletter, customer service links, social links, payment methods, legal links, awards, contact details, and geo popup exclusions.

Fields needing multiple language versions use createTranslatedField:

// Creates: title.nl, title.fr, title.de
createTranslatedField({
name: "title",
title: "Title",
type: "string",
});

In GROQ queries, access as title[$lang] or slug[$lang].current.

TypePurpose
linkInternal/external link with label
imageWithMetadataImage reference + DAM metadata
seoMetaDataSEO fields (title, description, canonical, robots)
openGraphOG metadata for social sharing
microcopyEntryTranslation key-value pair
  1. Create the file in the appropriate directory (documents/, reusableDocuments/, or objects/)
  2. Register in schemaTypes/index.ts
  3. If it needs internationalization, add to SANITY_CONFIG.TRANSLATION_SCHEMA_TYPES in sanityConstants.ts
  4. If reusable component, add to Studio desk structure in sanity.config.ts
  5. Run npm run validate-schema
  6. If data migration needed, create one in migrations/