Create Custom Skeletons

Build reusable site structure templates with skeleton YAML

5 min read 1065 words

Skeleton YAML files define reusable site structures. Create one skeleton, apply it to any project.

When to Use Custom Skeletons

  • Team standards: Enforce consistent site architecture across projects
  • Repeatable patterns: Scaffold the same structure without copy-paste
  • Onboarding: New team members get a working structure immediately
  • Experiments: Quickly test different information architectures

Skeleton Anatomy

A skeleton has two sections: metadata and structure.

# Metadata
name: My Custom Skeleton
description: What this skeleton creates
version: "1.0"

# Global cascade (optional)
cascade:
  type: doc

# Page structure
structure:
  - path: _index.md
    props:
      title: Home
    content: |
      # Welcome

Build a Skeleton Step by Step

Step 1: Define Metadata

name: API Documentation
description: OpenAPI-style docs with endpoints and schemas
version: "1.0"

Step 2: Add Global Cascade

Cascade settings apply to all pages unless overridden:

cascade:
  type: doc
  draft: false

Step 3: Define Root Pages

structure:
  - path: _index.md
    props:
      title: API Reference
      description: Complete API documentation
      weight: 100
    content: |
      # API Reference

      Welcome to the API documentation.

      ## Sections

      - [Authentication](/authentication)
      - [Endpoints](/endpoints)
      - [Schemas](/schemas)

Step 4: Add Sections with Nested Pages

Usepagesto nest content under a section:

  - path: endpoints/_index.md
    props:
      title: Endpoints
      weight: 20
    cascade:
      type: doc
    content: |
      # API Endpoints

      All available endpoints.

    pages:
      - path: users.md
        props:
          title: Users
          weight: 10
        content: |
          # Users Endpoint

          `GET /api/users`

          Returns a list of users.

      - path: posts.md
        props:
          title: Posts
          weight: 20
        content: |
          # Posts Endpoint

          `GET /api/posts`

          Returns a list of posts.

Step 5: Apply and Test

# Preview first
bengal project skeleton apply api-docs.yaml --dry-run

# Apply
bengal project skeleton apply api-docs.yaml

# Serve
bengal serve

Complete Example: API Documentation Skeleton

name: API Documentation
description: REST API docs with authentication, endpoints, and schemas
version: "1.0"

cascade:
  type: doc

structure:
  - path: _index.md
    props:
      title: API Reference
      description: Complete API documentation
      weight: 100
    content: |
      # API Reference

      This documentation covers all available API endpoints.

      ## Quick Links

      - [Authentication](/authentication) - API keys and OAuth
      - [Endpoints](/endpoints) - Available endpoints
      - [Schemas](/schemas) - Request/response formats
      - [Errors](/errors) - Error codes and handling

  - path: authentication.md
    props:
      title: Authentication
      weight: 10
    content: |
      # Authentication

      All API requests require authentication.

      ## API Keys

      Include your API key in the header:

      ```bash
      curl -H "Authorization: Bearer YOUR_API_KEY" \
        https://api.example.com/v1/users
      ```

      ## OAuth 2.0

      For user-authenticated requests, use OAuth 2.0.

  - path: endpoints/_index.md
    props:
      title: Endpoints
      weight: 20
    content: |
      # API Endpoints

      All available REST endpoints.

    pages:
      - path: users.md
        props:
          title: Users
          weight: 10
        content: |
          # Users

          ## List Users

          ```http
          GET /api/v1/users
          ```

          ## Get User

          ```http
          GET /api/v1/users/{id}
          ```

          ## Create User

          ```http
          POST /api/v1/users
          ```

      - path: posts.md
        props:
          title: Posts
          weight: 20
        content: |
          # Posts

          ## List Posts

          ```http
          GET /api/v1/posts
          ```

          ## Get Post

          ```http
          GET /api/v1/posts/{id}
          ```

  - path: schemas/_index.md
    props:
      title: Schemas
      weight: 30
    content: |
      # Data Schemas

      Request and response formats.

    pages:
      - path: user.md
        props:
          title: User Schema
        content: |
          # User Schema

          ```json
          {
            "id": "string",
            "email": "string",
            "name": "string",
            "created_at": "datetime"
          }
          ```

      - path: post.md
        props:
          title: Post Schema
        content: |
          # Post Schema

          ```json
          {
            "id": "string",
            "title": "string",
            "body": "string",
            "author_id": "string",
            "published_at": "datetime"
          }
          ```

  - path: errors.md
    props:
      title: Error Handling
      weight: 40
    content: |
      # Error Handling

      ## Error Response Format

      ```json
      {
        "error": {
          "code": "string",
          "message": "string"
        }
      }
      ```

      ## Common Error Codes

      | Code | Description |
      |------|-------------|
      | 400 | Bad Request |
      | 401 | Unauthorized |
      | 404 | Not Found |
      | 429 | Rate Limited |
      | 500 | Server Error |

Props Reference

Common frontmatter fields forprops:

Field Type Description
title string Page title (required)
description string SEO description
weight number Sort order (lower = first)
date string Publication date (ISO format)
tags list Taxonomy tags
draft boolean Exclude from production
type string Content type (doc,blog, etc.)
layout string Layout variant
nav_title string Short title for navigation

Custom props are accessible in templates viapage.props.fieldname.

Dynamic Placeholders

Use{{ date }}for the current date:

- path: posts/new-post.md
  props:
    title: New Post
    date: "{{date}}"
  content: |
    # New Post
    Written on {{date}}.

Cascade Patterns

Section-Level Cascade

Apply settings to all pages in a section:

- path: blog/_index.md
  props:
    title: Blog
  cascade:
    type: blog
    author: Default Author
  pages:
    - path: post-1.md
      props:
        title: First Post  # Inherits type: blog, author: Default Author

Override Cascade

Child pages can override cascaded values:

- path: docs/_index.md
  cascade:
    type: doc
    draft: false
  pages:
    - path: wip.md
      props:
        title: Work in Progress
        draft: true  # Overrides cascade

Tips

Version Your Skeletons

Includeversionin metadata. When you update the skeleton, increment the version:

name: Team Docs
version: "2.0"  # Breaking changes from 1.x

Organize by Purpose

Create separate skeletons for different use cases:

skeletons/
├── api-docs.yaml
├── blog.yaml
├── landing-page.yaml
└── product-docs.yaml

Test with Dry Run

Always preview before applying:

bengal project skeleton apply my-skeleton.yaml --dry-run

Troubleshooting

Files Not Created

Symptom:bengal project skeleton applyreports 0 files created.

Cause: Files already exist and--forcewas not used.

Fix:

bengal project skeleton apply my-skeleton.yaml --force

Content Not Rendering

Symptom: YAML content appears as raw text.

Cause: Incorrect indentation incontentblock.

Fix: Use|for multiline content and indent consistently:

content: |
  # Title

  Paragraph text.

  - List item

Cascade Not Applied

Symptom: Child pages don't inherit cascade settings.

Cause:cascadeis at the wrong level.

Fix: Placecascadeinside the parent page definition, not insideprops:

# Correct
- path: docs/_index.md
  cascade:
    type: doc

# Wrong
- path: docs/_index.md
  props:
    cascade:  # This won't work
      type: doc

Next Steps