| name | cms-component-builder |
| description | Build Optimizely CMS components for Astro v5. Use when creating/modifying components, pages, experiences, or working with GraphQL fragments, opti-type.json, or opti-style.json files (project) |
CMS Component Builder
Build production-ready Optimizely CMS components with proper GraphQL integration, daisyUI styling, and TypeScript type safety.
When to Use This Skill
ALWAYS use this skill when the user asks to:
- Create a new component, page, or experience (e.g., "create a testimonial component", "add a new page type")
- Modify or update
.opti-type.json files (content type definitions)
- Modify or update
.opti-style.json files (style/display template definitions)
- Create or modify GraphQL fragments (
.graphql or .dam.graphql files)
- Add components to
allComponents.graphql or similar aggregation files
- Debug component rendering issues
- Fix GraphQL type errors related to components
- Push/sync content types or styles to the CMS
- Understand how the component system works
Critical Instructions
YOU MUST FOLLOW THESE STEPS IN ORDER:
-
READ THE GUIDES FIRST - This skill includes comprehensive guides that YOU MUST reference:
CONTENTTYPE-GUIDE.md - For creating .opti-type.json files
STYLE-GUIDE.md - For creating .opti-style.json files
GRAPHQL-PATTERNS.md - For creating .graphql and .dam.graphql files
-
EXAMINE EXISTING COMPONENTS - Always look at similar existing components as examples before creating new ones. Check src/cms/components/ for patterns.
-
CREATE ALL REQUIRED FILES - A complete component needs 5 files minimum:
.astro - Component template with TypeScript
.opti-type.json - Content type definition
.opti-style.json - Style definition(s) (can have multiple)
.graphql - Base GraphQL fragment
.dam.graphql - DAM-enabled GraphQL fragment
-
INTEGRATE PROPERLY - After creating files:
- Add fragment to
src/cms/components/allComponents.graphql
- Push to CMS:
yarn type:push ComponentName and yarn style:push StyleName
- Wait 10 seconds for Optimizely Graph sync
- Generate types:
yarn codegen
Quick Start Example
Creating a new "Testimonial" component:
mkdir -p src/cms/components/TestimonialComponent
src/cms/components/TestimonialComponent/
├── Testimonial.astro
├── Testimonial.opti-type.json
├── DefaultTestimonial.opti-style.json
├── testimonial.graphql
└── testimonial.dam.graphql
Required Files Explained
Every component needs at least 5 files:
1. .astro - Component Template
IMPORTANT: Always follow the pattern from existing components like Button.astro
---
import type {
TestimonialFragment,
DisplaySettingsFragment,
} from '../../../../__generated/sdk';
import type { ContentPayload } from '../../../graphql/shared/ContentPayload';
import { isEditContext } from '../../shared/utils.ts';
const isCmsEdit = isEditContext(Astro.url);
export interface Props {
key: string;
data: TestimonialFragment;
displaySettings: DisplaySettingsFragment[];
displayTemplateKey: string;
contentPayload: ContentPayload;
}
const { key, data, displaySettings, displayTemplateKey, contentPayload } = Astro.props as Props;
const componentClass = 'component-testimonial';
---
<div data-epi-block-id={isCmsEdit && key || undefined} class={componentClass}>
<div =>
{data.Author}
Key Points:
- Import types from
__generated/sdk (NOT from @/graphql/__generated/graphql)
- Include ALL required props:
key, data, displaySettings, displayTemplateKey, contentPayload
- Use
data-epi-block-id for CMS editing context
- Use
set:html for rich text fields
- Add a component class for CSS targeting
2. .opti-type.json - Content Type
See CONTENTTYPE-GUIDE.md for complete guide on creating content types.
3. .opti-style.json - Style Definitions
See STYLE-GUIDE.md for complete guide on creating style definitions.
4. .graphql + .dam.graphql - GraphQL Fragments
ALWAYS create both versions. See GRAPHQL-PATTERNS.md for complete details.
Base (testimonial.graphql):
fragment Testimonial on Testimonial {
Quote { html }
Author
AuthorTitle
AuthorImage {
...ContentUrl
}
}
DAM (testimonial.dam.graphql):
fragment Testimonial on Testimonial {
Quote { html }
Author
AuthorTitle
AuthorImage {
...ContentUrl
...ContentReferenceItem
}
}
Component Integration
1. Add to allComponents.graphql
fragment AllComponentsExceptGrid on _IComponent {
...Text
...Button
...Testimonial
}
Important: Add to AllComponentsExceptGrid, NOT AllComponents
2. Sync to CMS & Generate Types
yarn type:push ComponentName
yarn style:push StyleName
yarn codegen
3. Verify Integration
Check __generated/graphql.ts for your component type.
Component Locations
- Components:
src/cms/components/ - Reusable UI (Button, Card, Hero)
- Pages:
src/cms/pages/ - Page types (ArticlePage, LandingPage)
- Experiences:
src/cms/experiences/ - Experience templates
- Compositions:
src/cms/compositions/ - Layout elements (Row, Column)
GraphQL Shared Fragments
Quick reference (full details in GRAPHQL-PATTERNS.md):
LinkUrl - Links with url, title, target, text
ContentUrl - Content reference URLs
LinkCollection - Simple link arrays
ContentReferenceItem - DAM metadata (.dam.graphql only)
DisplaySettings - Display/styling settings
PageUrl - Page metadata
Rich text fields: Always use { html }:
Body { html }
Description { html }
Styling with daisyUI + TailwindCSS
Prefer daisyUI components:
<button class="btn btn-primary btn-lg">
<div class="card card-compact">
<div class="hero min-h-screen">
Use theme variables:
<div class="bg-base-100 text-base-content">
Mobile-first responsive:
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
Complete Workflow (FOLLOW IN ORDER)
Step 1: Research & Planning
- Ask the user for requirements: What should this component do? What properties does it need?
- Find similar components: Use
Glob to find similar components in src/cms/components/
- Read example files: Study at least one similar component to understand the patterns
Step 2: Create Component Files
Create the component directory and all required files:
mkdir -p src/cms/components/ComponentNameComponent
cd src/cms/components/ComponentNameComponent
Create these files in order:
-
ComponentName.opti-type.json - Content type definition
- Reference
CONTENTTYPE-GUIDE.md for structure
- Look at similar component's
.opti-type.json as example
- Set
baseType: "component" for components
- Set
compositionBehaviors: ["elementEnabled"] for components usable in visual editor
-
DefaultComponentName.opti-style.json - Default style template
- Reference
STYLE-GUIDE.md for structure
- Set
contentType to match your component's key
- Set
isDefault: true for the default style
- Create meaningful display settings with choices
-
componentName.graphql - Base GraphQL fragment
- Reference
GRAPHQL-PATTERNS.md
- Fragment name must match component name (PascalCase)
- Include all properties from
.opti-type.json
- Use shared fragments:
...LinkUrl, ...ContentUrl, etc.
-
componentName.dam.graphql - DAM-enabled GraphQL fragment
- Copy from
componentName.graphql
- Add
...ContentReferenceItem to content references
- This enables DAM metadata for images/videos
-
ComponentName.astro - Component template
- Follow the pattern from existing components (see example above)
- Import types from
__generated/sdk
- Include all required props
- Handle display settings for styling
- Use daisyUI classes for styling
Step 3: Integrate with System
-
Add to allComponents.graphql
fragment AllComponentsExceptGrid on _IComponent {
...ExistingComponent
...YourNewComponent
}
-
Push to CMS
yarn type:push ComponentName
yarn style:push DefaultComponentName
-
Generate TypeScript Types
yarn codegen
Step 4: Verify & Test
-
Check generated types in __generated/sdk.ts:
- Look for
ComponentNameFragment type
- Verify all properties are present
-
Check for TypeScript errors:
yarn tsc --noEmit
-
Test in dev server:
yarn dev
-
Verify in CMS:
- Component appears in CMS UI
- All properties are editable
- Style options work correctly
Verification Checklist
YOU MUST CHECK ALL OF THESE before completing:
-
✓ All 5 required files created:
ComponentName.astro
ComponentName.opti-type.json
DefaultComponentName.opti-style.json
componentName.graphql
componentName.dam.graphql
-
✓ Content type (.opti-type.json) is valid:
- Has
key, displayName, baseType
- All properties defined correctly
- Uses correct property types
-
✓ Style template (.opti-style.json) is valid:
- Has
key, displayName, contentType
contentType matches component key
- Has
isDefault: true for default style
- All settings have choices
-
✓ Both GraphQL fragments created:
.graphql for base version
.dam.graphql with ...ContentReferenceItem for DAM
-
✓ Fragment added to allComponents.graphql:
- Added to
AllComponentsExceptGrid (NOT AllComponents)
-
✓ Pushed to CMS successfully:
yarn type:push ComponentName completed
yarn style:push StyleName completed
- Waited 10-15 seconds for Graph sync
-
✓ Types generated successfully:
yarn codegen completed without errors
ComponentNameFragment type exists in __generated/sdk.ts
-
✓ Component template (.astro) follows patterns:
- Imports from
__generated/sdk
- Has all required props
- Uses for CMS editing
Resources
All guides are bundled with this skill for quick reference:
-
GraphQL Patterns: See GRAPHQL-PATTERNS.md for:
- Complete fragment syntax and structure
- DAM vs non-DAM differences
- Real-world examples from existing components
- Shared fragment catalog
- Integration patterns
-
Content Type Creation: See CONTENTTYPE-GUIDE.md for:
- Content type structure and syntax
- Property definitions and types
- Display settings configuration
- Real-world examples
-
Style Creation: See STYLE-GUIDE.md for:
- Style definition structure
- Display settings mapping to CSS classes
- daisyUI and TailwindCSS patterns
- Responsive variants
Common Patterns
Component with links:
Links {
...LinkCollection
}
Component with images:
Image {
...ContentUrl
}
Image {
...ContentUrl
...ContentReferenceItem
}
Component with metadata:
_metadata {
types
displayName
}
Common Issues & Troubleshooting
Issue: GraphQL fragment not found after codegen
Solution:
- Check that fragment is added to
allComponents.graphql
- Ensure you pushed to CMS:
yarn type:push ComponentName
- Wait 10-15 seconds for Optimizely Graph to sync
- Run
yarn codegen again
Issue: TypeScript errors in .astro file
Solution:
- Verify imports are from
__generated/sdk not @/graphql/__generated/graphql
- Check that all Props interface fields are present
- Run
yarn codegen to regenerate types
- Check that fragment name matches component name exactly (case-sensitive)
Issue: Component doesn't appear in CMS
Solution:
- Verify
yarn type:push ComponentName completed successfully
- Check that
baseType is set correctly in .opti-type.json
- Check that
compositionBehaviors: ["elementEnabled"] is set
- Clear CMS cache and refresh
Issue: Display settings not working
Solution:
- Verify
yarn style:push StyleName completed successfully
- Check that
contentType in .opti-style.json matches component key
- Ensure
isDefault: true is set for default style
- Verify style mapping exists in component's styling helper file
Issue: Rich text fields not rendering HTML
Solution:
- Use
set:html={data.FieldName?.html} instead of {data.FieldName.html}
- Ensure GraphQL fragment requests
{ html } for rich text fields
Issue: Content references (images/videos) not loading
Solution:
- Check GraphQL fragment includes
...ContentUrl
- For DAM assets, verify
.dam.graphql includes ...ContentReferenceItem
- Check that
url?.default or url?.hierarchical is used in template
Pages and Experiences
This skill also handles Pages and Experiences, which follow the same patterns:
Pages (src/cms/pages/):
- Set
baseType: "Page" instead of "Component"
- Include SEO settings and page admin settings
- Add to appropriate page aggregation file
- May include
mayContainTypes for content areas
Experiences (src/cms/experiences/):
- Define experience templates
- Include composition structure
- Reference Row, Column, and Section compositions
Using Svelte 5 for Interactive Components
This project uses Svelte 5 (latest version with runes) for client-side interactivity. Svelte components are primarily used for admin/utility interfaces, NOT for CMS components.
When to Use Svelte
Use Svelte for:
- Admin interfaces (like
/opti-admin pages)
- Interactive forms and utilities
- Complex client-side state management
- Real-time features (SSE, WebSockets)
DO NOT use Svelte for:
- CMS components (use
.astro instead)
- Content rendering from Optimizely
- SEO-critical content
Svelte 5 Key Patterns
State Management with Runes:
<script lang="ts">
// Props using $props() rune
interface Props {
title: string;
count?: number;
}
let { title, count = 0 }: Props = $props();
// Reactive state using $state() rune
let currentCount = $state(0);
let isLoading = $state(false);
// Derived state using $derived rune
let doubleCount = $derived(currentCount * 2);
// Side effects using $effect rune
$effect(() => {
console.log('Count changed:', currentCount);
});
// Functions
function increment() {
currentCount++;
}
</script>
<div>
<h1>{title}</h1>
<p>Count: {currentCount}</p>
<p>Double: {doubleCount}</p>
<button onclick={increment}>Increment</button>
</div>
Important Svelte 5 Changes:
- Use
$props() instead of export let
- Use
$state() instead of let for reactive variables
- Use
$derived instead of $: for computed values
- Use
$effect() instead of $: for side effects
- Use
onclick={} instead of on:click={}
- Use
bind:value={} for two-way binding
Example: Admin Component with TypeScript
See src/pages/opti-admin/components/_CmsSync.svelte for a complete example showing:
- TypeScript interfaces with Props
- State management with
$state() and $derived
- Event handling with EventSource (SSE)
- Lifecycle with
onMount()
- Conditional rendering with
{#if}
- List rendering with
{#each}
Embedding Svelte in Astro
---
// MyPage.astro
import MyCoolComponent from './_components/MyCoolComponent.svelte';
const someData = { title: 'Hello', count: 5 };
---
<div>
<MyCoolComponent client:load title={someData.title} count={someData.count} />
</div>
Client Directives:
client:load - Load immediately on page load
client:idle - Load when browser is idle
client:visible - Load when component is visible
client:only="svelte" - Only run on client, no SSR
Remember: Every component must be production-ready with:
- Complete integration into the system
- Proper TypeScript types
- Consistent daisyUI styling
- Both GraphQL fragment versions (.graphql and .dam.graphql)
- Comprehensive verification before completion