Skip to content

Create Custom Content Types

Let’s create a complete example of a Product content type for an e-commerce feature:

1. Define the Product Content Type

This represents the object version of a schema that you will be defining on the CMS. When you run the app, the JSON that is downloaded from the headless CMS will be cast to this particular type for easier usage.

features/shop/content/product.ts
import { ContentItem } from '@vyuh/react-extension-content'
export const PRODUCT_SCHEMA_TYPE = 'shop.product'
export interface Product extends ContentItem {
title: string
price: number
description?: string
image?: string
skuId: string
category: string
inStock: boolean
}

2. Create a Layout for the Product

The layout represents the appearance of this content type. You can choose to have multiple layouts for the same content type. For example, in case of product, the way it looks on a search listing page will be very different from the way it looks on a detail page or the payments page. You could create different layouts for this. For now, let’s create a default layout for the product.

features/shop/layouts/product-layout.tsx
import React from 'react'
import { LayoutConfiguration, TypeDescriptor } from '@vyuh/react-core'
import { Product, PRODUCT_SCHEMA_TYPE } from '../content/product'
export class DefaultProductLayout extends LayoutConfiguration<Product> {
static readonly schemaName = `${PRODUCT_SCHEMA_TYPE}.layout.default`
static readonly typeDescriptor = new TypeDescriptor(this.schemaName, this)
constructor() {
super({
schemaType: DefaultProductLayout.schemaName,
title: 'Default Product Layout',
})
}
render(content: Product): React.ReactNode {
return (
<div className="rounded-lg shadow-md overflow-hidden bg-white">
{content.image && (
<img
src={content.image}
alt={content.title}
className="w-full h-48 object-cover"
/>
)}
<div className="p-4">
<div className="text-xs text-gray-500 uppercase tracking-wider mb-1">
{content.category}
</div>
<h2 className="text-lg font-semibold text-gray-800 mb-2">
{content.title}
</h2>
{content.description && (
<p className="text-sm text-gray-600 mb-3">{content.description}</p>
)}
<div className="flex justify-between items-center">
<span className="text-lg font-bold text-accent-600">
${content.price.toFixed(2)}
</span>
<span className="text-xs px-2 py-1 rounded-full bg-gray-100 text-gray-800">
SKU: {content.skuId}
</span>
</div>
<div className="mt-3 pt-3 border-t border-gray-100 flex justify-between items-center">
<span
className={`text-sm px-2 py-1 rounded-full ${
content.inStock
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}
>
{content.inStock ? 'In Stock' : 'Out of Stock'}
</span>
<button className="px-4 py-2 bg-accent-600 text-white rounded-md hover:bg-accent-900 transition-colors">
View Details
</button>
</div>
</div>
</div>
)
}
}

3. Create a Content Builder for the Product

Finally, the Content Builder is the one that connects the content type to a specific layout. It is also the core actor in the whole process of rendering content from the CMS.

A Content Builder can also do more interesting things like custom configurations for a content type. We’ll talk more about this in future sessions.

features/shop/content/product-builder.ts
import { ContentBuilder } from '@vyuh/react-extension-content'
import { Product, PRODUCT_SCHEMA_TYPE } from './product'
import { DefaultProductLayout } from '../layouts/product-layout'
export class ProductContentBuilder extends ContentBuilder<Product> {
constructor() {
super({
schemaType: PRODUCT_SCHEMA_TYPE,
defaultLayout: new DefaultProductLayout(),
defaultLayoutDescriptor: DefaultProductLayout.typeDescriptor,
})
}
}

4. Register Everything in the Feature Descriptor

Finally, this is the step where we inform the framework about our content item - how it’s to be constructed and how it should be rendered on the screen. It does that by federating the features across your entire app and composing them into a singular type registry that can be used to look up types on the fly and render them according to their layouts.

The FeatureDescriptor is the one that gives modularity to this framework.

features/shop/index.ts
import { FeatureDescriptor } from '@vyuh/react-core'
import { ContentExtensionDescriptor } from '@vyuh/react-extension-content'
import { ProductContentBuilder } from './content/product-builder'
export const shopFeature = new FeatureDescriptor({
name: 'shop',
title: 'Shop Feature',
description: 'E-commerce functionality for your app',
extensions: [
new ContentExtensionDescriptor({
contentBuilders: [new ProductContentBuilder()],
}),
],
})

What’s Next?

This is just the beginning for Vyuh React. We’re working on bringing more features from the Flutter version to React, including:

  • Additional CMS integrations beyond Sanity.io
  • Enhanced developer tools for React
  • More pre-built components and templates

We’re excited to see what you build with Vyuh for React! Share your projects with us on GitHub 🔗 or join our Discord community 🔗 to connect with other Vyuh developers.