raisindb-content-modeling

Installation
SKILL.md

RaisinDB Content Modeling

Define content schemas, page templates, and composable blocks using YAML files inside a RaisinDB package.

MANDATORY: After creating or modifying ANY .yaml or .node.yaml file in package/, immediately run:

npm run validate

Fix all errors before proceeding. Never skip validation.

1. NodeType YAML

NodeTypes define the data schema for content. Place files in package/nodetypes/.

Naming

Use namespace:PascalCase format: myapp:Article, launchpad:Page, shop:Product.

Full Example

name: launchpad:Page
title: Page
description: Base page type for Launchpad content
icon: file-text
color: "#6366f1"
version: 1

properties:
  - name: title
    title: Title
    type: String
    required: true
    is_translatable: true
    index:
      - Fulltext
  - name: slug
    title: Slug
    type: String
    required: true
  - name: description
    title: Description
    type: String
    required: false
    is_translatable: true

versionable: true
publishable: true
auditable: true
indexable: true

Property Types

Type Stored as Example
String JSON string "hello"
Number JSON number 42
Boolean JSON boolean true
Date ISO 8601 string "2025-01-15"
Object JSON object {"key": "val"}
Array JSON array ["a", "b"]
Reference raisin:ref object See below
Resource Resource metadata File attachment

Property Options

Option Type Description
required boolean Must be set on creation
is_translatable boolean Enable i18n translation
default any Default value when not provided
index string[] Index types, e.g. [Fulltext]

Top-Level Flags

Flag Effect
versionable Tracks revision history, enables draft/publish workflow
publishable Adds publish/unpublish lifecycle
auditable Records who changed what and when
indexable Includes in full-text search index

Reference Values at Runtime

A Reference property stores a raisin:ref object: {"raisin:ref": "node-id-or-path", "raisin:workspace": "workspace-name"}.


2. Archetype YAML

Archetypes define page templates linking a NodeType to editor fields. Place files in package/archetypes/. Multiple archetypes can share one NodeType for different layouts.

Example

name: launchpad:LandingPage
title: Landing Page
description: Landing page template with hero, content blocks, and features
icon: layout
color: "#6366f1"
base_node_type: launchpad:Page
version: 1

fields:
  - $type: TextField
    name: title
    title: Page Title
    required: true
    translatable: true

  - $type: TextField
    name: slug
    title: URL Slug
    required: true

  - $type: SectionField
    name: content
    title: Page Content
    allowed_element_types:
      - launchpad:Hero
      - launchpad:TextBlock
      - launchpad:FeatureGrid

publishable: true

Field Types

Field Type Purpose
TextField Text input (single or multi-line)
NumberField Numeric input
BooleanField Toggle / checkbox
DateField Date or datetime picker
MediaField File/image upload (maps to Resource property)
RichTextField Rich text / HTML editor
SectionField Container for ElementTypes -- the composition mechanism
CompositeField Group of sub-fields; use multiple: true for repeatable lists
ReferenceField Link to another RaisinDB node (raisin:ref object). Use for CTA links, related content, author, tags. Use multiple: true for arrays of references

Field Options

All fields support: name, title, required, translatable, description, multiple.

Option Type Description
name string Unique field identifier (required)
title string Human-readable label for the editor UI
required boolean Field must have a value
translatable boolean Field supports translation overlays
description string Help text shown in the editor
multiple boolean Allow multiple values (stores as array). Works on any field type. On CompositeField, creates a repeatable list of structured items

SectionField -- Composition Mechanism

SectionField composes pages from ElementTypes. Declare which element types are allowed via allowed_element_types. At runtime the property stores an array of element objects.

CompositeField -- Repeatable Sub-Fields

Use CompositeField with multiple: true for repeatable arrays of structured objects. Any field type supports multiple: true, but on CompositeField it creates a list of structured items. You can nest SectionField inside a CompositeField:

  - $type: CompositeField
    name: columns
    title: Columns
    multiple: true
    translatable: true
    fields:
      - $type: TextField
        name: id
        title: Column ID
        required: true

      - $type: TextField
        name: title
        title: Column Title
        required: true
        translatable: true

      - $type: SectionField
        name: cards
        title: Cards
        translatable: true
        allowed_element_types:
          - launchpad:KanbanCard

UUID Rule for Repeatable CompositeFields

When a repeatable CompositeField has ANY sub-field with translatable: true, each item in the content YAML must have a unique uuid. This enables per-item translation merging without losing non-translatable fields.

# In content YAML — items need uuid when composite has translatable fields
features:
  - uuid: feat-1
    title: Fast Development      # translatable
    icon: zap                    # NOT translatable — preserved during translation
  - uuid: feat-2
    title: Scalable
    icon: trending-up

Without UUIDs, npm run validate will emit COMPOSITE_MISSING_UUID. Duplicate UUIDs within the same array emit COMPOSITE_DUPLICATE_UUID.

ReferenceField — Node References

Use ReferenceField for any link to another RaisinDB node: CTA buttons linking to pages, related articles, author references, tag links, parent categories. Do NOT use TextField for these — TextField is only for external URLs (https://...), email addresses, or arbitrary strings that don't point to RaisinDB nodes.

Schema definition:

fields:
  - $type: ReferenceField
    name: cta_link
    title: CTA Link
    config:
      allowed_entry_types:
        - launchpad:Page

  - $type: ReferenceField
    name: author
    title: Author
    required: true
    config:
      allowed_entry_types:
        - myapp:Author

config.allowed_entry_types restricts which node types can be referenced. Omit it to allow any node type.

Content YAML format — both raisin:ref and raisin:workspace are required:

properties:
  cta_link:
    raisin:ref: /launchpad/contact
    raisin:workspace: launchpad
  author:
    raisin:ref: /launchpad/authors/jane
    raisin:workspace: launchpad
  • raisin:ref — node path (starting with /) or node ID (UUID). Paths are auto-resolved to UUIDs during INSERT/UPDATE.
  • raisin:workspace — target workspace name. Required even for same-workspace references.
  • Referenced node must exist at INSERT/UPDATE time (path-based refs are validated).

Frontend usage — read raisin:path for links:

<a href={element.cta_link?.['raisin:path']}>{element.cta_text}</a>

Multiple References

Use ReferenceField with multiple: true for one-to-many node references:

fields:
  - $type: ReferenceField
    name: related_articles
    title: Related Articles
    multiple: true
    config:
      allowed_entry_types:
        - myapp:Article

Content YAML stores an array of references:

properties:
  related_articles:
    - raisin:ref: /blog/articles/intro-to-raisin
      raisin:workspace: blog
    - raisin:ref: /blog/articles/advanced-queries
      raisin:workspace: blog

Querying References

-- Find all nodes that reference a specific target
SELECT * FROM blog
WHERE REFERENCES('blog:/articles/intro-to-raisin')

-- Resolve references inline (replace raisin:ref objects with full node data)
SELECT RESOLVE(properties) FROM blog WHERE path = $1

-- Resolve with depth control (nested references, max 10)
SELECT RESOLVE(properties, 3) FROM blog WHERE path = $1

3. ElementType YAML

ElementTypes are composable content blocks placed inside SectionField containers. Place files in package/elementtypes/. They use the same field types as archetypes.

Hero Example

name: launchpad:Hero
title: Hero Section
description: Full-width hero section with headline, subheadline, and call-to-action
icon: image
color: "#8b5cf6"
version: 1

fields:
  - $type: TextField
    name: headline
    title: Headline
    required: true
    translatable: true

  - $type: TextField
    name: subheadline
    title: Subheadline
    required: false
    translatable: true

  - $type: TextField
    name: cta_text
    title: CTA Button Text
    required: false
    translatable: true

  - $type: ReferenceField
    name: cta_link
    title: CTA Button Link
    required: false
    config:
      allowed_entry_types:
        - launchpad:Page

  - $type: MediaField
    name: background_image
    title: Background Image
    required: false

RichTextField Example

Use RichTextField for rich text / HTML content:

  - $type: RichTextField
    name: content
    title: Content
    required: true
    translatable: true

ElementType with Nested CompositeField

ElementTypes can use CompositeField with multiple: true for repeatable arrays of structured items:

name: launchpad:FeatureGrid
title: Feature Grid
icon: grid-3x3
color: "#f59e0b"
version: 1

fields:
  - $type: TextField
    name: heading
    title: Section Heading
    translatable: true

  - $type: CompositeField
    name: features
    title: Features
    multiple: true
    fields:
      - $type: TextField
        name: title
        title: Feature Title
        required: true
        translatable: true
      - $type: TextField
        name: description
        title: Feature Description
        required: true
        translatable: true

Dynamic Listing Pattern

Instead of hardcoding items in a CompositeField array, model a dynamic listing element type that references a folder and queries children at render time. This is content-driven: editors configure what to show, the frontend queries for it.

Element type (elementtypes/content-listing.yaml):

name: myapp:ContentListing
title: Content Listing
description: Dynamically lists child nodes of a referenced folder
icon: list
color: "#3b82f6"
version: 1

fields:
  - $type: TextField
    name: heading
    title: Section Heading
    translatable: true

  - $type: ReferenceField
    name: source
    title: Source Folder
    required: true
    config:
      allowed_entry_types:
        - raisin:Folder

  - $type: TextField
    name: filter_node_type
    title: Filter by Node Type
    description: "e.g. myapp:Project"

  - $type: NumberField
    name: limit
    title: Max Items

  - $type: TextField
    name: sort_by
    title: Sort By
    description: "Property name to sort by, e.g. title, created_at"

Content YAML — just configure, no data duplication:

content:
  - uuid: projects-listing-1
    element_type: myapp:ContentListing
    heading: Featured Projects
    source:
      raisin:ref: /myapp/projects
      raisin:workspace: myapp
    filter_node_type: myapp:Project
    limit: 6
    sort_by: created_at

Frontend component — queries at render time:

<script lang="ts">
  export let element: ContentListingElement

  const sourcePath = element.source?.['raisin:path']
  const nodeType = element.filter_node_type || ''
  const limit = element.limit || 10

  // Use CHILD_OF for direct children only, or DESCENDANT_OF for
  // the full subtree (nested folders like /projects/2025/01/post).
  // Results are already in natural path order (no ORDER BY needed).
  const sql = `
    SELECT id, path, name, properties
    FROM ${WORKSPACE}
    WHERE DESCENDANT_OF('${sourcePath}')
      ${nodeType ? `AND node_type = '${nodeType}'` : ''}
    LIMIT ${limit}
  `
  const items = query(sql)
</script>

<section>
  <h2>{element.heading}</h2>
  {#each items as item}
    <a href={item.path}>{item.properties.title}</a>
  {/each}
</section>

Choose based on your content structure:

  • CHILD_OF('/projects') — direct children only (flat list)
  • DESCENDANT_OF('/projects') — full subtree, handles nested folders like /projects/2025/01/my-project

Results are returned in natural path order by default — no ORDER BY needed. Add ORDER BY only when sorting by a different field (e.g., ORDER BY properties->>'created_at'::String DESC).

This pattern avoids duplicating content data in composite arrays. Editors add/remove/reorder items by managing actual nodes in the tree, and any listing element pointing to that folder updates automatically.

Runtime Representation

Elements appear in properties.content[] with uuid and element_type:

{ "uuid": "hero-1", "element_type": "launchpad:Hero", "headline": "Launch Your Vision" }

4. Mixins

Mixins are reusable property sets. Define them as NodeTypes with is_mixin: true. Place files in package/mixins/.

name: myapp:SEOFields
title: SEO Fields
description: Common SEO properties for pages
is_mixin: true

properties:
  - name: meta_title
    title: Meta Title
    type: String
  - name: meta_description
    title: Meta Description
    type: String
  - name: og_image
    title: Open Graph Image
    type: Resource

Register under provides.mixins in manifest.yaml. Mixins install before NodeTypes.


5. Workspace YAML

Workspaces scope content and control allowed NodeTypes. Place files in package/workspaces/.

name: launchpad
title: Launchpad
description: Content workspace for Launchpad portal
icon: rocket
color: "#6366f1"

allowed_node_types:
  - launchpad:Page
  - raisin:Folder
  - raisin:Asset        # Required for file uploads

allowed_root_node_types:
  - raisin:Folder
  - launchpad:Page

root_structure:
  - name: pages
    node_type: raisin:Folder
    title: Pages
    description: Site pages
  • allowed_node_types -- types that can be created in this workspace
  • allowed_root_node_types -- types allowed at root level
  • root_structure -- nodes created automatically on workspace init

6. manifest.yaml

Declares package metadata and all provided types. Place at package/manifest.yaml.

name: launchpad-next
version: 1.0.5
title: Launchpad Next
description: LaunchKit customer portal
author: SOLUTAS GmbH

provides:
  nodetypes:
    - launchpad:Page
  archetypes:
    - launchpad:LandingPage
    - launchpad:KanbanBoard
    - launchpad:FileBrowser
  elementtypes:
    - launchpad:Hero
    - launchpad:TextBlock
    - launchpad:FeatureGrid
    - launchpad:KanbanCard
  workspaces:
    - launchpad
  functions:
    - /lib/launchpad/handle-read-receipt
  triggers:
    - /triggers/on-read-receipt

workspace_patches:
  launchpad:
    allowed_node_types:
      add:
        - launchpad:Page
        - raisin:Folder
        - raisin:Asset          # Required for file uploads

Supported provides keys: nodetypes, archetypes, elementtypes, mixins, workspaces, functions, triggers, flows. Each maps to its directory (nodetypes/, archetypes/, etc.; functions use content/functions/lib/, triggers use content/functions/triggers/).

Use workspace_patches to add allowed node types to existing workspaces without overwriting their definition. Always include raisin:Asset if the workspace needs file uploads — without it, uploads will fail silently.


7. Content YAML

Content files define initial data installed with the package. Place .node.yaml files inside package/content/{workspace}/{path}/. The directory structure maps to the node tree.

File: package/content/launchpad/launchpad/home/.node.yaml

node_type: launchpad:Page
archetype: launchpad:LandingPage
properties:
  title: Welcome to Launchpad
  slug: home
  description: Your gateway to launching amazing projects
  content:
    - uuid: hero-1
      element_type: launchpad:Hero
      headline: Launch Your Vision
      subheadline: Build, deploy, and scale your ideas with Launchpad
      cta_text: Get Started
      cta_link:
        raisin:ref: /launchpad/contact
        raisin:workspace: launchpad

    - uuid: intro-1
      element_type: launchpad:TextBlock
      heading: Why Launchpad?
      content: |
        Launchpad is your all-in-one platform for turning ideas into reality.

    - uuid: features-1
      element_type: launchpad:FeatureGrid
      heading: Features
      features:
        - uuid: feat-fast
          icon: zap
          title: Fast Development
          description: Build and iterate quickly with our modern stack

Content YAML Rules

  • node_type -- required, must match a type in provides.nodetypes
  • archetype -- optional, links to an archetype for the editor UI
  • properties -- the node's property values
  • Each element in content needs a unique uuid and a valid element_type
  • Element fields correspond to the ElementType's field definitions

8. Validation

MANDATORY — run after every YAML change:

npm run validate

Do NOT proceed until all errors are resolved.

Common Errors

Error Cause
INVALID_NODE_TYPE_NAME Use namespace:PascalCase format (e.g. myapp:Article)
MISSING_REQUIRED_FIELD Manifest needs name and version at minimum
UNKNOWN_NODE_TYPE_REFERENCE Referenced type not listed in provides.nodetypes
DUPLICATE_PROPERTY No duplicate property names within a single NodeType
UNKNOWN_ELEMENT_TYPE Element type in allowed_element_types not in provides.elementtypes
MISSING_BASE_NODE_TYPE Archetype base_node_type not found in provides.nodetypes
COMPOSITE_MISSING_UUID Repeatable composite with translatable sub-fields has items without uuid
COMPOSITE_DUPLICATE_UUID Two items in the same composite array share the same uuid

Every type in provides must have a corresponding YAML file in the matching directory (nodetypes/, archetypes/, elementtypes/, mixins/, workspaces/). File names use kebab-case (e.g., landing-page.yaml for launchpad:LandingPage).

Related skills
Installs
3
GitHub Stars
1
First Seen
Apr 3, 2026