Skip to content

Instantly share code, notes, and snippets.

@esafwan
Last active September 30, 2025 20:53
Show Gist options
  • Select an option

  • Save esafwan/d9908a51a52574e611e3926a6fc9b289 to your computer and use it in GitHub Desktop.

Select an option

Save esafwan/d9908a51a52574e611e3926a6fc9b289 to your computer and use it in GitHub Desktop.
How is Frappe Studio build, its architecture, doctypes and more. And prompt for AI.

Frappe Studio Architecture Documentation

Github Url: https://github.com/frappe/studio

Overview

Frappe Studio is a visual application builder for the Frappe Framework that enables developers to create applications through a drag-and-drop interface. It combines Vue.js frontend technology with Frappe Framework's backend capabilities to provide a comprehensive low-code development environment.

Architecture Overview

High-Level Architecture

┌─────────────────────┐    ┌─────────────────────┐    ┌─────────────────────┐
│   Frontend (Vue)    │    │  Frappe Backend     │    │   Generated Apps    │
│                     │    │                     │    │                     │
│ • Studio Builder    │◄──►│ • DocTypes          │◄──►│ • Published Pages   │
│ • Canvas System     │    │ • API Endpoints     │    │ • App Routes        │
│ • Component Library │    │ • Export System     │    │ • Runtime Views     │
└─────────────────────┘    └─────────────────────┘    └─────────────────────┘

Technology Stack

  • Frontend: Vue 3 + TypeScript + Vite
  • UI Library: Frappe UI (Vue-based components)
  • State Management: Pinia stores
  • Backend: Frappe Framework (Python)
  • Database: Frappe DocTypes (MariaDB/PostgreSQL)
  • Build System: Vite for development, Frappe for production

Core DocTypes & Data Model

Primary DocTypes

1. Studio App

Purpose: Represents a complete application built in Studio

  • app_title: Human readable app name
  • app_name: Unique app identifier (auto-naming field)
  • route: URL route for the app
  • app_home: Link to the home page
  • published: Boolean publication status
  • is_standard: Flag for export to Frappe apps
  • frappe_app: Target Frappe app for export

2. Studio Page

Purpose: Individual pages within an app, contains the layout structure

  • page_name: Unique page identifier (auto-naming field)
  • page_title: Display name for the page
  • studio_app: Link to parent Studio App
  • route: URL route for the page
  • blocks: JSON field storing the published layout structure
  • draft_blocks: JSON field storing the draft layout structure
  • resources: Child table of data sources (Studio Page Resource)
  • variables: Child table of page variables (Studio Page Variable)
  • watchers: Child table of reactive scripts (Studio Page Watcher)
  • published: Boolean publication status

3. Studio Component

Purpose: Reusable custom components

  • component_name: Display name
  • component_id: Unique identifier (auto-naming field)
  • block: JSON structure of the component
  • inputs: Child table defining component props (Studio Component Input)

Child DocTypes

Studio Page Resource

Purpose: Data sources for pages (API endpoints, DocType queries)

  • resource_name: Unique identifier
  • resource_type: "Document List", "Document", or "API Resource"
  • document_type: Link to Frappe DocType
  • fields: JSON array of fields to fetch
  • filters: JSON object with filter conditions
  • url: API endpoint URL
  • method: HTTP method (GET, POST, PUT, DELETE)
  • transform: JavaScript function for data transformation

Studio Page Variable

Purpose: Page-level reactive variables

  • variable_name: Variable identifier
  • variable_type: "String", "Number", "Boolean", or "Object"
  • initial_value: JavaScript code for initial value

Studio Page Watcher

Purpose: Reactive scripts that respond to data changes

  • source: JavaScript expression to watch
  • script: JavaScript code to execute when source changes
  • immediate: Boolean flag to run script on page load

Vue.js Integration & Frontend Architecture

Application Structure

Main Application Setup (main.ts)

The main Vue application is initialized with essential plugins:

import { createApp } from "vue"
import { createPinia } from "pinia"
import { resourcesPlugin } from "frappe-ui"

const studio = createApp(App)
const pinia = createPinia()

studio.use(studio_router)      // Vue Router for navigation
studio.use(resourcesPlugin)    // Frappe UI resource management
studio.use(pinia)              // State management

Component Hierarchy

App.vue
├── StudioCanvas.vue           // Main visual editor canvas
│   ├── StudioComponent.vue    // Individual component renderer
│   └── StudioComponentWrapper.vue // Component interaction wrapper
├── StudioLeftPanel.vue        // Component palette & pages panel
├── StudioRightPanel.vue       // Properties & styles panel
└── StudioToolbar.vue          // Top toolbar with actions

State Management (Pinia Stores)

1. Studio Store (studioStore.ts)

Central store managing the overall application state:

  • App Management: Current app, app pages, navigation
  • Page Management: Active page, blocks, saving/publishing workflow
  • Data Management: Resources, variables, reactive data binding

Key methods:

  • setApp(appName): Load app and its pages
  • setPage(pageName): Load page and initialize blocks
  • savePage(): Serialize blocks to JSON and save to backend
  • publishPage(): Convert draft to published blocks

2. Canvas Store (canvasStore.ts)

Manages the visual editor canvas state:

  • Selection: Currently selected components and multi-selection
  • Editing: Component editing modes, drag/drop operations
  • Layout: Canvas scaling, panning, responsive breakpoints

3. Component Store (componentStore.ts)

Manages the component library:

  • Component Library: Available Frappe UI components
  • Custom Components: Studio-created reusable components
  • Component Loading: Dynamic component registration

Block System Architecture

Block Class (utils/block.ts)

The Block class is the core abstraction representing every UI element:

class Block {
  componentId: string           // Unique identifier
  componentName: string         // Component type (Button, Input, etc.)
  componentProps: object        // Component properties
  componentSlots: object        // Slot content for complex components
  componentEvents: object       // Event handlers
  children: Block[]            // Child components
  baseStyles: object           // CSS styles for desktop
  mobileStyles: object         // Mobile-specific style overrides
  tabletStyles: object         // Tablet-specific style overrides
  visibilityCondition: string  // Conditional visibility logic
}

Key Block Methods:

  • Hierarchy Management: addChild(), removeChild(), getParentBlock()
  • Style Management: setStyle(), getStyles(), responsive breakpoint handling
  • Slot Management: addSlot(), updateSlot() for complex component composition
  • Event Management: addEvent(), updateEvent() for component interactivity

Layout System & Data Flow

Layout Creation Process

1. Design Time (Studio Builder)

User Action → Canvas Interaction → Block Creation → Store Update → UI Refresh
     ↓              ↓                    ↓             ↓           ↓
Drag Component → Drop on Canvas → new Block() → studioStore → Re-render

2. Block Structure Creation

// When user drags a component from palette:
const newBlock = new Block({
  componentName: 'Button',
  componentProps: { variant: 'solid', text: 'Click me' },
  baseStyles: { padding: '8px 16px', backgroundColor: '#3b82f6' },
  componentEvents: { click: 'handleButtonClick()' }
})

// Add to parent container:
parentBlock.addChild(newBlock)

Database Serialization

Blocks to JSON (Save Process)

// Convert Block tree to JSON for database storage
function jsToJson(blocks: Block[]): string {
  const serialized = blocks.map(block => getBlockCopyWithoutParent(block))
  return JSON.stringify(serialized, null, 2)
}

// Stored in Studio Page.blocks or Studio Page.draft_blocks field

JSON to Blocks (Load Process)

// Convert JSON from database back to Block instances
function jsonToJs(jsonString: string): Block[] {
  const parsed = JSON.parse(jsonString)
  return parsed.map(blockData => getBlockInstance(blockData))
}

Runtime Rendering

1. Published Page Rendering

When a page is published, the JSON block structure is converted to Vue render functions:

// StudioComponent.vue renders blocks dynamically
const renderBlock = (block: Block) => {
  return h(
    COMPONENTS[block.componentName].component,  // Vue component
    {
      ...block.componentProps,                  // Component props
      style: block.getStyles(breakpoint),      // Computed styles
      ...block.componentEvents                 // Event handlers
    },
    renderChildren(block.children)             // Recursive children
  )
}

2. Data Binding & Reactivity

Resources and variables are made reactive and available to components:

// Resources are fetched and made reactive
const resources = computed(() => {
  return studioStore.resources  // Auto-updates when data changes
})

// Variables are reactive and can be referenced in expressions
const variables = reactive({
  userName: 'John Doe',
  isLoggedIn: true,
  userProfile: { /* ... */ }
})

Component System

1. Frappe UI Integration

Studio leverages Frappe UI components as building blocks:

// Component definitions with Studio metadata
export const COMPONENTS: FrappeUIComponents = {
  Button: {
    component: defineAsyncComponent(() => import('frappe-ui/Button')),
    icon: LucideMousePointer2,
    initialState: { variant: 'solid', text: 'Button' },
    initialSlots: [],
    category: 'Input'
  },
  // ... 50+ more components including Input, Select, Table, etc.
}

2. Custom Studio Components

Users can create reusable components stored as Studio Component DocType:

// Custom component usage
const customComponent = new Block({
  componentName: 'UserCard',
  isStudioComponent: true,
  block: savedComponentStructure,  // JSON from Studio Component.block
  inputs: componentInputDefinitions // Props interface
})

Responsive Design System

Breakpoint Management

const breakpoints = {
  desktop: { width: 1200, device: 'desktop' },
  tablet: { width: 768, device: 'tablet' },  
  mobile: { width: 375, device: 'mobile' }
}

// Style inheritance: mobile inherits tablet inherits desktop
const computedStyles = {
  ...block.baseStyles,      // Desktop (base)
  ...block.tabletStyles,    // Tablet overrides  
  ...block.mobileStyles     // Mobile overrides
}

Data Integration & API Layer

Resource System

Studio provides a unified interface for various data sources:

1. Document Resources (Frappe DocTypes)

{
  resource_type: "Document List",
  document_type: "Customer", 
  fields: ["name", "customer_name", "email"],
  filters: { "status": "Active" },
  limit: 20
}

2. API Resources (External APIs)

{
  resource_type: "API Resource",
  url: "https://api.example.com/users",
  method: "GET",
  params: { "page": 1 },
  transform: "return data.results.map(user => ({ id: user.id, name: user.full_name }))"
}

Variable System & Reactivity

Variable Types

  • String: Text values, form inputs
  • Number: Numeric calculations, counters
  • Boolean: Toggle states, conditions
  • Object: Complex data structures, API responses

Reactive Watchers

// Watcher configuration
{
  source: "variables.searchTerm",           // What to watch
  script: "resources.customers.reload()",   // What to execute
  immediate: false                          // Run on page load?
}

Export & Deployment System

Code Generation Process

1. App Build Generation

# studio/studio/doctype/studio_app/studio_app.py
def generate_app_build(self):
    """Generate static files for published app"""
    pages = get_published_pages(self.name)
    for page in pages:
        generate_page_files(page)
        generate_component_files(page)

2. File Export Structure

apps/[frappe_app]/
├── public/
│   └── studio/
│       └── [app_name]/
│           ├── pages/
│           │   └── [page_name].json
│           └── components/
│               └── [component_name].json
└── templates/
    └── generators/
        └── [app_name]/
            └── [page_name].html

Key Design Patterns

1. Component Composition Pattern

  • Blocks as Building Blocks: Every UI element is a Block instance
  • Hierarchical Structure: Parent-child relationships for layout
  • Slot System: Complex components support named slots for content

2. Reactive Data Flow

  • Unidirectional Flow: Data flows from stores to components
  • Computed Properties: Derived values update automatically
  • Watchers: Side effects triggered by data changes

3. Serialization Strategy

  • JSON Storage: Block trees serialized as JSON in database
  • Hydration: JSON converted back to reactive Block instances
  • Export: Published pages generate static Vue templates

This architecture enables rapid application development while maintaining the flexibility and power of the underlying Frappe Framework.

CSS Styling System & Variable Storage

How CSS Properties Are Stored in JSON

Frappe Studio stores CSS styles in the Block's JSON structure using three separate style objects for responsive design:

Style Storage Structure

// Block class properties
class Block {
  baseStyles: BlockStyleMap      // Desktop/default styles  
  mobileStyles: BlockStyleMap    // Mobile-specific overrides
  tabletStyles: BlockStyleMap    // Tablet-specific overrides
  rawStyles: BlockStyleMap       // Raw CSS styles
}

Example JSON Structure with Flex Properties

{
  "componentName": "container",
  "componentId": "container-abc123",
  "baseStyles": {
    "display": "flex",
    "flexDirection": "column", 
    "flexWrap": "wrap",
    "flexShrink": 0,
    "alignItems": "center",
    "justifyContent": "center",
    "backgroundColor": "#f4f4f4",
    "padding": "20px",
    "width": "100%",
    "height": "200px"
  },
  "tabletStyles": {
    "flexDirection": "row",
    "padding": "15px"
  },
  "mobileStyles": {
    "flexDirection": "column",
    "padding": "10px"
  },
  "children": [...]
}

CSS Property Name Conversion

Studio uses camelCase for CSS property names in the JSON structure, converting from kebab-case:

// Utility function in helpers.ts
function kebabToCamelCase(str: string) {
  // convert border-color to borderColor
  return str.replace(/-([a-z])/g, function (g) {
    return g[1].toUpperCase();
  });
}

// Usage in Block class
setStyle(style: styleProperty, value: StyleValue) {
  style = kebabToCamelCase(style) as styleProperty
  // Store as: flexDirection, backgroundColor, etc.
}

Style Application Process

1. Style Computation (Responsive)

// Block.getStyles() method
getStyles(breakpoint: string = "desktop"): BlockStyleMap {
  let styleObj = { ...this.baseStyles }
  
  if (["mobile", "tablet"].includes(breakpoint)) {
    styleObj = { ...styleObj, ...this.tabletStyles }
    if (breakpoint === "mobile") {
      styleObj = { ...styleObj, ...this.mobileStyles }
    }
  }
  styleObj = { ...styleObj, ...this.rawStyles }
  return styleObj
}

2. Style Application in Vue Components

// StudioComponent.vue
const styles = computed(() => {
  const _styles = { ...props.block.getStyles(props.breakpoint) }
  
  // Handle dynamic values (expressions)
  Object.entries(_styles).forEach(([key, value]) => {
    if (value && isDynamicValue(value.toString())) {
      _styles[key] = getDynamicValue(value.toString(), evaluationContext.value)
    }
  })
  return _styles
})

3. DOM Application

<!-- StudioComponent.vue template -->
<component
  :is="componentName"
  :style="styles"  <!-- Computed styles applied here -->
  :class="classes"
  v-bind="componentProps"
>

Flex Properties Handling

Common Flex Properties Stored

{
  "baseStyles": {
    "display": "flex",           // or "inline-flex"
    "flexDirection": "row",      // "column", "row-reverse", "column-reverse"
    "flexWrap": "nowrap",        // "wrap", "wrap-reverse" 
    "flexShrink": 0,             // numeric values
    "flexGrow": 1,               // numeric values
    "alignItems": "center",      // "flex-start", "flex-end", "stretch"
    "justifyContent": "center",  // "flex-start", "flex-end", "space-between"
    "alignContent": "center",    // for wrapped flex containers
    "gap": "10px",               // spacing between flex items
    "order": 1                   // flex item order
  }
}

Template Usage for Flex Containers

// Block templates with flex defaults
const getBlockTemplate = (element: string) => {
  if (element === "body") {
    return {
      componentName: "div",
      originalElement: "body", 
      baseStyles: {
        display: "flex",
        flexWrap: "wrap",
        flexDirection: "column",
        flexShrink: 0,
        alignItems: "center"
      }
    }
  }
}

CSS Variables Usage

While Studio doesn't extensively use CSS custom properties (CSS variables), it does use some for layout:

/* Used in layout components */
:root {
  --toolbar-height: 56px;  /* Toolbar height */
}

/* Applied in templates */
.canvas-container {
  top: var(--toolbar-height);
}

Dynamic Style Values

Studio supports dynamic expressions in style values:

{
  "baseStyles": {
    "width": "{{ variables.containerWidth }}px",
    "backgroundColor": "{{ variables.theme.primaryColor }}",
    "display": "{{ variables.isVisible ? 'flex' : 'none' }}"
  }
}

These expressions are evaluated at runtime using the getDynamicValue() function with access to:

  • Page variables
  • Resource data
  • Route parameters
  • Component context

Style Inheritance & Breakpoints

Breakpoint Priority (Cascading)

  1. Desktop (baseStyles): Default styles applied to all breakpoints
  2. Tablet (tabletStyles): Inherits desktop + tablet overrides
  3. Mobile (mobileStyles): Inherits desktop + tablet + mobile overrides
// Example of style resolution for mobile
const mobileStyles = {
  ...block.baseStyles,      // Desktop base
  ...block.tabletStyles,    // Tablet overrides
  ...block.mobileStyles     // Mobile overrides
}

This comprehensive styling system allows for responsive design with granular control over CSS properties while maintaining a clean JSON structure for database storage.

Component Architecture & Implementation

Frappe Studio implements a two-tier component system with Standard Components (Frappe UI) and Custom Components (Studio Components), each serving different purposes and implemented differently.

Component Types Overview

1. Standard Components (Frappe UI Components)

  • Source: Pre-built components from the Frappe UI library
  • Count: 36+ components (Button, Input, Select, Table, etc.)
  • Storage: Defined in code, not in database
  • Customization: Props and styling only
  • Reusability: Global across all Studio apps

2. Custom Components (Studio Components)

  • Source: User-created reusable components
  • Storage: Stored in Studio Component DocType
  • Customization: Full layout, props interface, and logic
  • Reusability: Available across pages within Studio

Standard Component Implementation

Component Registry

// /frontend/src/data/components.ts
export const COMPONENTS: FrappeUIComponents = {
  Button: {
    name: "Button",
    title: "Button",
    icon: LucideMousePointer2,
    initialState: { variant: "solid", text: "Button" },
    category: "Input"
  },
  Input: {
    name: "Input", 
    title: "Text Input",
    icon: LucideEdit,
    initialState: { placeholder: "Enter text..." },
    category: "Input"
  },
  // ... 50+ more components
}

Component Categories

Standard components are organized into categories:

  • Input: Button, Input, Select, Checkbox, Switch
  • Display: Alert, Badge, Avatar, Progress
  • Layout: Card, Tabs, Divider
  • Data: ListView, Tree
  • Charts: AxisChart, DonutChart, NumberChart

Component Loading

// Standard components are loaded from Frappe UI
import { Button, Input, Select } from "frappe-ui"

// Components are registered globally in the component registry
const componentMap = new Map([
  ["Button", Button],
  ["Input", Input],
  // ...
])

Custom Component (Studio Component) Implementation

Database Schema

-- Studio Component DocType structure
CREATE TABLE `tabStudio Component` (
  `name` varchar(140) PRIMARY KEY,
  `component_name` varchar(140),     -- Display name
  `component_id` varchar(140),       -- Unique identifier
  `block` longtext,                  -- JSON structure of the component
  -- Child table for component inputs/props
)

-- Studio Component Input (child table)
CREATE TABLE `tabStudio Component Input` (
  `input_name` varchar(140),         -- Prop name
  `type` varchar(50),               -- Data type
  `description` text,               -- Documentation
  `default` longtext,               -- Default value (code)
  `required` int(1),                -- Required flag
  `options` text                    -- Additional options
)

Component Creation Process

// 1. User creates component from selected blocks
async function createComponent(componentName: string, block?: Block) {
  const component = { 
    component_name: componentName,
    block: getBlockObject(block)  // Serialize block tree to JSON
  }
  
  return studioComponents.insert.submit(component)
}

// 2. Component is stored in database
// 3. Component becomes available in component palette

Component Storage Structure

// Example Studio Component JSON in database
{
  "component_name": "UserCard",
  "component_id": "user-card-abc123",
  "block": {
    "componentName": "div",
    "baseStyles": {
      "display": "flex",
      "flexDirection": "column",
      "padding": "16px",
      "borderRadius": "8px",
      "backgroundColor": "#f9fafb"
    },
    "children": [
      {
        "componentName": "Avatar",
        "componentProps": {
          "src": "{{ inputs.avatarUrl }}",
          "size": "lg"
        }
      },
      {
        "componentName": "TextBlock", 
        "componentProps": {
          "text": "{{ inputs.userName }}",
          "fontSize": "text-lg",
          "fontWeight": "font-semibold"
        }
      }
    ]
  },
  "inputs": [
    {
      "input_name": "userName",
      "type": "String",
      "description": "User's display name",
      "default": "John Doe",
      "required": true
    },
    {
      "input_name": "avatarUrl", 
      "type": "String",
      "description": "URL to user's avatar image",
      "default": "",
      "required": false
    }
  ]
}

Component Loading & Caching System

Component Store Architecture

// /stores/componentStore.ts
export const useComponentStore = defineStore("componentStore", () => {
  const componentMap = reactive<Map<string, Block>>(new Map())
  const componentDocMap = reactive<Map<string, StudioComponent>>(new Map())
  
  async function loadComponent(componentName: string) {
    if (!componentMap.has(componentName)) {
      // Fetch from database
      const componentDoc = await fetchComponent(componentName)
      cacheComponent(componentDoc)
    }
  }
  
  function cacheComponent(componentDoc: StudioComponent) {
    componentDocMap.set(componentDoc.component_id, componentDoc)
    if (componentDoc.block) {
      // Convert JSON to Block instance
      componentMap.set(componentDoc.component_id, 
        markRaw(getBlockInstance(componentDoc.block)))
    }
  }
})

Lazy Loading Strategy

  • Standard Components: Loaded synchronously from imports
  • Custom Components: Loaded asynchronously from database when first used
  • Caching: Components cached in memory after first load
  • Missing Components: Fallback to "missing component" template

Component Usage & Rendering

Standard Component Usage

// Block creation for standard component
const buttonBlock = new Block({
  componentName: "Button",           // Maps to Frappe UI Button
  isStudioComponent: false,         // Standard component flag
  componentProps: {
    variant: "solid",
    text: "Click me",
    onClick: "handleClick()"
  }
})

Custom Component Usage

// Block creation for custom component  
const userCardBlock = new Block({
  componentName: "user-card-abc123", // Maps to Studio Component ID
  isStudioComponent: true,          // Custom component flag
  componentProps: {
    userName: "{{ variables.currentUser.name }}",
    avatarUrl: "{{ variables.currentUser.avatar }}"
  }
})

Rendering Differences

Standard Component Rendering:

<!-- Direct Vue component rendering -->
<component
  :is="Button"                    <!-- Frappe UI component -->
  :variant="props.variant"
  :text="props.text"
  @click="handleClick"
/>

Custom Component Rendering:

<!-- Wrapper component for custom components -->
<StudioComponentWrapper
  v-if="block.isStudioComponent"
  :studioComponent="block"
  :evaluationContext="context"
>
  <!-- Renders the stored block structure -->
  <StudioComponent :block="componentBlock" />
</StudioComponentWrapper>

Component Interface System

Input Definition

Custom components support typed inputs/props:

// Component input definition
interface ComponentInput {
  input_name: string      // Property name (e.g., "userName")
  type: string           // Data type ("String", "Number", "Boolean", "Object")
  description?: string   // Documentation
  default?: any         // Default value
  required: boolean     // Required flag
  options?: string      // Additional configuration
}

Context Injection

// StudioComponentWrapper.vue
const componentContext = computed(() => {
  const context = { ...studioComponent.componentProps }
  const componentDoc = componentStore.getComponentDoc(componentName)
  
  // Apply default values from input definitions
  componentDoc.inputs?.forEach((input) => {
    if (!(input.input_name in context) && input.default !== undefined) {
      context[input.input_name] = input.default
    }
  })
  
  return { inputs: context }
})

// Provide context to child components
provide("componentContext", componentContext)

Component Editor System

Visual Editor

  • Canvas Mode: Edit component layout visually
  • Interface Tab: Define component inputs/props
  • Preview Mode: Test component with sample data

Interface Definition

// Component editor for defining inputs
const componentInputs = ref<ComponentInput[]>([
  {
    input_name: "title",
    type: "String", 
    description: "Card title",
    default: "Default Title",
    required: true
  }
])

Key Differences: Standard vs Custom Components

Aspect Standard Components Custom Components
Source Frappe UI library User-created in Studio
Storage Code-based registry Database (Studio Component DocType)
Props Fixed component API User-defined input interface
Layout Single Vue component Nested block structure
Reusability Global (all apps) Studio-wide
Customization Props + styling only Full layout + logic
Performance Direct component rendering Wrapper + block rendering
Loading Synchronous import Async database fetch
Icon Blue border in editor Purple border in editor

Component Identification

Components are distinguished at runtime:

// Block property determines component type
if (block.isStudioComponent) {
  // Custom component - load from database
  return <StudioComponentWrapper studioComponent={block} />
} else {
  // Standard component - use directly
  return <component is={block.componentName} {...props} />
}

This dual-component architecture provides both the reliability of pre-built UI components and the flexibility of user-created custom components, enabling rapid application development while maintaining extensibility.

Complete Standard Component Library

Frappe Studio provides 52 standard components organized into two main categories: Frappe UI Components (36 components) and Studio Components (16 components). Each component comes with predefined initial states and specific capabilities.

Frappe UI Components (36 Components)

These are direct integrations of the Frappe UI library components with Studio-specific configurations.

Form & Input Components (11 Components)

1. Button

  • Purpose: Interactive buttons for actions
  • Props:
    • label (string): Button text - "Submit"
    • variant (string): Button style - "solid", "outline", "ghost", "subtle"
  • Icon: Rectangle Horizontal
  • Use Cases: Form submissions, actions, navigation

2. FormControl

  • Purpose: Enhanced form input with label and validation
  • Props:
    • type (string): Input type - "text", "email", "password", "number"
    • label (string): Field label - "Name"
    • placeholder (string): Placeholder text - "John Doe"
    • autocomplete (string): Browser autocomplete - "off", "on"
    • modelValue (any): Bound value (optional)
  • Icon: Book Type
  • Use Cases: Text input with built-in labeling

3. TextInput

  • Purpose: Basic text input field
  • Props:
    • placeholder (string): Placeholder text - "Enter your name"
    • modelValue (string): Input value
  • Icon: A Large Small
  • Use Cases: Simple text entry

4. Textarea

  • Purpose: Multi-line text input
  • Props:
    • placeholder (string): Placeholder text - "Enter your message"
    • modelValue (string): Textarea content
    • rows (number): Number of visible rows
  • Icon: Letter Text
  • Use Cases: Comments, descriptions, long text

5. Select

  • Purpose: Dropdown selection with options
  • Props:
    • placeholder (string): Placeholder text - "Person"
    • options (array): Selection options with label, value, disabled properties
    • modelValue (any): Selected value
  • Example Options: [{ label: "John Doe", value: "john-doe" }, { label: "Jane Smith", value: "jane-smith", disabled: true }]
  • Icon: Mouse Pointer 2
  • Use Cases: Single selection from predefined options

6. Autocomplete

  • Purpose: Search-enabled selection with images
  • Props:
    • placeholder (string): Placeholder text - "Select Person"
    • options (array): Options with label, value, image properties
    • modelValue (any): Selected value
  • Example Options: [{ label: "John Doe", value: "john-doe", image: "https://randomuser.me/api/portraits/men/59.jpg" }]
  • Icon: Text Search
  • Use Cases: User selection, searchable dropdowns

7. Checkbox

  • Purpose: Boolean selection with label
  • Props:
    • label (string): Checkbox label - "Enable feature"
    • padding (boolean): Add padding - true/false
    • checked (boolean): Checked state - true/false
    • modelValue (boolean): Bound value
  • Icon: Circle Check
  • Use Cases: Feature toggles, agreements

8. Switch

  • Purpose: Toggle switch with description
  • Props:
    • label (string): Switch label - "Enable Notifications"
    • description (string): Help text - "Get notified when someone mentions you"
    • modelValue (boolean): Switch state - true/false
  • Icon: Toggle Left
  • Use Cases: Settings, preferences

9. FileUploader

  • Purpose: File upload interface
  • Props:
    • label (string): Upload button text - "Upload File"
    • fileTypes (array): Accepted file types - ['image/*'], ['.pdf', '.doc']
    • multiple (boolean): Allow multiple files
  • Icon: File Up
  • Use Cases: Document uploads, image selection

10. DatePicker

  • Purpose: Date selection widget
  • Props:
    • placeholder (string): Placeholder text - "Select Date"
    • modelValue (string/Date): Selected date
    • format (string): Date display format
  • Icon: Calendar Check
  • Use Cases: Event dates, deadlines

11. DateTimePicker

  • Purpose: Date and time selection
  • Props:
    • placeholder (string): Placeholder text - "Select Date Time"
    • modelValue (string/Date): Selected datetime
    • format (string): DateTime display format
  • Icon: Calendar Clock
  • Use Cases: Appointments, precise timing

12. DateRangePicker

  • Purpose: Date range selection
  • Props:
    • placeholder (string): Placeholder text - "Select Date Range"
    • modelValue (array): Date range [startDate, endDate]
    • format (string): Date display format
  • Icon: Calendar Search
  • Use Cases: Reporting periods, bookings

Display & Feedback Components (8 Components)

13. Alert

  • Purpose: Status messages and notifications
  • Props:
    • title (string): Alert message - "This user is inactive"
    • type (string): Alert type - "warning", "error", "success", "info"
    • description (string): Additional details (optional)
  • Icon: Circle Alert
  • Use Cases: Error messages, warnings, success notifications

14. Badge

  • Purpose: Status indicators and labels
  • Props:
    • variant (string): Badge style - "subtle", "solid", "outline"
    • theme (string): Color theme - "green", "red", "blue", "yellow", "gray"
    • size (string): Badge size - "sm", "md", "lg"
    • label (string): Badge text - "Active"
  • Icon: Badge Check
  • Use Cases: Status tags, categories, counts

15. Avatar

  • Purpose: User profile images
  • Props:
  • Icon: User
  • Use Cases: User representation, profile displays

16. Progress

  • Purpose: Progress indicators
  • Props:
    • value (number): Progress percentage - 50 (0-100)
    • size (string): Progress bar size - "xs", "sm", "md", "lg"
    • label (string): Progress label - "Progress"
    • color (string): Progress color theme
  • Icon: Ellipsis
  • Use Cases: Loading states, completion tracking

17. ErrorMessage

  • Purpose: Error display component
  • Props:
    • message (string): Error text - "Transaction failed due to insufficient balance"
    • title (string): Error title (optional)
  • Icon: Circle X
  • Use Cases: Form validation, error handling

18. Tooltip

  • Purpose: Contextual help text
  • Props:
    • text (string): Tooltip content - "This is a tooltip"
    • placement (string): Tooltip position - "top", "bottom", "left", "right"
    • hoverDelay (number): Show delay in ms
  • Icon: Message Square
  • Use Cases: Help text, additional information

19. FeatherIcon

  • Purpose: Icon display component
  • Props:
    • name (string): Icon name - "activity", "home", "settings", "user"
    • class (string): CSS classes - "h-6 w-6"
    • size (string): Icon size preset
  • Icon: Feather
  • Use Cases: UI icons, visual indicators

20. FormLabel

  • Purpose: Standalone form labels
  • Props:
    • label (string): Label text - "Form Label"
    • required (boolean): Show required indicator
    • description (string): Help text (optional)
  • Icon: Tag
  • Use Cases: Custom form layouts

Layout & Navigation Components (7 Components)

21. Card

  • Purpose: Content containers with title/subtitle
  • Props:
    • title (string): Card title - "John Doe"
    • subtitle (string): Card subtitle - "Engineering Lead"
    • image (string): Header image URL (optional)
    • padding (boolean): Add internal padding
  • Icon: ID Card
  • Use Cases: Profile cards, content sections

22. Tabs

  • Purpose: Tabbed content organization
  • Props:
    • as (string): HTML element - "div", "section"
    • tabs (array): Tab configuration with label and content
    • modelValue (string): Active tab identifier
  • Example Tabs: [{ label: "Github", content: "Github is a code hosting platform..." }, { label: "Twitter", content: "Twitter is an American microblogging..." }]
  • Icon: Arrow Right Left
  • Use Cases: Content organization, multi-view interfaces

23. TabButtons

  • Purpose: Tab-style button navigation
  • Props:
    • buttons (array): Button configuration with label and value
    • modelValue (string): Selected button value
  • Example Buttons: [{ label: "My Tasks", value: "mytasks" }, { label: "Team Tasks", value: "teamtasks" }]
  • Icon: Arrow Right Left
  • Use Cases: View switching, filters

24. Breadcrumbs

  • Purpose: Navigation path display
  • Props:
    • items (array): Navigation items with label and route
    • separator (string): Custom separator character
  • Example Items: [{ label: "Home", route: { name: "Home" } }, { label: "List", route: "/components/breadcrumbs" }]
  • Icon: Chevrons Right
  • Use Cases: Navigation hierarchy, current location

25. Divider

  • Purpose: Visual content separation
  • Props:
    • orientation (string): "horizontal", "vertical"
    • thickness (string): Line thickness
    • color (string): Divider color
  • Icon: Minus
  • Use Cases: Section breaks, visual organization

26. Dialog

  • Purpose: Modal dialogs and popups
  • Props:
    • modelValue (boolean): Dialog visibility - false
    • disableOutsideClickToClose (boolean): Prevent outside click close - true
    • options (object): Dialog configuration with title, message, size, actions
  • Example Options: { title: "Confirm", message: "Are you sure?", size: "xl", actions: [{ label: "Confirm", variant: "solid", onClick: () => {} }] }
  • Special Features: editInFragmentMode: true, uses ProxyDialog component
  • Icon: App Window Mac
  • Use Cases: Confirmations, forms, detailed views

27. Dropdown

  • Purpose: Action menus and options
  • Props:
    • options (array): Menu items with label, onClick, icon
    • button (object): Trigger button configuration
    • placement (string): Menu position
  • Example Options: [{ label: "Edit Title", onClick: () => {}, icon: () => h(FeatherIcon, { name: "edit-2" }) }]
  • Icon: Chevron Down
  • Use Cases: Context menus, action lists

Data Display Components (7 Components)

28. ListView

  • Purpose: Tabular data display
  • Props:
    • columns (array): Column definitions with label, key, width, getLabel, prefix
    • rows (array): Data rows as objects
    • rowKey (string): Unique row identifier - "id"
    • selectable (boolean): Enable row selection
  • Example Columns: [{ label: "Name", key: "name", width: 3, getLabel: ({ row }) => row.name, prefix: ({ row }) => h(Avatar, { image: row.user_image }) }]
  • Example Rows: [{ id: 1, name: "John Doe", email: "john@doe.com", status: "Active", role: "Developer" }]
  • Icon: List Check
  • Use Cases: Data tables, user lists

29. Tree

  • Purpose: Hierarchical data display
  • Props:
    • options (object): Tree configuration with showIndentationGuides, rowHeight, indentWidth
    • nodeKey (string): Node identifier property - "name"
    • node (object): Root node with name, label, children
  • Example Options: { showIndentationGuides: true, rowHeight: "25px", indentWidth: "15px" }
  • Example Node: { name: "guest", label: "Guest", children: [{ name: "downloads", label: "Downloads", children: [...] }] }
  • Icon: List Tree
  • Use Cases: File browsers, organizational structures

30. Calendar

  • Purpose: Event calendar display
  • Props:
    • config (object): Calendar settings with defaultMode, isEditMode, eventIcons, allowCustomClickEvents
    • events (array): Event objects with title, participant, id, venue, fromDate, toDate, color
  • Example Config: { defaultMode: "Month", isEditMode: true, allowCustomClickEvents: true, redundantCellHeight: 100 }
  • Example Events: [{ title: "English by Ryan Mathew", participant: "Ryan Mathew", fromDate: "2024-07-08 16:30:00", toDate: "2024-07-08 17:30:00", color: "green" }]
  • Icon: Calendar
  • Use Cases: Scheduling, event management

31. AxisChart

  • Purpose: Bar/line charts with axes
  • Props:
    • config (object): Chart configuration with data, title, subtitle, xAxis, yAxis, series
    • data (array): Chart data points
    • xAxis (object): X-axis config with key, type, title, timeGrain
    • yAxis (object): Y-axis config with title, echartOptions
    • series (array): Data series with name, type
  • Example Config: { title: "Monthly Sales", xAxis: { key: "month", type: "time", timeGrain: "month" }, yAxis: { title: "Amount ($)" }, series: [{ name: "sales", type: "bar" }] }
  • Example Data: [{ month: "2021-01-01", sales: 200 }, { month: "2021-02-01", sales: 300 }]
  • Icon: Chart Line
  • Use Cases: Analytics, trends, reporting

32. DonutChart

  • Purpose: Circular chart for proportional data
  • Props:
    • config (object): Chart configuration with data, title, subtitle, categoryColumn, valueColumn
    • data (array): Chart data with category and value fields
  • Example Config: { title: "Product Sales Distribution", subtitle: "Sales distribution across products", categoryColumn: "product", valueColumn: "sales" }
  • Example Data: [{ product: "Apple Watch", sales: 400 }, { product: "Services", sales: 400 }, { product: "iPhone", sales: 200 }]
  • Icon: Chart Pie
  • Use Cases: Market share, distribution analysis

33. NumberChart

  • Purpose: Single metric display with delta
  • Props:
    • config (object): Metric configuration with title, value, prefix, delta, deltaSuffix, negativeIsBetter
  • Example Config: { title: "Total Sales", value: 123456, prefix: "$", delta: 10, deltaSuffix: "% MoM", negativeIsBetter: false }
  • Icon: Dollar Sign
  • Use Cases: KPIs, dashboards, metrics

34. TextEditor

  • Purpose: Rich text editing
  • Props:
    • content (string): Editor content - "Type something..."
    • editorClass (string): CSS classes for editor
    • editable (boolean): Enable editing - true
    • fixedMenu (boolean): Show fixed toolbar - true
    • bubbleMenu (boolean): Show bubble menu - true
  • Special Features: useOverridenPropTypes: true
  • Icon: Edit
  • Use Cases: Content creation, documentation

Specialized Components (3 Components)

35. TextBlock

  • Purpose: Styled text display
  • Props:
    • fontSize (string): Text size - "text-xs", "text-sm", "text-base", "text-lg", "text-xl"
    • fontWeight (string): Text weight - "font-normal", "font-medium", "font-semibold", "font-bold"
    • text (string): Display text content
    • color (string): Text color
  • Icon: Type
  • Use Cases: Headings, paragraphs, styled text

Studio Components (16 Components)

These are Studio-specific components built for layout and specialized functionality.

Layout Components (4 Components)

1. Container

  • Purpose: Basic layout container
  • Props:
    • padding (string): Internal padding
    • margin (string): External margin
    • backgroundColor (string): Background color
  • Use Cases: Content grouping, layout structure

2. FitContainer

  • Purpose: Responsive container that fits content
  • Props:
    • minHeight (string): Minimum height constraint
    • maxWidth (string): Maximum width constraint
  • Use Cases: Adaptive layouts, responsive design

3. SplitView

  • Purpose: Two-panel layout
  • Props:
    • orientation (string): "horizontal", "vertical"
    • splitRatio (number): Panel size ratio (0-1)
    • resizable (boolean): Allow resizing
  • Initial Slots: ["left", "right"]
  • Icon: Square Split Horizontal
  • Use Cases: Master-detail views, comparisons

4. Repeater

  • Purpose: Dynamic content repetition
  • Props:
    • data (array): Data source for repetition
    • itemKey (string): Unique key property
    • template (object): Item template configuration
  • Icon: Repeat
  • Use Cases: Lists, grids, dynamic content

Navigation Components (4 Components)

5. Header

  • Purpose: Page header with navigation
  • Props:
    • title (string): Header title - "Frappe"
    • menuItems (array): Navigation items with label and url
    • logo (string): Logo image URL
  • Example MenuItems: [{ label: "Home", url: "#" }, { label: "Settings", url: "#" }]
  • Icon: Frame
  • Use Cases: Site headers, navigation bars

6. AppHeader

  • Purpose: Application-specific header
  • Props:
    • title (string): App title - "Frappe"
    • subtitle (string): App subtitle
    • actions (array): Header action buttons
  • Icon: Frame
  • Use Cases: App-specific branding

7. Sidebar

  • Purpose: Side navigation panel
  • Props:
    • title (string): Sidebar title - "Frappe"
    • menuItems (array): Menu items with label, featherIcon, route_to
    • collapsed (boolean): Collapsed state
  • Example MenuItems: [{ label: "Home", featherIcon: "home", route_to: "/" }, { label: "Settings", featherIcon: "settings", route_to: "/" }]
  • Icon: Sidebar
  • Use Cases: App navigation, menu systems

8. BottomTabs

  • Purpose: Bottom navigation tabs
  • Props:
    • tabs (array): Tab configuration with label, icon, route
    • activeTab (string): Currently active tab
  • Example Tabs: [{ label: "Home", icon: "home", route: "/" }, { label: "Settings", icon: "settings", route: "/settings" }]
  • Icon: Arrow Right Left
  • Use Cases: Mobile navigation, tab bars

Content Components (5 Components)

9. AvatarCard

  • Purpose: Card with avatar/image
  • Props:
    • title (string): Card title - "Up&Up"
    • subtitle (string): Card subtitle - "Coldplay"
    • imageURL (string): Image source URL
    • size (string): Card size - "sm", "md", "lg"
  • Icon: Image
  • Use Cases: Music cards, profile displays

10. CardList

  • Purpose: List of cards
  • Props:
    • title (string): List title - "Card List"
    • cards (array): Card data with title, subtitle, imageURL
    • layout (string): Grid layout - "grid", "list"
  • Example Cards: [{ title: "Card Title", subtitle: "Subtitle", imageURL: "https://avatars.githubusercontent.com/u/499550" }]
  • Icon: List
  • Use Cases: Product lists, content galleries

11. Audio

  • Purpose: Audio player component
  • Props:
    • file (string): Audio file URL
    • autoplay (boolean): Auto-start playback
    • controls (boolean): Show player controls
    • loop (boolean): Loop playback
  • Icon: Music
  • Use Cases: Music players, audio content

12. ImageView

  • Purpose: Image display component
  • Props:
    • image (string): Image source URL
    • size (string): Image size - "xs", "sm", "md", "lg", "xl"
    • alt (string): Alt text for accessibility
    • fit (string): Image fit - "cover", "contain", "fill"
  • Icon: Image
  • Use Cases: Image galleries, media display

13. TextBlock

  • Purpose: Styled text component
  • Props:
    • text (string): Text content
    • fontSize (string): Text size classes
    • fontWeight (string): Font weight classes
    • textAlign (string): Text alignment
  • Icon: Type
  • Use Cases: Headings, content blocks

Content Creation Components (3 Components)

14. MarkdownEditor

  • Purpose: Markdown editing interface
  • Props:
    • modelValue (string): Markdown content - "# This is a markdown editor"
    • preview (boolean): Show preview mode
    • toolbar (boolean): Show editing toolbar
    • height (string): Editor height
  • Icon: File Pen Line
  • Use Cases: Documentation, content creation

Complete Component Lists:

  • FRAPPE_UI_COMPONENTS (36): Alert, Autocomplete, Avatar, Badge, Button, Breadcrumbs, Card, Checkbox, Calendar, DatePicker, DateTimePicker, DateRangePicker, Dialog, Divider, Dropdown, ErrorMessage, FeatherIcon, FileUploader, FormLabel, FormControl, ListView, Progress, Select, Switch, Tabs, TabButtons, Textarea, TextInput, TextEditor, Tooltip, Tree, AxisChart, NumberChart, DonutChart, TextBlock
  • STUDIO_COMPONENTS (16): Container, FitContainer, Repeater, Header, Sidebar, SplitView, AvatarCard, CardList, Audio, ImageView, TextBlock, AppHeader, BottomTabs, MarkdownEditor

Component Special Features

Proxy Components

Some components use proxy components for enhanced editing:

  • Dialog: Uses ProxyDialog.vue for better modal editing experience

Fragment Mode Editing

  • Dialog: editInFragmentMode: true - Opens in a dedicated editing mode

Slot-Based Components

  • SplitView: Predefined slots for ["left", "right"] content areas

Enhanced Prop Types

  • TextEditor: useOverridenPropTypes: true for custom property handling
  • FormControl: Additional props with modelValue and placeholder options

Component Organization

// Component registry structure
export const COMPONENTS: FrappeUIComponents = {
  // 36 Frappe UI Components
  Alert: { name: "Alert", title: "Alert", icon: LucideCircleAlert, ... },
  Button: { name: "Button", title: "Button", icon: LucideRectangleHorizontal, ... },
  // ... more components
  
  // 16 Studio Components  
  Repeater: { name: "Repeater", title: "Repeater", icon: LucideRepeat },
  Header: { name: "Header", title: "Header", icon: LucideFrame, ... },
  // ... more components
}

// Utility functions
export default {
  ...COMPONENTS,
  list: Object.values(COMPONENTS),        // Array of all components
  names: Object.keys(COMPONENTS),         // Array of component names
  getProxyComponent,                      // Get proxy component if exists
  isFrappeUIComponent,                    // Check if component is from Frappe UI
  get,                                    // Get component by name
}

This comprehensive component library provides developers with a rich set of building blocks covering all aspects of modern web application development, from basic forms and inputs to complex data visualization and navigation patterns.

Publishing System & Runtime Architecture

Publishing Workflow Overview

When a user clicks the Publish button in Studio, a multi-step process is triggered that transforms draft blocks into live, publicly accessible pages. The workflow involves validation, app build generation, block promotion, and optional file export.

Frontend Publishing Flow

1. Publish Button Click (StudioToolbar.vue, lines 100-113)

The publish button in the toolbar triggers the publishing workflow. When clicked:

  • Sets a publishing loading state to provide user feedback
  • Calls store.publishPage() from the studio store
  • Ensures the loading state is cleared regardless of success or failure

2. Store Method: publishPage() (studioStore.ts, lines 195-230)

This is the main orchestration method for publishing a page. It executes a three-step process:

Step 1: Generate App Build (Optional)

  • Calls generateAppBuild() to create optimized production assets
  • Wrapped in try-catch to ensure publishing continues even if build fails
  • Build failures are logged but don't block page publication

Step 2: Call Backend Publish Method

  • Submits a DocMethod call to studio_page.publish()
  • Passes the current page name
  • Triggers server-side validation and block promotion

Step 3: Refresh and Open Page

  • Fetches the updated page data from backend
  • Automatically opens the published page in a new browser window/tab
  • Uses openPageInBrowser() to construct the correct URL

3. App Build Generation: generateAppBuild() (studioStore.ts, lines 266-282)

Triggers the Vite bundling process for the current app:

Method Behavior:

  • Calls the backend studio_app.generate_app_build() method
  • Shows success toast: "App build generated"
  • On error, shows warning toast with error details (non-blocking)
  • Warning toast persists indefinitely to alert developers of build issues
  • Returns a promise that resolves regardless of build outcome

Backend Publishing Process

1. Studio Page Publish Method (studio_page.py, lines 206-214)

Method: publish(self, **kwargs) - whitelisted for frontend calls

This method executes the core publishing logic on the server side:

Key Actions:

  1. Validation: Calls validate_conflicts_with_other_pages() to ensure no duplicate routes or page titles exist in published pages within the same app
  2. Block Promotion: Copies content from draft_blocks field to blocks field, then clears draft_blocks
  3. Published Flag: Sets published = 1 to make the page publicly accessible
  4. Save Trigger: Calls save() which triggers on_update() hook, initiating export workflow if is_standard = 1

The method accepts kwargs from the frontend but primarily operates on the document's own state. It ensures atomic publishing - either all steps succeed or the page remains unpublished.

2. Conflict Validation (studio_page.py, lines 216-231)

Method: validate_conflicts_with_other_pages(self)

Purpose: Prevent routing conflicts and naming collisions within an app

Query Logic:

  • Searches for other published pages in the same Studio App
  • Excludes the current page from search (by name)
  • Uses OR logic to check for duplicate route OR duplicate page_title
  • Returns matching pages with their route and title information

Error Handling:

  • If conflicts found, throws a user-friendly error message listing all conflicting pages
  • Error format: "Page(s) with duplicate Route or Page Title already exist in this app: [Page Title - /route]"
  • Prevents publish operation from completing, maintaining route uniqueness

App Build Generation (Vite Bundling)

1. Build Trigger (studio_app.py, lines 137-153)

Method: generate_app_build(self) - whitelisted for frontend calls

Permission Check:

  • Requires "write" permission on Studio App DocType
  • Throws PermissionError if user lacks required permissions

Smart Build Detection:

  • Calls get_app_components(app_name, "draft_blocks") to analyze components in draft pages
  • Calls get_app_components(app_name) to analyze components in published pages
  • Uses set symmetric difference to detect if component usage has changed
  • Optimization: Skips build entirely if no component changes detected (avoids unnecessary rebuilds)

Build Execution:

  • Constructs yarn command: yarn build-studio-app [app_name] --components [comma-separated-list]
  • Gets Studio app source path using frappe.get_app_source_path("studio")
  • Executes command via popen() with raise_err=True for error propagation
  • Command runs in Studio app directory with proper working directory context

Build Output:

  • Generates optimized JavaScript bundle with tree-shaking
  • Creates minified CSS with only used styles
  • Output location: /studio/public/app_builds/[app_name]/assets/
  • Generates Vite manifest for asset path resolution

2. Vite Manifest Structure

Location: /studio/public/app_builds/[app_name]/.vite/manifest.json

Manifest Purpose: The manifest maps logical entry points to content-hashed physical files, enabling:

  • Cache busting through hash-based filenames
  • Reliable asset path resolution
  • Dynamic stylesheet injection
  • Production asset loading

Manifest Entry Structure:

  • Key: Original entry point name (e.g., renderer-[app_name].js)
  • file: Hashed output filename (e.g., assets/renderer-a3f2b1c9.js)
  • css: Array of associated CSS files with hashes

Backend Integration: Method get_assets_from_manifest() (lines 155-196 in studio_app.py) reads this manifest to:

  • Find the entry point for the specific app
  • Extract script and stylesheet paths
  • Pass them to the HTML template for injection
  • Handle missing manifest gracefully with fallback to development renderer

Export System (for is_standard Apps)

When is_standard = 1 and developer_mode = 1, published pages are automatically exported to the file system as JSON for version control and deployment.

Export Conditions (export.py, lines 37-45)

Function: can_export(doc) -> bool

Purpose: Gate-keeping function that determines if a document should be exported to the file system

Required Conditions (ALL must be true):

  1. doc.is_standard = 1 - Document marked for export
  2. frappe.conf.developer_mode = 1 - Site in development mode
  3. not frappe.flags.in_install - Not during app installation
  4. not frappe.flags.in_uninstall - Not during app uninstallation
  5. not frappe.flags.in_import - Not during data import operations

Rationale: Prevents circular exports during migrations, installations, and imports while ensuring only designated standard documents are exported in developer environments.

Export Workflow (studio_page.py, lines 85-106)

Trigger Point - on_update(self) hook:

  • Called automatically after any document save operation
  • Initiates export process if conditions are met
  • Ensures file system stays in sync with database

Main Export Method - export_page(self):

  • Checks can_export() before proceeding
  • Calls write_document_file() to serialize page JSON to disk
  • Calls delete_old_page_file() to clean up renamed pages
  • Calls export_components() to export all used Studio Components

Component Export - export_components(self):

  • Calls get_studio_components() to scan page blocks for custom components
  • Creates component folder with __init__.py for Python module structure
  • Iterates through each component and writes JSON file
  • Ensures components are available for import on other sites

File Operations:

  • write_document_file() - Serializes document to JSON with proper formatting
  • get_folder_path() - Constructs path: [frappe_app]/studio/[app_name]/studio_page/
  • get_component_folder_path() - Returns: [frappe_app]/studio/[app_name]/studio_components/

Export File Structure

[frappe_app]/
├── studio/
│   └── [app_name]/
│       ├── studio_app.json               # App configuration
│       ├── studio_page/
│       │   ├── __init__.py
│       │   ├── home_page.json            # Page 1 (JSON)
│       │   └── about_page.json           # Page 2 (JSON)
│       └── studio_components/
│           ├── __init__.py
│           ├── user_card.json            # Custom component 1
│           └── product_card.json         # Custom component 2
└── public/
    └── app_builds/
        └── [app_name]/
            ├── .vite/
            │   └── manifest.json         # Vite build manifest
            └── assets/
                ├── renderer-[hash].js    # Bundled app code
                └── renderer-[hash].css   # Bundled styles

Runtime Page Rendering

Published pages are served through two different rendering paths depending on whether the user is previewing drafts or accessing the published app.

1. Preview Mode (Draft Testing) - studio_app.py, lines 33-41

URL Pattern: /dev/[app_route]/[page_route]

Method: update_context(self) - with is_preview() returning True

Context Setup:

  • Sets is_preview = True in template context
  • Prepends /dev/ to app route for preview URLs
  • Uses studio_renderer.html template (development renderer)
  • Loads all pages from Studio App (published + unpublished) for testing access
  • Query: frappe.get_all("Studio Page", {"studio_app": app_name}, ["name", "page_title", "route"])

Rendering Characteristics:

  • Uses development assets from Vite dev server (port 8080)
  • Enables Hot Module Replacement (HMR) for instant updates during development
  • Shows all pages regardless of publication status for testing
  • Includes Vite client scripts for live reloading
  • No build required - direct TypeScript/Vue compilation

Use Cases: Testing draft changes, developing pages, iterating on layout without publishing

2. Production Mode (Published Apps) - studio_app.py, lines 41-49

URL Pattern: /[app_route]/[page_route]

Method: update_context(self) - with is_preview() returning False

Context Setup:

  • Uses app_renderer.html template (production renderer)
  • Calls get_assets_from_manifest() to read Vite manifest
  • Extracts stylesheet and script paths from manifest
  • Falls back to studio_renderer.html if manifest not found (with assets_not_found = True)

Manifest Reading Logic:

  • Opens /studio/public/app_builds/[app_name]/.vite/manifest.json
  • Searches for entry key matching pattern renderer-[app_name].js
  • Constructs full asset URLs with base path /assets/studio/app_builds/[app_name]/
  • Handles missing manifest gracefully with fallback renderer

Rendering Characteristics:

  • Uses production-built assets from Vite build output
  • Shows only published pages (filtered by published = 1)
  • Serves optimized, minified, and tree-shaken JavaScript/CSS
  • Assets have content-hashed filenames for optimal caching
  • No HMR - static production bundle

Use Cases: Live applications, end-user access, production deployments

3. HTML Template Comparison

Production Template (app_renderer.html): Location: studio/templates/generators/app_renderer.html

Structure:

  • Standard HTML5 document with full-height layout classes
  • Title set from app_title context variable
  • Dynamic asset injection via Jinja2 loops:
    • Iterates through stylesheets array for CSS links
    • Conditionally includes script if present from manifest
  • All assets loaded with crossorigin attribute for CORS

Mount Points:

  • #app - Main Vue application mount point
  • #modals - Portal target for modal dialogs
  • #popovers - Portal target for popover components

Window Context Variables:

  • csrf_token - CSRF token for API calls
  • app_name - Studio App identifier
  • app_route - App base route
  • app_title - App display title
  • app_pages - JSON array of published pages only

Preview Template (studio_renderer.html): Location: studio/templates/generators/studio_renderer.html

Structure:

  • Similar HTML5 structure to production template
  • Static asset references pointing to Studio frontend build:
    • Hardcoded script: /assets/studio/frontend/assets/renderer-[hash].js
    • Hardcoded stylesheet: /assets/studio/frontend/assets/block-[hash].css
  • Assets are pre-built Studio renderer, not app-specific

Additional Window Variables:

  • is_preview - Flag indicating preview mode
  • app_pages - JSON array of all pages (published + unpublished)

Developer Mode Enhancement:

  • Conditionally includes HMR scripts when is_developer_mode = True
  • Loads Vite client from dev server: http://[site_name]:8080/@vite/client
  • Loads renderer source: http://[site_name]:8080/src/renderer.ts
  • Enables live reloading during development

Client-Side Page Rendering

Once the HTML is loaded, the Vue renderer takes over:

  1. App Bootstrap: The renderer script initializes a Vue application
  2. Page Data Fetch: Uses window.app_pages to get available pages
  3. Route Matching: Vue Router matches URL to page based on route field
  4. Block Hydration: Fetches page's blocks JSON from backend
  5. Component Rendering: Recursively renders blocks as Vue components
  6. Resource Loading: Fetches page resources (Document List, API, etc.)
  7. Variable Initialization: Sets up reactive variables with initial values
  8. Watcher Activation: Registers reactive watchers for side effects

Key DocType Fields in Publishing

Studio Page DocType

  • draft_blocks (JSON): Work-in-progress layout (editable in Studio)
  • blocks (JSON): Published layout (served to end users)
  • published (Check): Boolean flag enabling public access
  • route (Data): URL path for the page (e.g., /home, /products/:id)
  • is_standard (Check): Enables file export to Frappe app
  • frappe_app (Link): Target Frappe app for exports

Studio App DocType

  • app_name (Data): Unique app identifier
  • app_title (Data): Human-readable app name
  • route (Data): Base URL route (e.g., myapp)
  • app_home (Link): Default landing page
  • published (Check): App publication status
  • is_standard (Check): Enables export to Frappe app
  • frappe_app (Link): Target Frappe app for exports

Publishing State Transitions

┌─────────────────────┐
│   Draft State       │
│                     │
│ • draft_blocks: {}  │
│ • blocks: null      │
│ • published: 0      │
└──────────┬──────────┘
           │
           │ Click "Publish"
           │
           ▼
┌─────────────────────┐
│  Validation Phase   │
│                     │
│ • Check route dups  │
│ • Check title dups  │
└──────────┬──────────┘
           │
           │ Validation passes
           │
           ▼
┌─────────────────────┐
│   Build Phase       │
│  (if required)      │
│                     │
│ • Scan components   │
│ • Run Vite build    │
│ • Generate manifest │
└──────────┬──────────┘
           │
           │ Build complete (or skipped)
           │
           ▼
┌─────────────────────┐
│  Published State    │
│                     │
│ • draft_blocks: {}  │ ─┐
│ • blocks: {}        │  │ Same content
│ • published: 1      │ ─┘
└──────────┬──────────┘
           │
           │ User makes edits
           │
           ▼
┌─────────────────────┐
│   Draft-Published   │
│      State          │
│                     │
│ • draft_blocks: {}  │ ← New changes
│ • blocks: {}        │ ← Old published version
│ • published: 1      │
└─────────────────────┘

Performance Optimizations

1. Lazy Build Generation

  • Builds are only generated when component usage changes
  • Skips build if draft_blocks components == blocks components

2. Asset Caching

  • Production builds use content-hashed filenames (e.g., renderer-a3f2b1c9.js)
  • Browser can cache assets indefinitely
  • New builds generate new hashes, auto-invalidating cache

3. Component Tree Shaking

  • Only components actually used in published pages are bundled
  • Unused Frappe UI components are excluded from build
  • Reduces bundle size significantly

4. Preview Fallback

  • If production build fails or is missing, system falls back to development renderer
  • Ensures pages remain accessible even with build issues
  • Error message logged to browser console

Summary: Publish Flow Diagram

User Action: Click "Publish" Button
           │
           ▼
Frontend: studioStore.publishPage()
           │
           ├─► generateAppBuild() (optional)
           │   │
           │   └─► Backend: studio_app.generate_app_build()
           │       │
           │       ├─► Analyze component usage
           │       ├─► Run Vite: yarn build-studio-app
           │       └─► Generate manifest.json
           │
           └─► Backend: studio_page.publish()
               │
               ├─► validate_conflicts_with_other_pages()
               ├─► Set published = 1
               ├─► Copy draft_blocks → blocks
               ├─► Clear draft_blocks
               └─► save() triggers on_update()
                   │
                   └─► export_page() (if is_standard = 1)
                       │
                       ├─► Write page JSON to file system
                       └─► Export used Studio Components
           
           ▼
Result: Page accessible at /[app_route]/[page_route]
        Using optimized production assets

This publishing system enables a smooth workflow from visual editing to production deployment, with built-in optimization, validation, and export capabilities for both hosted Studio apps and exportable Frappe applications.

SYSTEM / INSTRUCTIONS TO MODEL You are generating a UI layout for Frappe Studio. Produce a Blocks AST as a JSON array only (no envelope, no prose, no comments). Follow the exact schema and rules below. Insert the user brief at the end where indicated.

— Goal Return a ready-to-store value for Studio Page.blocks: an array of Block nodes that renders a complete page.

— Output Format • Output only a JSON array of Block nodes.
• Root MUST be a "div" with "componentId":"root" and "originalElement":"body".

— Block Node Schema (authoritative) Every node MUST include these keys (empty objects/arrays allowed): { "componentId": string, // unique & stable; e.g. "container-abc123" "componentName": string, // e.g., "container", "div", "Button", "TextBlock", "ImageView", "Sidebar" "componentProps": object, // component-specific props (see components section) "componentSlots": object, // { [slotName]: Block[] } if used; else {} "componentEvents": object, // { click: "navigate('/')" } etc. "blockName": string, // human label, e.g., "Hero", "Card A" "children": Block[], // ordered children (default slot) "baseStyles": object, // camelCase CSS with units "rawStyles": object, // keep {} "mobileStyles": object, // overrides "tabletStyles": object, // overrides "originalElement": string, // e.g., "div", "button", "img", "body" "classes": string[], // Tailwind/utility classes; optional list // optional: "visibilityCondition": string // JS boolean expression; optional }

— AST / Structure Rules • Tree is hierarchical; each node has one parent; children is ordered.
• Use ID-based addressing internally; keep componentId unique, URL-safe. Suggested pattern: <type>-<8..12 base36>.
• Use children for default content; use componentSlots only for components that need named slots.
• Avoid mixing display:grid and flex props on the same node.

— Styling Rules • Use camelCase CSS and include units: "16px", "1rem", "100%".
• Breakpoint cascade: baseStylestabletStylesmobileStyles (later overrides earlier).
• To avoid collapsed sections/cards: set display and either height/minHeight or ample padding.
• Keep heights fixed when requested.
• Common layout defaults for sections/cards:

  • display: "flex" or "grid"
  • width: "100%"
  • padding: "16px" (or more)
  • border/background to show structure (e.g., 1px solid, subtle color)

— Allowed CSS (allowlist) Layout: display, flexDirection, flexWrap, alignItems, justifyContent, alignContent, gap, rowGap, columnGap, alignSelf, justifySelf, placeItems, gridTemplateColumns, gridTemplateRows, gridAutoRows.
Sizing: width, minWidth, maxWidth, height, minHeight, maxHeight, padding*, margin*, overflow, overflowX, overflowY.
Typography: fontSize, fontWeight, lineHeight, letterSpacing, textAlign, color, backgroundColor, opacity.
Borders & effects: border*, borderRadius, boxShadow.
Positioning: position, top, right, bottom, left, zIndex.

— Components & Props — Extended Catalog

General notes • For every node, keep all required Block keys (componentId, componentName, componentProps, componentSlots, componentEvents, blockName, children, baseStyles, rawStyles, mobileStyles, tabletStyles, originalElement, classes).
• componentEvents values are strings (e.g., "navigate('/path')").
• Use camelCase CSS with units in baseStyles/tabletStyles/mobileStyles.
• originalElement: pick something sensible (e.g., "div", "button", "input", "img"). If unknown, use "div".

CORE / COMMON

  1. TextBlock componentName: "TextBlock" componentProps:

    • fontSize: "text-sm|text-base|text-lg|text-xl|text-2xl|text-3xl" (default: "text-sm")
    • fontWeight: "font-normal|font-medium|font-semibold|font-bold" (default: "font-normal")
    • tag: "P|H1|H2|H3|H4" (optional)
    • textColor: e.g., "text-gray-900","text-slate-700" (optional)
    • text: string classes: Tailwind ok (e.g., "font-sans","antialiased","tracking-tight")
  2. ImageView componentName: "ImageView" componentProps:

    • image: Picsum or any URL (see Picsum rules)
    • size: "xs|sm|md|lg" (default: "xs")
    • shape: "circle" (optional) classes: add ["object-cover","w-full","h-full"] for cover behavior
  3. Button componentName: "Button" componentProps:

    • label: string (default: "Submit")
    • variant: "solid|subtle|ghost" (default: "solid") componentEvents: e.g., { "click": "navigate('/app')" } originalElement: "button"
  4. Sidebar componentName: "Sidebar" componentProps:

    • title: string (default: "Frappe")
    • menuItems: [{ label, featherIcon, route_to }] Notes: use fixed width + full height in baseStyles for layouts.
  5. container / div componentName: "container" or "div" Purpose: structure/layout wrappers. Props: none required. originalElement: "div" Notes: give display + size (flex/grid) and fixed heights as needed.

FEEDBACK & STATUS 6) Alert componentName: "Alert" componentProps:

  • title: string (default: "This user is inactive")
  • type: "info|success|warning|error" (default: "warning")
  1. ErrorMessage componentName: "ErrorMessage" componentProps:

    • message: string
  2. Progress componentName: "Progress" componentProps:

    • value: number (0–100, default: 50)
    • size: "sm|md|lg" (default: "sm")
    • label: string
  3. Tooltip componentName: "Tooltip" componentProps:

    • text: string

ICONS / BADGES / AVATARS 10) FeatherIcon componentName: "FeatherIcon" componentProps: - name: string (e.g., "activity","edit-2") - class: string (Tailwind size classes, e.g., "h-6 w-6")

  1. Badge componentName: "Badge" componentProps:

    • variant: "subtle|solid|outline" (default: "subtle")
    • theme: color token (e.g., "green")
    • size: "sm|md|lg" (default: "sm")
    • label: string (default: "Active")
  2. Avatar componentName: "Avatar" componentProps:

    • shape: "circle|square" (default: "circle")
    • size: "xs|sm|md|lg" (default: "md")
    • image: URL
    • label: string (fallback initials)

NAVIGATION & LAYOUT 13) Breadcrumbs componentName: "Breadcrumbs" componentProps: - items: [{ label: string, route: string | { name: string } }]

  1. Header componentName: "Header" componentProps:

    • title: string (default: "Frappe")
    • menuItems: [{ label, url }]
  2. AppHeader componentName: "AppHeader" componentProps:

    • title: string (default: "Frappe")
  3. BottomTabs componentName: "BottomTabs" componentProps:

    • tabs: [{ label, icon, route }]
  4. SplitView componentName: "SplitView" componentSlots:

    • "left": Block[]
    • "right": Block[] Notes: render children into componentSlots.left/right, not default children.

FORMS & INPUTS 18) TextInput componentName: "TextInput" componentProps: - placeholder: string

  1. Textarea componentName: "Textarea" componentProps:

    • placeholder: string
  2. FormControl componentName: "FormControl" componentProps:

    • type: "text|email|password|number|…"
    • label: string (default: "Name")
    • placeholder: string (default: "John Doe")
    • autocomplete: string (default: "off")
    • modelValue (optional) originalElement: "input" (usually)
  3. FormLabel componentName: "FormLabel" componentProps:

    • label: string (default: "Form Label")
  4. Checkbox componentName: "Checkbox" componentProps:

    • label: string
    • padding: boolean (default: true)
    • checked: boolean (default: true)
  5. Select componentName: "Select" componentProps:

    • placeholder: string
    • options: [{ label, value, disabled? }]
  6. Autocomplete componentName: "Autocomplete" componentProps:

    • placeholder: string
    • options: [{ label, value, image? }]
  7. Switch componentName: "Switch" componentProps:

    • label: string
    • description: string
    • modelValue: boolean
  8. DatePicker componentName: "DatePicker" componentProps:

    • placeholder: string
  9. DateTimePicker componentName: "DateTimePicker" componentProps:

    • placeholder: string
  10. DateRangePicker componentName: "DateRangePicker" componentProps:

    • placeholder: string
  11. FileUploader componentName: "FileUploader" componentProps:

    • label: string (default: "Upload File")
    • fileTypes: string or string[] (e.g., "['image/*']")

MODALS / MENUS / DIVIDERS 30) Dialog componentName: "Dialog" componentProps: - modelValue: boolean (open/close) - disableOutsideClickToClose: boolean - options: { title: string, message: string, size: "sm|md|lg|xl", actions: [{ label, variant: "solid|subtle|ghost", onClick: string }] } Notes: actions.onClick should be a string action; do not embed JS functions.

  1. Dropdown componentName: "Dropdown" componentProps:

    • button: { label: string }
    • options: [{ label: string, onClick: string, icon?: { name: string } }] Notes: if you need an icon, prefer a FeatherIcon-like shape: { name: "edit-2" }.
  2. Divider componentName: "Divider" componentProps: {} (no required props)

DATA DISPLAY & LISTS 33) Card componentName: "Card" componentProps: - title: string - subtitle: string

  1. ListView componentName: "ListView" componentProps:

    • columns: [{ label, key, width? }] // avoid functions in JSON; keep to data config
    • rows: array of objects
    • rowKey: string (unique key) Notes: If your runtime supports formatter/prefix functions, replace with simple keys or reference strings (no inline JS fns).
  2. Tabs componentName: "Tabs" componentProps:

    • as: "div" (or tag)
    • tabs: [{ label: string, content: string }]
  3. TabButtons componentName: "TabButtons" componentProps:

    • buttons: [{ label, value }]

TREE & STRUCTURED 37) Tree componentName: "Tree" componentProps: - options: { showIndentationGuides?: boolean, rowHeight?: string, indentWidth?: string } - nodeKey: string (e.g., "name") - node: { name, label, children: Node[] }

TEXT / MARKUP EDITORS 38) TextEditor componentName: "TextEditor" componentProps: - content: string (default: "Type something...") - editorClass: string (Tailwind classes) - editable: boolean - fixedMenu: boolean - bubbleMenu: boolean

  1. MarkdownEditor componentName: "MarkdownEditor" componentProps:
    • modelValue: string (markdown content)

MEDIA 40) Audio componentName: "Audio" componentProps: - file: URL

  1. AvatarCard componentName: "AvatarCard" componentProps:

    • title: string
    • subtitle: string
    • imageURL: string
  2. CardList componentName: "CardList" componentProps:

    • title: string
    • cards: [{ title, subtitle, imageURL }]

CHARTS 43) NumberChart componentName: "NumberChart" componentProps: - config: { title: string, value: number, prefix?: string, delta?: number, deltaSuffix?: string, negativeIsBetter?: boolean }

  1. AxisChart componentName: "AxisChart" componentProps:

    • config: { data: object[], title: string, subtitle?: string, xAxis: { key: string, type: "time|category|value", title?: string, timeGrain?: string }, yAxis: { title?: string, echartOptions?: object }, series: [{ name: string, type: "bar|line|area|…" }] }
  2. DonutChart componentName: "DonutChart" componentProps:

    • config: { data: object[], title: string, subtitle?: string, categoryColumn: string, valueColumn: string }

STUDIO-SPECIFIC 46) Repeater componentName: "Repeater" Purpose: repeats child template over a bound array. componentProps: - itemsBind?: string (path to array) OR items?: array Notes: place the template as the sole child of Repeater; runtime will iterate.

  1. SplitView (already above; reiterated) componentSlots: "left","right" (use componentSlots, not children)

Notes on complex props • Where the original component allows JS functions in config (e.g., ListView column formatters, Dialog action handlers, Dropdown icons as render functions), use simple data fields or string references instead of inline JS.
• If you must reference a function, supply its name as a string (e.g., "formatName(row)") and let runtime resolve it.

— Picsum Image Rules for Dummy Image Generation (quick cheat-sheet) Random by size: https://picsum.photos/200/300
Square: https://picsum.photos/200
Specific ID: https://picsum.photos/id/237/200/300
Seeded (stable): https://picsum.photos/seed/myseed/240/160
Grayscale: https://picsum.photos/200/300?grayscale
Blur (1–10): https://picsum.photos/200/300?blur=3
Combine: https://picsum.photos/id/870/200/300?grayscale&blur=2
Avoid caching multiples: ?random=1, ?random=2
.jpg or .webp endings allowed.

— Tailwind Classes Allowed in classes array (e.g., "font-sans", "antialiased", "tracking-tight", "object-cover", "w-full", "h-full"). These complement CSS in baseStyles; do not replace the required fixed heights.

— Responsiveness If needed, add tabletStyles and mobileStyles. Otherwise keep them {}. Never omit baseStyles.

— Events & Visibility componentEvents values are strings (e.g., "navigate('/shop')", "resources.orders.reload()").
visibilityCondition is a string expression; if omitted, node is visible.

— Validation Checklist [ ] Root exists and matches spec (componentId:"root", originalElement:"body").
[ ] All nodes include all required keys (even if empty).
[ ] All dimensional CSS include units.
[ ] No duplicate componentId.
[ ] No mixing grid+flex on the same node.
[ ] Sections/cards have visible height or padding.
[ ] Images have explicit width/height and/or object-cover classes.
[ ] Output is a JSON array only.

— Example Minimal Root (keep keys; edit styles as needed) [ { "componentId": "root", "componentName": "div", "componentProps": {}, "componentSlots": {}, "componentEvents": {}, "blockName": "div", "children": [], "baseStyles": { "display": "flex", "flexDirection": "column", "width": "inherit", "height": "1000px", "overflowX": "hidden", "backgroundColor": "#ffffff" }, "rawStyles": {}, "mobileStyles": {}, "tabletStyles": {}, "originalElement": "body", "classes": [] } ]

— WHAT TO GENERATE NOW Using the user brief below, generate a complete boutique website (site title, hero, story/about, collection grid with images, CTA, footer). Use pastel palette, grid/flex, Tailwind classes where helpful, and fixed heights for all sections. Prefer seeded Picsum URLs for stable images.

Ask the user to fill these placeholders for getting brief, ask 3-4 question in one go: • Brand / Site Title: {{SITE_TITLE}} • Tagline / Hero Headline: {{HERO_TITLE}} • Hero Description: {{HERO_DESC}} • About/Story Title & Copy: {{STORY_TITLE}} / {{STORY_COPY}} • Collection Section Title: {{COLLECTION_TITLE}} • 3–6 Product Cards: {{PRODUCT_LABELS (comma-separated)}} • CTA Title & Copy: {{CTA_TITLE}} / {{CTA_COPY}} • Primary Button Label & Target: {{BUTTON_LABEL}} / {{BUTTON_ROUTE}} • Fixed Section Heights (px): {{HEADER_H}}, {{HERO_H}}, {{STORY_H}}, {{COLLECTION_H}}, {{CTA_H}}, {{FOOTER_H}} • Seed names for images (one per product + hero + story): {{IMAGE_SEEDS (comma-separated)}}

RESPONSE RULES • Return the final Blocks JSON array only. Do not include any prose. • Ensure every node includes all schema keys. • Ensure sections have fixed heights from the brief. • Use Picsum seeds from the brief for ImageView image URLs. • Use clear blockName values (e.g., "Hero", "Story Copy", "Card 1"). • Use reasonable padding, borders, and background colors for corporate pastel style.

END OF SYSTEM / INSTRUCTIONS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment