Template Authoring
Templates define the schema for every entity type in StudioBrain. Each template is a markdown file with YAML frontmatter that declares all fields, their types, defaults, and structure. Creating a new template automatically creates a new entity type — no code changes required.
Template File Format
Templates live at _Templates/Standard/{TYPE}_TEMPLATE.md. The file name must follow the pattern {UPPERCASE_TYPE}_TEMPLATE.md.
A template has two parts:
- YAML frontmatter (between
---delimiters): Declares all fields with their types and default values. - Markdown body (below the frontmatter): Provides the default markdown content structure with section headings and placeholder text that serves as instructions for both human authors and AI generation.
Template Walkthrough: CHARACTER_TEMPLATE
Here is a simplified excerpt from the Character template showing the key patterns:
---
# METADATA
template_version: "2.0"
id: "[snake_case_name]"
entity_type: "character"
created_date: "[YYYY-MM-DD]"
last_updated: "[YYYY-MM-DD]"
status: "active"
# PRODUCTION STATUS TRACKING
production_status:
general: "concept"
game_uefn: "none"
tv_showrunner: "none"
notes: ""
# PRIMARY IMAGE
primary_image: ""
# BASIC IDENTITY
name: "[Generate Full Name]"
nickname: "[Generate Nickname]"
age: "18"
gender: "[male/female/non-binary]"
species: "human"
# TEMPORAL INFORMATION
birth_year: null
death_year: null
key_dates: []
current_age_reference_year: 1998
# PHYSICAL DESCRIPTION
height: "[Height in feet/inches]"
build: "[slim/average/muscular/heavyset]"
hair_color:
name: ""
hex: ""
eye_color:
name: ""
hex: ""
distinguishing_features:
- "[Unique physical feature 1]"
- "[Unique physical feature 2]"
# LOCATION & MOVEMENT
primary_location: "[Reference existing location ID]"
secondary_locations:
- "[Secondary location 1]"
# RELATIONSHIPS
faction: "[Faction affiliation or independent]"
family:
- "[Relationship]: [Person name/description]"
friends:
- "[Friend name/description]": "[relationship_type]"
enemies: []
# PERSONALITY & PSYCHOLOGY
personality_traits:
- "[Core trait 1]"
- "[Core trait 2]"
fears:
- "[Primary fear]"
motivations:
- "[Primary motivation]"
secrets:
- "[Hidden secret 1]"
defining_moment_summary: "[Brief summary of pivotal life event]"
# SKILLS & ABILITIES
primary_skills:
- "[Main skill 1]"
secondary_skills:
- "[Secondary skill 1]"
special_abilities: []
# STORY INTEGRATION
story_function:
- "[Role in narrative]"
inventory_items:
- "[Item 1]"
associated_brands:
- "[Brand name 1]"
job:
- "[Reference existing Job name 1]"
narrative_importance: "[main/major/minor]"
# AI GENERATION HELPERS
ai_profile_description: "[Visual description for AI]"
ai_voice_style: "[Speaking style description]"
ai_bio_summary: "[2-3 sentence bio]"
dialogue_samples:
- "[Example line 1]"
- "[Example line 2]"
---
# [Character Full Name]
## Background
[2-3 paragraphs describing the character's background...]
## Personality
[1-2 paragraphs describing personality...]
## Defining Moment
[1-2 paragraphs describing the pivotal life event...]
## Dialogue & Speech Patterns
[Description of how they speak...]
### Example Dialogue
> "[Greeting line]"
> "[Emotional response line]"
## Relationships
**[Category]**: [Description...]
## Story Role
[Function in the narrative...]Field Types
The type system is inferred from the default values in the template YAML. The type generator (generate_types.py) reads these defaults to determine TypeScript types.
| YAML Default | Inferred Type | TypeScript | Zod Schema |
|---|---|---|---|
"" or "placeholder" | string | string | z.string() |
0 or 42 | number | number | z.number() |
true or false | boolean | boolean | z.boolean() |
null | nullable string | string | null | z.string().nullable() |
[] | string array | string[] | z.array(z.string()) |
["item1", "item2"] | string array | string[] | z.array(z.string()) |
Nested object (key: value) | object | { key: type } | z.object({ ... }) |
String Fields
name: "[Generate Full Name]" # String with placeholder instruction
gender: "[male/female/non-binary]" # String with allowed values hint
status: "active" # String with default valueNumber Fields
age: "18" # Stored as string, parsed as needed
founded: 1987 # Integer
population: 0 # Integer with zero defaultBoolean Fields
is_active: true
is_deceased: falseArray Fields
# Simple string array
personality_traits:
- "[Core trait 1]"
- "[Core trait 2]"
# Empty array (items added by user)
special_abilities: []
enemies: []Nested Objects
# Object with named sub-fields
production_status:
general: "concept"
game_uefn: "none"
tv_showrunner: "none"
notes: ""
# Object with color data
hair_color:
name: ""
hex: ""
# Object with multiple sub-objects
skin_tone:
palette: ""
base: ""
midtone: ""
shadow: ""
highlight: ""Reference Fields
Fields that reference other entities use the target entity’s ID as a string value. The cross-reference system recognizes these patterns:
# Single reference
primary_location: "[Reference existing location ID]"
faction: "[Faction affiliation]"
# Array of references
secondary_locations:
- "[Secondary location 1]"
- "[Secondary location 2]"
associated_brands:
- "[Brand name 1]"
job:
- "[Reference existing Job name 1]"Entity Types Reference
StudioBrain ships with 16 entity templates. Each defines a different content type.
| Entity Type | Template File | Prefix | Description |
|---|---|---|---|
| character | CHARACTER_TEMPLATE.md | CH_ | Characters, NPCs, players |
| location | LOCATION_TEMPLATE.md | LOC_ | Places, buildings, landmarks |
| district | DISTRICT_TEMPLATE.md | DST_ | City areas, neighborhoods, zones |
| brand | BRAND_TEMPLATE.md | BR_ | Companies, products, services |
| faction | FACTION_TEMPLATE.md | FAC_ | Organizations, gangs, governments |
| item | ITEM_TEMPLATE.md | ITM_ | Game items, weapons, consumables |
| job | JOB_TEMPLATE.md | JOB_ | Occupations, roles, positions |
| quest | QUEST_TEMPLATE.md | QST_ | Missions, objectives, tasks |
| campaign | CAMPAIGN_TEMPLATE.md | CMP_ | Story arcs, campaigns |
| event | EVENT_TEMPLATE.md | EVT_ | World events, incidents |
| dialogue | DIALOGUE_TEMPLATE.md | DLG_ | Conversation trees, scripts |
| timeline | TIMELINE_TEMPLATE.md | TL_ | Historical entries, chronology |
| assembly | ASSEMBLY_TEMPLATE.md | ASM_ | Modular entity assemblies |
| style_bible | STYLE_BIBLE_TEMPLATE.md | STY_ | Visual style guides |
| universe | UNIVERSE_TEMPLATE.md | UNI_ | World-building, lore |
File and Folder Structure
Entities are stored in folders named after their entity ID:
Characters/
rex_marshall/
CH_rex_marshall.md # Main entity file
images/
portrait.png
concept_art.jpg
audio/
voice_sample.mp3
data/
stats.json
Locations/
downtown_plaza/
LOC_downtown_plaza.md
images/
aerial_view.png
Brands/
double_dip_dog_sauce/
BR_double_dip_dog_sauce.md
images/
logo.pngThe file prefix (CH_, LOC_, BR_, etc.) is defined per entity type in the EntityFieldMappings service.
Required Metadata Fields
Every template must include these metadata fields:
template_version: "2.0" # Template format version
id: "[snake_case_name]" # Unique entity identifier
entity_type: "character" # Must match template type
created_date: "[YYYY-MM-DD]" # ISO date
last_updated: "[YYYY-MM-DD]" # ISO date
status: "active" # active, archived, draftThe production_status block is optional but recommended for tracking workflow:
production_status:
general: "concept" # concept, in_progress, needs_work, narrative_done, art_done, complete
game_uefn: "none" # none, planned, in_progress, review, published, live
tv_showrunner: "none" # none, planned, in_progress, review, episode, current, archived
notes: ""AI Generation Sections
Templates can include fields specifically for AI context:
# AI GENERATION HELPERS
ai_profile_description: "[Visual description from profile image]"
ai_voice_style: "[Speaking style/vocal characteristics]"
ai_bio_summary: "[Concise 2-3 sentence bio for AI context]"
dialogue_samples:
- "[Example line showing personality]"These fields are consumed by the AI service’s Enhanced Context Builder to generate content that matches the entity’s established characteristics.
Rules Files
Rules files at _Rules/{TYPE}_RULES.md define validation and generation rules for each entity type. They use the same YAML frontmatter + markdown format.
Rule Structure
Each rule has these fields:
rules:
- id: "char_name_required"
category: "validation"
priority: "high"
validation_type: "required_field"
applies_to: "name"
description: "Character name is required"
- id: "char_age_range"
category: "validation"
priority: "medium"
validation_type: "range"
applies_to: "age"
min: 18
max: 85
description: "Character age must be between 18 and 85"
- id: "char_personality_count"
category: "generation"
priority: "high"
validation_type: "min_count"
applies_to: "personality_traits"
min_count: 3
description: "Characters should have at least 3 personality traits"Rule Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique rule identifier |
category | string | validation, generation, consistency, style |
priority | string | high, medium, low |
validation_type | string | required_field, range, min_count, regex, reference_exists, custom |
applies_to | string | Field name this rule validates |
description | string | Human-readable explanation |
Available Rule Files
| File | Entity Type | Purpose |
|---|---|---|
| CHARACTER_RULES.md | character | Name, age, traits, relationships |
| LOCATION_RULES.md | location | Coordinates, district refs, atmosphere |
| DISTRICT_RULES.md | district | Population, faction control |
| BRAND_RULES.md | brand | Industry, market position |
| FACTION_RULES.md | faction | Members, territory, hierarchy |
| ITEM_RULES.md | item | Rarity, pricing, stats |
| JOB_RULES.md | job | Salary, skills, employer |
| DIALOGUE_RULES.md | dialogue | Voice consistency, line format |
| CONTENT_RULES.md | (all) | General content quality |
| GENERATION_RULES.md | (all) | AI generation constraints |
| IMAGE_GENERATION_RULES.md | (all) | Image generation styles |
| STORY_RULES.md | (all) | Narrative consistency |
| TIMELINE_RULES.md | timeline | Date validation, ordering |
| UNIVERSE_RULES.md | universe | Lore consistency |
| VOICE_GENERATION_RULES.md | (all) | Voice/audio generation |
| WORLD_LORE_RULES.md | (all) | World-building consistency |
Rules API
Rules are managed through the REST API:
GET /api/rules # List all rule files
GET /api/rules/{type} # Get parsed rules
POST /api/rules/{type} # Add new rule
PUT /api/rules/{type}/{rule_id} # Update rule
DELETE /api/rules/{type}/{rule_id} # Delete rule
POST /api/rules/validate/{type} # Validate entity against rulesAutomatic Type Generation
Running npm run generate:types or npm run dev triggers the Python script CityBrainsStoryBuilder/backend/generate_types.py, which:
- Reads all
_Templates/Standard/*_TEMPLATE.mdfiles - Parses the YAML frontmatter
- Infers TypeScript types from default values
- Generates three files:
src/types/generated/entities.ts — TypeScript interfaces:
export interface EntityBase {
entity_type: string;
entity_id: string;
name: string;
fields: Record<string, any>;
markdown_body?: string;
created_at?: string;
updated_at?: string;
}
export interface Character extends EntityBase {
age?: string;
gender?: string;
faction?: string;
personality_traits?: string[];
primary_location?: string;
// ... all fields from template
}src/types/generated/schemas.ts — Zod validation schemas:
import { z } from 'zod';
export const CharacterSchema = z.object({
entity_type: z.literal('character'),
entity_id: z.string(),
name: z.string(),
age: z.string().optional(),
gender: z.string().optional(),
personality_traits: z.array(z.string()).optional(),
// ... runtime validation for all fields
});src/types/generated/mappings.ts — Type utilities:
export const ENTITY_TYPES = [
'character', 'location', 'district', 'brand',
'faction', 'item', 'job', 'quest', 'campaign',
'event', 'dialogue', 'timeline', 'assembly',
'style_bible', 'universe'
] as const;
export type EntityType = typeof ENTITY_TYPES[number];Importing Generated Types
Always import from the barrel export:
// Correct
import { Character, Location, CharacterSchema } from '@/types';
// Wrong -- don't import from generated/ directly
import { Character } from '@/types/generated/entities';
// Wrong -- don't create local interfaces
interface Character { ... }Adding a New Entity Type
Creating a new entity type requires only one step: create the template file.
- Create
_Templates/Standard/MYENTITY_TEMPLATE.md:
---
template_version: "2.0"
id: "[snake_case_name]"
entity_type: "myentity"
created_date: "[YYYY-MM-DD]"
last_updated: "[YYYY-MM-DD]"
status: "active"
production_status:
general: "concept"
game_uefn: "none"
tv_showrunner: "none"
notes: ""
primary_image: ""
name: "[Entity Name]"
custom_field_one: ""
custom_field_two: []
custom_nested:
sub_field_a: ""
sub_field_b: 0
---
# [Entity Name]
## Description
[Describe this entity...]
## Details
[Additional details...]-
Run
npm run generate:typesto produce TypeScript interfaces and Zod schemas. -
The new entity type is immediately available:
GET /api/entity/myentitylists entitiesPOST /api/entity/myentitycreates entitiesimport { MyEntity } from '@/types'provides the TypeScript interface- The frontend dynamically renders edit forms based on the template schema
No backend code changes, no database migrations, no frontend component creation.
Template Versioning
The template_version field tracks the template format. When you update a template (add fields, change structure), bump the version. The backend’s FrontmatterValidator can detect version mismatches and offer to migrate entity files to the new format.
template_version: "2.0" # Current format versionExisting entities with older template versions continue to work. Missing fields get default values from the template. Removed fields are preserved in the entity’s fields JSON but not validated.