# Workflow: Content Types

Define structured content like blog posts, products, testimonials, or services. Content types can generate pages or be embedded in existing pages.

## When to Use Content Types

**Use content types for:**
- Reusable content entries (testimonials, team members, products)
- Collection-based content (blog posts, portfolio items, case studies)
- Structured data that needs consistent fields across entries

**Content types vs blocks:**
- Content types define *data* (entries)
- Blocks define *presentation* (how data appears on pages)
- Use `block.embed` to display content type entries on pages

## Prerequisites

- `riverbank.config.ts` exists
- Understanding of your data structure needs

## Steps

### Step 1: Define Content Type in Config

Add your content type to the `contentTypes` array in `riverbank.config.ts`:

```typescript
import { defineConfig } from '@riverbankcms/sdk/config';

export default defineConfig({
  siteId: '...',

  contentTypes: [
    {
      key: 'testimonial',
      name: 'Testimonial',
      description: 'Customer testimonials and reviews',
      hasPages: false,
      titleField: 'name',
      fields: [
        { id: 'name', type: 'text', label: 'Name', required: true },
        { id: 'jobTitle', type: 'text', label: 'Job Title' },
        { id: 'company', type: 'text', label: 'Company' },
        { id: 'body', type: 'richText', label: 'Quote', required: true, format: 'html' },
      ],
    },
  ],
});
```

### Step 2: Choose hasPages Setting

| Setting | Behavior |
|---------|----------|
| `hasPages: false` | Entries are only displayed via `block.embed` on other pages |
| `hasPages: true` | Each entry gets its own page at the route pattern URL |

For `hasPages: true`, also set `routePattern`:

```typescript
{
  key: 'post',
  name: 'Blog Post',
  hasPages: true,
  routePattern: '/blog/{slug}',  // Entry at /blog/my-post-title
  fields: [...],
}
```

### Step 3: Push Configuration

Push the content type to make it available in the CMS:

```bash
riverbankcms push-config
```

### Step 4: Create Entries

Create entries via CLI:

```bash
# Testimonial (no pages)
riverbankcms entry upsert testimonial john-smith \
  --data '{"name": "John Smith", "company": "Acme Corp", "body": "<p>Great service!</p>"}'

# Blog post (with pages)
riverbankcms entry upsert post first-post \
  --title "My First Post" \
  --data '{"excerpt": "A quick intro", "content": "<p>Hello world...</p>"}'
```

Or create via the CMS dashboard.

### Step 5: Display with block.embed

Add entries to pages using the embed block:

```bash
# Embed all testimonials
riverbankcms block upsert home testimonials-section \
  --kind block.embed \
  --data '{"contentType": "testimonial", "layout": "grid"}'

# Embed specific entries
riverbankcms block upsert home featured-reviews \
  --kind block.embed \
  --data '{"contentType": "testimonial", "mode": "manual", "entries": ["john-smith", "jane-doe"]}'
```

## Content Type Options

### Basic Options

| Property | Description |
|----------|-------------|
| `key` | Unique identifier (e.g., `'testimonial'`, `'blog-post'`) |
| `name` | Display name in CMS |
| `description` | Helpful description for editors |
| `fields` | Array of field definitions |
| `titleField` | Which field to use as the entry title (defaults to `'title'`) |

### Page Options

| Property | Description |
|----------|-------------|
| `hasPages` | `true` if entries should have their own pages |
| `routePattern` | URL pattern with `{slug}` placeholder |

## Complete Examples

### Example 1: Testimonials (No Pages)

Testimonials displayed via embed blocks, no individual pages:

```typescript
{
  key: 'testimonial',
  name: 'Testimonial',
  description: 'Customer testimonials and reviews',
  hasPages: false,
  titleField: 'name',
  fields: [
    { id: 'name', type: 'text', label: 'Name', required: true },
    { id: 'jobTitle', type: 'text', label: 'Job Title' },
    { id: 'company', type: 'text', label: 'Company' },
    { id: 'body', type: 'richText', label: 'Quote', required: true, format: 'html' },
  ],
}
```

### Example 2: Blog Posts (With Pages)

Blog posts with individual pages at `/blog/{slug}`:

```typescript
{
  key: 'post',
  name: 'Blog Post',
  description: 'Blog articles and news',
  hasPages: true,
  routePattern: '/blog/{slug}',
  fields: [
    { id: 'excerpt', type: 'text', label: 'Excerpt', multiline: true },
    { id: 'image', type: 'media', label: 'Featured Image', mediaKinds: ['image'] },
    { id: 'content', type: 'richText', label: 'Content', required: true, format: 'html' },
    { id: 'author', type: 'text', label: 'Author' },
    { id: 'publishedAt', type: 'datetime', label: 'Publish Date' },
  ],
}
```

### Example 3: Portfolio Items (With Pages)

Portfolio entries with case study pages:

```typescript
{
  key: 'portfolio-item',
  name: 'Portfolio Item',
  description: 'Work portfolio entries with case study pages',
  hasPages: true,
  routePattern: '/work/{slug}',
  fields: [
    { id: 'short-description', type: 'text', label: 'Short Description', multiline: true },
    { id: 'full-description', type: 'richText', label: 'Full Description', format: 'html' },
    { id: 'image', type: 'media', label: 'Featured Image', mediaKinds: ['image'] },
    { id: 'content', type: 'richText', label: 'Case Study Content', format: 'html' },
    { id: 'link', type: 'link', label: 'External Link' },
  ],
}
```

### Example 4: Service Tiles (No Pages, With Button Group)

Services with grouped button fields:

```typescript
{
  key: 'featured-service',
  name: 'Featured Service',
  description: 'Detailed service cards with CTA buttons',
  hasPages: false,
  titleField: 'title',
  fields: [
    { id: 'title', type: 'text', label: 'Title', required: true },
    { id: 'description', type: 'richText', label: 'Description', format: 'html' },
    { id: 'icon', type: 'media', label: 'Icon', mediaKinds: ['image'] },
    {
      id: 'button',
      type: 'group',
      label: 'Button',
      schema: {
        fields: [
          { id: 'label', type: 'text', label: 'Label', required: true },
          {
            id: 'variant',
            type: 'select',
            label: 'Style',
            options: [
              { value: 'primary', label: 'Primary' },
              { value: 'secondary', label: 'Secondary' },
            ],
          },
          { id: 'link', type: 'link', label: 'Link', required: true },
        ],
      },
    },
  ],
}
```

## Configuring Embed Layout Options

Customize how entries display in embed blocks by overriding layout options:

```typescript
// In riverbank.config.ts
blockFieldOptions: {
  'block.embed': {
    layout: {
      options: [
        { value: 'showcase', label: 'Services Showcase' },
        { value: 'list', label: 'Simple List' },
        { value: 'grid', label: 'Grid Layout' },
        { value: 'carousel', label: 'Carousel' },
      ],
    },
  },
}
```

Then create an override component to render each layout:

```typescript
// src/components/EmbedRenderer.tsx
import type { BlockOverrideProps, EmbedContent } from '@riverbankcms/sdk/rendering';

export function EmbedRenderer({ content, data }: BlockOverrideProps<EmbedContent>) {
  const layout = content.layout || 'list';
  const entries = data?.entries || [];

  switch (layout) {
    case 'showcase':
      return <ShowcaseLayout entries={entries} />;
    case 'grid':
      return <GridLayout entries={entries} />;
    case 'carousel':
      return <CarouselLayout entries={entries} />;
    default:
      return <ListLayout entries={entries} />;
  }
}
```

## Entry CLI Commands

```bash
# Create or update entry
riverbankcms entry upsert <content-type> <identifier> --data '...'

# With title (for entries with pages)
riverbankcms entry upsert post my-post --title "My Post" --data '...'

# List entries
riverbankcms entry list <content-type>

# Get single entry
riverbankcms entry get <content-type> <identifier>

# Publish entry
riverbankcms entry publish <content-type> <identifier>

# Delete entry
riverbankcms delete entry <content-type> <identifier> --yes
```

## Verification

1. Check content type appears in CMS dashboard
2. Create an entry via CMS or CLI
3. For `hasPages: true`, verify the entry page loads at the route pattern URL
4. Add an embed block to a page and verify entries display

## Troubleshooting

### Content type not appearing in CMS

- Run `riverbankcms push-config` to sync
- Verify `key` is unique across all content types
- Check for syntax errors in config

### Entry page not loading (hasPages: true)

- Verify `routePattern` is correct (e.g., `/blog/{slug}`)
- Check entry is published: `riverbankcms entry get <type> <id>`
- Ensure your catch-all route handles content entries

### Embed block not showing entries

- Verify `contentType` matches the content type key exactly
- Check entries exist: `riverbankcms entry list <type>`
- Ensure entries are published

### titleField not working

- Verify `titleField` matches an existing field `id`
- The field must be a text field
- If not set, defaults to `'title'`
