# Workflow: Content Type Templates

Define default template blocks for content types that generate pages. Templates allow you to specify which blocks appear on entry pages and how entry data maps to block fields.

## When to Use Templates

**Use templates for:**
- Content types with `hasPages: true` that need consistent page layouts
- Automating block setup for new entries
- Binding entry data to block fields (e.g., headline from title)

**Templates vs manual blocks:**
- Templates define the *default* blocks for new entries
- Editors can still modify blocks after entry creation
- SDK always wins on sync—templates overwrite manual changes

## Prerequisites

- `riverbank.config.ts` with content types defined
- Understanding of which blocks to use
- Knowledge of content type fields for bindings

## Steps

### Step 1: Add Template to Content Type

Add the `template` property to your content type:

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

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

  content: {
    contentTypes: [
      {
        key: 'post',
        name: 'Blog Post',
        hasPages: true,
        routePattern: '/blog/{slug}',
        fields: [
          { id: 'excerpt', type: 'text', label: 'Excerpt', multiline: true },
          { id: 'image', type: 'media', label: 'Featured Image', mediaKinds: ['image'] },
          { id: 'body', type: 'richText', label: 'Content', required: true },
        ],
        template: {
          blocks: [
            {
              identifier: 'hero',
              kind: 'block.hero',
              scope: 'entry',
              bindings: {
                headline: { source: 'meta', path: 'title' },
                backgroundImage: { source: 'field', path: 'image' },
              },
            },
            {
              identifier: 'body',
              kind: 'block.body-text',
              scope: 'entry',
              bindings: {
                body: { source: 'field', path: 'body' },
              },
            },
          ],
        },
      },
    ],
  },
});
```

### Step 2: Choose Block Scope

Each template block has a scope that determines how its content is sourced:

| Scope | Description |
|-------|-------------|
| `entry` | Block content is bound to entry data via bindings |
| `template` | Block content is static, defined in the template |

**Entry-scoped blocks** pull data from the content entry:
```typescript
{
  identifier: 'hero',
  kind: 'block.hero',
  scope: 'entry',
  bindings: {
    headline: { source: 'meta', path: 'title' },
  },
}
```

**Template-scoped blocks** have fixed content:
```typescript
{
  identifier: 'related-posts',
  kind: 'block.blog-listing',
  scope: 'template',
  content: {
    heading: 'Related Posts',
    layout: 'grid',
  },
}
```

### Step 3: Configure Bindings

Bindings map block fields to data sources. Available binding sources:

| Source | Description | Example |
|--------|-------------|---------|
| `field` | Entry field value | `{ source: 'field', path: 'image' }` |
| `meta` | Entry metadata | `{ source: 'meta', path: 'title' }` |
| `literal` | Static value | `{ source: 'literal', value: 'Welcome!' }` |
| `transform` | Transformed field value | `{ source: 'transform', path: 'body', transform: 'richtext.firstParagraph' }` |
| `fallback` | Try sources in order | `{ source: 'fallback', sources: [...] }` |

**Valid meta paths:** `title`, `slug`, `publishedAt`, `createdAt`, `updatedAt`

### Step 4: Push Configuration

Push the template to sync it with the CMS:

```bash
riverbankcms push-config
```

### Step 5: Verify Template

1. Create a new entry for the content type
2. The entry page should have the template blocks
3. Entry data should appear in bound block fields

## Complete Example

Blog with hero, body, and related posts:

```typescript
{
  key: 'post',
  name: 'Blog Post',
  hasPages: true,
  routePattern: '/blog/{slug}',
  fields: [
    { id: 'excerpt', type: 'text', label: 'Excerpt', multiline: true },
    { id: 'image', type: 'media', label: 'Featured Image', mediaKinds: ['image'] },
    { id: 'body', type: 'richText', label: 'Content', required: true },
    { id: 'category', type: 'text', label: 'Category' },
  ],
  template: {
    blocks: [
      // Hero bound to entry data
      {
        identifier: 'hero',
        kind: 'block.hero',
        scope: 'entry',
        bindings: {
          headline: { source: 'meta', path: 'title' },
          subheadline: { source: 'field', path: 'excerpt' },
          backgroundImage: { source: 'field', path: 'image' },
        },
      },
      // Body text bound to entry content
      {
        identifier: 'body',
        kind: 'block.body-text',
        scope: 'entry',
        bindings: {
          body: { source: 'field', path: 'body' },
        },
      },
      // Static related posts section
      {
        identifier: 'related',
        kind: 'block.blog-listing',
        scope: 'template',
        content: {
          heading: 'More Posts',
          layout: 'grid',
          limit: 3,
        },
      },
    ],
  },
}
```

## Binding Patterns

### Using Fallback Bindings

Try multiple sources in order:

```typescript
{
  identifier: 'hero',
  kind: 'block.hero',
  scope: 'entry',
  bindings: {
    backgroundImage: {
      source: 'fallback',
      sources: [
        { source: 'field', path: 'heroImage' },
        { source: 'field', path: 'image' },
        { source: 'literal', value: { src: '/default-hero.jpg' } },
      ],
    },
  },
}
```

### Mixing Scopes

Use entry bindings with literal defaults:

```typescript
{
  identifier: 'hero',
  kind: 'block.hero',
  scope: 'entry',
  bindings: {
    headline: { source: 'meta', path: 'title' },
    eyebrow: { source: 'literal', value: 'Blog Post' },
    ctas: {
      source: 'literal',
      value: [{ label: 'Back to Blog', link: { kind: 'internal', path: '/blog' } }],
    },
  },
}
```

## First Sync Behavior

When you first push a template to a content type that has existing entries:

1. **Existing CMS blocks are replaced** with SDK-defined blocks
2. This only happens on the *first* SDK sync for that template
3. Subsequent syncs update SDK blocks by identifier

## Sync Rules

| Scenario | Behavior |
|----------|----------|
| First SDK sync | Replaces all existing CMS blocks |
| New block in config | Created with `sdk_identifier` |
| Block identifier matches | Block is updated |
| Block removed from config | SDK block is soft-deleted |
| Non-SDK blocks | Preserved (not deleted) |

## Troubleshooting

### Blocks not appearing on entry pages

- Run `riverbankcms push-config` to sync
- Verify content type has `hasPages: true`
- Check that `identifier` is unique within the template

### Binding not working

- Verify `path` matches a field ID in your content type
- For meta bindings, use valid paths: `title`, `slug`, `publishedAt`, `createdAt`, `updatedAt`
- Check block manifest supports the binding key

### Template changes not reflecting

- SDK always wins—changes are applied on every push
- Run `riverbankcms push-config` after config changes
- Clear any browser/CDN caches

### Validation errors on push

- Check binding keys match block field IDs
- Verify field paths exist in content type fields
- Ensure block kinds are valid system or custom blocks
