Last updated

Themes System Architecture

Table of Contents


  1. Overview
  2. Core Concepts
  3. System Architecture
  4. Request Flow Architecture
  5. Understanding Schema Components
  6. Implementation Details
  7. Template Development Patterns
  8. Reserved Section IDs
  9. Theme Customization Architecture
  10. Company Theme Switching
  11. API Design and Usage
  12. Error Handling
  13. Model Usage Examples
  14. Use Cases
  15. Benefits

Overview

The Themes System provides a flexible, multi-layered architecture for customizing the appearance and behavior of pages across the platform. It separates theme management from template content, enabling companies to customize their user-facing pages through a combination of theme selection, template customization, and variable configuration. The system supports both traditional Liquid templating and modern JSON-based section components.

Core Concepts

Themes vs Application Themes

The system maintains two distinct theme models representing different generations of theming functionality:

Legacy Theme Model (Theme):

  • Simple theme selection system with predefined options
  • Used for basic theme switching in older parts of the system
  • Contains hardcoded theme names like fashion, cosmetics, minimalistic, etc.
  • Primarily used by UserCompany associations for individual user theme preferences

Modern Application Theme Model (ApplicationTheme):

  • Full-featured theme system with template management, versioning, and customization
  • Supports company-specific theme customization and template overrides
  • Includes CSS variable injection, custom stylesheets, and rich media support
  • Primary system for all current theme functionality

Application Themes

Application Themes represent complete theme packages that define the visual appearance and layout structure for a company's pages. Each theme can exist as either a root theme (shared across the platform) or a company-specific theme (customized for individual companies).

Key characteristics:

  • Themes contain collections of templates for different page types
  • Support CSS variable injection for dynamic styling
  • Include versioning and publishing workflows
  • Can be cloned and customized per company
  • Maintain audit trails for change tracking

Theme Hierarchy:

  • Root Themes: Platform-provided base themes (fluid, vox, base) that serve as templates
  • Company Themes: Company-specific themes that clone and customize root themes
  • Template Inheritance: Company themes can override specific templates while inheriting others

Root Theme Definition: A theme becomes a "root theme" when it has company_id: nil in the database. Root themes are platform-wide resources that:

  • Are created and maintained by the platform administrators
  • Serve as the foundation for all company-specific theme customization
  • Cannot be directly modified by individual companies
  • Provide the base templates and styling that companies clone and customize
  • Are accessible to all companies for cloning purposes
  • Include complete sets of templates for all major page types (product, home_page, etc.)

Application Theme Templates

Application Theme Templates are individual page templates within a theme that define the layout and content structure for specific page types. Each template can render different types of content and supports both Liquid and JSON formats.

Key characteristics:

  • Templates belong to specific themes and handle particular content types
  • Support both Liquid templating and JSON-based section systems
  • Include schema definitions for dynamic content configuration
  • Can be assigned to specific content items (products, media, pages)
  • Maintain status tracking (draft/active) and versioning
  • Support template-specific variables via variables.json for customizable data unique to each template

Template Types:

  • Content Templates: product, medium, enrollment_pack, library, collection - Render specific content items
  • Page Templates: home_page, shop_page, cart_page, category_page, collection_page, join_page - Handle page types
  • Layout Components: navbar, footer, layouts - Provide page structure and navigation
  • Structural Components: sections, components, config - Reusable template parts and configuration
  • Localization Templates: locales - Contain language-specific JSON files that define translatable text and labels used across templates.

Template Processing System

The system processes templates through a sophisticated pipeline that combines content resolution, variable injection, and rendering:

Liquid Template Processing:

  • Traditional server-side templating using Liquid syntax
  • Variables injected as {{ variable_name }} expressions
  • Control flow through {'%' tag '%'} blocks
  • Support for layouts, includes, and custom filters

Custom Liquid Filters: The system provides specialized filters for common theming tasks:

  • {{ product.price | money }} - Formats currency values with proper symbols and localization
  • {{ image.url | img_url: '300x200' }} - Resizes images to specified dimensions via ImageKit
  • {{ 'common.add_to_cart' | t }} - Translates text based on current locale
  • {{ product.created_at | date: '%B %d, %Y' }} - Formats dates with custom patterns
  • {{ content | truncate: 150 }} - Truncates text to specified character length
  • {{ product.tags | join: ', ' }} - Joins array elements with specified separator

JSON Section Processing: JSON templates represent a modern, component-based approach that separates content structure from presentation logic:

  • Schema-Driven Configuration: Templates define sections with JSON schemas that specify three key components:
    • Settings: Individual configuration options that control section appearance and behavior (colors, text, toggles, etc.)
    • Blocks: Repeatable content elements within a section that can be added, removed, and reordered dynamically
    • Presets: Pre-configured combinations of settings and blocks that provide starting templates for common use cases
  • Component-Based Architecture: Each section type corresponds to a reusable Liquid component template
  • Dynamic Content Blocks: Sections can contain configurable blocks that are rendered with context-specific data
  • Visual Page Builder Integration: JSON structure enables drag-and-drop page building interfaces
  • Settings Management: Schema definitions automatically generate form interfaces for template customization

JSON Template Structure:

{
  "layout": "theme",
  "sections": {
    "header": {
      "type": "product_header",
      "settings": {
        "show_vendor": true,
        "show_price": true,
        "title_size": "large"
      }
    },
    "gallery": {
      "type": "product_gallery",
      "settings": {
        "gallery_type": "slideshow",
        "show_thumbnails": true
      },
      "blocks": {
        "block_1": {
          "type": "image_slide",
          "settings": {
            "image": "{{ product.featured_image }}",
            "caption": "Main product image"
          }
        }
      },
      "block_order": ["block_1"]
    }
  },
  "order": ["header", "gallery"]
}

Section Processing Flow:

  1. Section Resolution: Each section type maps to a Liquid template in the sections themeable_type
  2. Settings Injection: Section settings are merged with schema defaults and injected as variables
  3. Block Processing: Dynamic blocks are processed with their individual settings and rendered within sections
  4. Schema Validation: Settings are validated against the section's schema definition
  5. Template Rendering: The resolved section template is rendered with populated variables

Understanding Schema Components: Settings, Blocks, and Presets

Settings: Individual Configuration Options Settings are individual form fields that control specific aspects of a section's appearance or behavior. Each setting has a type, default value, and user-facing label.

{
  "name": "Product Gallery",
  "settings": [
    {
      "type": "header",
      "id": "content_header",
      "label": "Content Settings"
    },
    {
      "type": "text",
      "id": "heading_text",
      "label": "Section heading",
      "default": "Product Images"
    },
    {
      "type": "textarea",
      "id": "description",
      "label": "Gallery Description",
      "default": "Browse our product images"
    },
    {
      "type": "header",
      "id": "layout_header",
      "label": "Layout Settings"
    },
    {
      "type": "select",
      "id": "gallery_layout",
      "label": "Gallery Layout",
      "options": [
        { "value": "grid", "label": "Grid" },
        { "value": "carousel", "label": "Carousel" },
        { "value": "stacked", "label": "Stacked" }
      ],
      "default": "grid"
    },
    {
      "type": "range",
      "id": "columns_count",
      "label": "Columns per row",
      "min": 2,
      "max": 6,
      "step": 1,
      "default": 4
    },
    {
      "type": "checkbox",
      "id": "show_thumbnails",
      "label": "Show thumbnail navigation",
      "default": true
    },
    {
      "type": "header",
      "id": "style_header",
      "label": "Style Settings"
    },
    {
      "type": "color_background",
      "id": "background_color",
      "label": "Background color",
      "default": "#ffffff"
    },
    {
      "type": "color",
      "id": "border_color",
      "label": "Border color",
      "default": "#e0e0e0"
    },
    {
      "type": "text_alignment",
      "id": "text_align",
      "label": "Text alignment",
      "default": "left"
    },
    {
      "type": "header",
      "id": "content_selection_header",
      "label": "Content Selection"
    },
    {
      "type": "product",
      "id": "featured_product",
      "label": "Featured Product",
      "default": null
    },
    {
      "type": "product_list",
      "id": "related_products",
      "label": "Related Products",
      "default": [],
      "limit": 10
    },
    {
      "type": "image_picker",
      "id": "banner_image",
      "label": "Banner Image",
      "default": ""
    },
    {
      "type": "link_list",
      "id": "navigation_links",
      "label": "Navigation Menu",
      "default": []
    }
  ]
}

Available Setting Types:

Settings control how your sections look and behave. Each setting type provides a different interface and stores data in a specific format.

Text & Content:

  • text: Single-line text input for short text (e.g., headings, labels)
  • textarea: Rich text editor with formatting options (bold, italic, links, etc.)
  • html: Raw HTML code editor for custom HTML

Layout & Style:

  • select: Dropdown menu to choose from predefined options
  • radio: Radio button tabs for single-choice selection
  • checkbox: Toggle switch for yes/no options
  • range: Slider control for numeric values with min/max limits
  • text_alignment: Quick picker for text alignment (left, center, right, justify)
  • color_background: Color picker that supports both solid colors and gradients
    • Important: When using gradients, use the CSS background property instead of background-color

Media & Assets:

  • image_picker: Choose images from your media library
  • video_picker: Select videos from your media library
  • font_picker: Choose fonts with live preview

Navigation & Links:

  • url: URL input field with validation
  • link_list: Manage a list of navigation links

Single Resource Selectors:

  • product: Select one product
  • collection: Select one collection
  • posts: Select one blog post
  • category: Select one category
  • enrollment_pack: Select one enrollment pack

Multiple Resource Selectors:

  • product_list, products_list: Select multiple products
  • collections_list: Select multiple collections
  • posts_list: Select multiple blog posts
  • categories: Select multiple categories
  • enrollment_list: Select multiple enrollments

Organization:

  • header: Creates collapsible sections to organize your settings (doesn't store data)

Important Things to Know:

  1. Using Color Gradients: The color_background setting can hold both solid colors and gradients. When you use gradients in your template, make sure to use background instead of background-color:

    <!-- ✅ Works with both solid colors and gradients -->
    <div style="background: {{ section.settings.background_color }}">
    
    <!-- ❌ Only works with solid colors, gradients won't show -->
    <div style="background-color: {{ section.settings.background_color }}">
    
  2. Always Provide Default Values: Every setting needs a default value that matches what type of data it stores:

    • Text settings: Use "" (empty) or a sample text like "Enter text here"
    • Number settings: Use 0 or a reasonable starting number
    • Toggle switches: Use true or false
    • Single resource selectors: Use null or a resource ID number
    • Multiple resource selectors: Use [] (empty array) or an array with resource IDs
    • Link lists: Use [] (empty array) or an array with sample URLs

What Data Does Each Setting Store?

Understanding what data format each setting uses helps you work with the values in your templates:

Setting TypeStoresExample
Text inputs (text, textarea, html)Plain text or HTML"Welcome" or "<p>Hello</p>"
Dropdowns (select, radio)Selected option value"center" or "grid"
Alignment picker (text_alignment)Alignment value"left", "center", "right", or "justify"
Font picker (font_picker)Font name"Arial"
URL input (url)Web address"https://example.com"
Toggle switch (checkbox)True or falsetrue or false
Slider (range)Number24
Color picker (color_background)Color code or gradient"#ff0000" or "linear-gradient(...)"
Link list (link_list)Array of URLs["/home", "/products"]
Single resource selectors (product, collection, etc.)Resource ID (as number)123
Multiple resource selectors (product_list, collections_list, etc.)Array of resource IDs (as numbers)[123, 456, 789]
Image/video pickers (image_picker, video_picker)File URL"https://cdn.example.com/image.jpg"
Section headers (header)Nothing - just for organization-

Blocks: Repeatable Content Elements

Blocks are dynamic content items that can be added, removed, and reordered within a section. Each block has its own settings and can represent different content types.

{
  "name": "Testimonials Section",
  "settings": [
    {
      "type": "text",
      "id": "section_title",
      "label": "Section Title",
      "default": "What Our Customers Say"
    },
    {
      "type": "textarea",
      "id": "section_subtitle",
      "label": "Section Subtitle",
      "default": "<p>Hear from our satisfied customers</p>"
    },
    {
      "type": "color_background",
      "id": "section_background",
      "label": "Section Background",
      "default": "#f8f9fa"
    },
    {
      "type": "text_alignment",
      "id": "content_alignment",
      "label": "Content Alignment",
      "default": "center"
    }
  ],
  "blocks": [
    {
      "type": "testimonial",
      "name": "Customer Testimonial",
      "limit": 10,
      "settings": [
        {
          "type": "text",
          "id": "customer_name",
          "label": "Customer Name",
          "default": ""
        },
        {
          "type": "textarea",
          "id": "testimonial_text",
          "label": "Testimonial",
          "default": ""
        },
        {
          "type": "image_picker",
          "id": "customer_photo",
          "label": "Customer Photo",
          "default": ""
        },
        {
          "type": "text",
          "id": "customer_title",
          "label": "Customer Title/Company",
          "default": ""
        },
        {
          "type": "range",
          "id": "star_rating",
          "label": "Star Rating",
          "min": 1,
          "max": 5,
          "step": 1,
          "default": 5
        },
        {
          "type": "checkbox",
          "id": "show_photo",
          "label": "Show Customer Photo",
          "default": true
        },
        {
          "type": "url",
          "id": "company_link",
          "label": "Company Website",
          "default": ""
        }
      ]
    },
    {
      "type": "video_testimonial",
      "name": "Video Testimonial",
      "limit": 3,
      "settings": [
        {
          "type": "text",
          "id": "customer_name",
          "label": "Customer Name",
          "default": ""
        },
        {
          "type": "video_picker",
          "id": "testimonial_video",
          "label": "Testimonial Video",
          "default": ""
        },
        {
          "type": "checkbox",
          "id": "autoplay",
          "label": "Autoplay Video",
          "default": false
        }
      ]
    },
    {
      "type": "divider",
      "name": "Section Divider",
      "settings": [
        {
          "type": "select",
          "id": "divider_style",
          "label": "Divider Style",
          "options": [
            { "value": "line", "label": "Line" },
            { "value": "dots", "label": "Dots" },
            { "value": "stars", "label": "Stars" }
          ],
          "default": "line"
        },
        {
          "type": "color_background",
          "id": "divider_color",
          "label": "Divider Color",
          "default": "#e0e0e0"
        }
      ]
    }
  ]
}

Block Characteristics:

  • Dynamic Quantity: Users can add/remove blocks as needed
  • Reorderable: Blocks can be dragged to change their sequence
  • Type-Specific: Different block types have different settings schemas
  • Limited: Optional limit property restricts maximum number of blocks

Presets: Pre-Configured Starting Points Presets provide ready-to-use configurations that combine section settings with a predefined set of blocks, giving users a starting point for common use cases.

{
  "name": "Feature Showcase",
  "settings": [
    {
      "type": "header",
      "id": "content_header",
      "label": "Content"
    },
    {
      "type": "text",
      "id": "section_heading",
      "label": "Section Heading",
      "default": "Key Features"
    },
    {
      "type": "textarea",
      "id": "section_description",
      "label": "Section Description",
      "default": ""
    },
    {
      "type": "header",
      "id": "layout_header",
      "label": "Layout"
    },
    {
      "type": "select",
      "id": "layout_style",
      "label": "Layout Style",
      "options": [
        { "value": "grid", "label": "Grid" },
        { "value": "list", "label": "List" },
        { "value": "carousel", "label": "Carousel" }
      ],
      "default": "grid"
    },
    {
      "type": "range",
      "id": "columns_desktop",
      "label": "Columns (Desktop)",
      "min": 2,
      "max": 4,
      "step": 1,
      "default": 3
    },
    {
      "type": "header",
      "id": "style_header",
      "label": "Style"
    },
    {
      "type": "color_background",
      "id": "background_color",
      "label": "Background Color",
      "default": "#ffffff"
    },
    {
      "type": "text_alignment",
      "id": "content_alignment",
      "label": "Content Alignment",
      "default": "center"
    }
  ],
  "blocks": [
    {
      "type": "feature_item",
      "name": "Feature",
      "settings": [
        {
          "type": "text",
          "id": "feature_title",
          "label": "Feature Title",
          "default": ""
        },
        {
          "type": "textarea",
          "id": "feature_description",
          "label": "Feature Description",
          "default": ""
        },
        {
          "type": "image_picker",
          "id": "feature_icon",
          "label": "Feature Icon",
          "default": ""
        },
        {
          "type": "url",
          "id": "feature_link",
          "label": "Link URL",
          "default": ""
        },
        {
          "type": "checkbox",
          "id": "show_icon",
          "label": "Show Icon",
          "default": true
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "Basic Features (3 items)",
      "settings": {
        "section_heading": "Why Choose Us"
      },
      "blocks": [
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Fast Delivery",
            "feature_description": "Get your order in 24 hours or less"
          }
        },
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Quality Guarantee",
            "feature_description": "100% satisfaction or your money back"
          }
        },
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Expert Support",
            "feature_description": "24/7 customer service from our team"
          }
        }
      ]
    },
    {
      "name": "Product Benefits (5 items)",
      "settings": {
        "section_heading": "Product Benefits"
      },
      "blocks": [
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Eco-Friendly",
            "feature_description": "Made from sustainable materials"
          }
        },
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Durable Design",
            "feature_description": "Built to last for years of use"
          }
        },
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Easy to Use",
            "feature_description": "Simple setup in under 5 minutes"
          }
        },
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Versatile",
            "feature_description": "Works in multiple environments"
          }
        },
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Cost Effective",
            "feature_description": "Saves money compared to alternatives"
          }
        }
      ]
    }
  ]
}

Preset Benefits:

  • Quick Setup: Users can start with pre-configured content instead of building from scratch
  • Best Practices: Presets demonstrate optimal configurations for common scenarios
  • Inspiration: Show users what's possible with the section
  • Consistency: Ensure common use cases follow established patterns

How They Work Together in Practice:

  1. User Selects Preset: When adding a section, user chooses "Product Benefits (5 items)" preset
  2. System Applies Configuration: Section gets the preset's settings and 5 pre-configured feature blocks
  3. User Customizes Settings: User modifies section heading, colors, layout options through the settings
  4. User Manages Blocks: User can edit individual features, reorder them, add more, or remove some
  5. Final Rendering: Section template renders with the customized settings and blocks

This three-tier system (settings + blocks + presets) provides maximum flexibility while maintaining usability for non-technical users.

User Experience Flow
Preset Components
Block Components
Settings Components
JSON Schema Structure
generates forms for
populates
populates
Select Preset
User adds section
Apply preset settings & blocks
Customize Settings
Add/Remove/Reorder Blocks
Final Rendering
Preset 1: Basic Setup
Default Settings Values
Pre-configured Blocks
Preset 2: Advanced Setup
Default Settings Values
Pre-configured Blocks
Block Type Definition
Block Settings
Block Limit
Another Block Type
Block Settings
Text Input
Select Dropdown
Color Picker
Range Slider
Checkbox Toggle
Section Schema
Settings Array
Blocks Array
Presets Array

Layout Resolution:

  • Templates can specify layout wrappers using {'%' layout 'theme' '%'} tags
  • Layouts are separate templates with themeable_type: "layouts"
  • Content injection through {{ content_for_layout }} variable
  • Supports nested layouts and template inheritance

Variable System

The themes system includes a comprehensive variable injection system that populates templates with dynamic content:

Variable Categories:

  • Global Variables: Company information, site settings, localization data
  • Content Variables: Product details, page content, media information
  • Request Variables: URL paths, query parameters, user session data
  • Theme Variables: CSS custom properties, color schemes, typography settings

Variable Processing:

  • Variables are resolved by specialized service classes in app/services/themes/templates/variables/
  • Different content types have dedicated variable providers
  • Caching mechanisms prevent N+1 queries and improve performance
  • Variables support both simple values and complex objects (drops)

Theme Architecture

A theme is distributed as a zip file containing various template files organized in a specific folder structure. Here's a detailed breakdown of the theme architecture:

Layout File

The layout file (theme.liquid) serves as the foundation of your theme and is located in the layout folder. This file contains:

  • Common elements (headers, footers)
  • Shared JavaScript files
  • Global CSS styles
  • Other repeated theme elements

Layout Folder Structure

theme/
├── layout/
│   ├── theme.liquid
├── ..
:

content_for_layout variables

Dynamically returns content based on the current template.

Include {{ content_for_layout }} in your layout files between the <body> and </body> HTML tags.

content_for_header variables

Returns the content of 'app/views/layouts/_theme_header.html.slim' which includes all metadata, scripts, global variables, and other required data by Fluid.

Include {{ content_for_header }} in your layout files between the <head> and </head> HTML tags.

Important: You must include the {{ content_for_header }} and {{ content_for_layout }} in your layout files for the theme to render correctly.

Minimal Layout Example

<!DOCTYPE html>
<html>
  <head>
    {{ content_for_header }}
  </head>
  <body>
    {{ content_for_layout }}
  </body>
</html>

Changing layouts

To use a different layout from the default theme.liquid, add the {'%' layout "custom_layout" '%'} tag at on your template file:

{'%'​ layout "custom_layout" ​'%'}

<h1>Welcome to the Dashboard</h1>

Disabling layout

To render a template without any layout, use {'%' layout none '%'} on your template file:

{'%'​ layout none ​'%'}

<!DOCTYPE html>

<html>

<head>

{{​ content_for_header ​}}

</head>

<body>

<h1>Welcome to the Dashboard</h1>

</body>

</html>

You will still have access to content_for_header variable which includes all necessary metadata, scripts and stylesheets required to run the page smoothly.

Template Files

Template files are organized in page-specific folders. Each page type has its own dedicated folder.

Template Example

theme/
:
├── product/
│   ├── default/
│   │   ├── index.liquid
│   │   └── styles.css
│   └── new_temp/
│       ├── index.json
│       └── styles.css
├── shop_page/
├── medium/
└── ...
:

Navbar and Footer templates are located in the navbar and footer folders respectively. They are referenced in the layout as:

{'%' section 'navbar' '%'}

{'%' section 'footer' '%'}

theme/
:
├── navbar/
│   ├── default/
│   │   ├── index.liquid
│   │   └── styles.css
├── footer/
│   ├── default/
│   │   ├── index.liquid
│   │   └── styles.css
└── ...
:

Multiple Templates

  • Each page type can have multiple templates
  • Example for product page:
    • product/default/
    • product/new_temp/
  • Only one template can be published at a time
  • A default template is always included

Template Structure

Each template consists of two main files:

  1. Main template file (one of the following):
    • index.liquid
    • index.json
  2. Styles file:
    • styles.css

Note: Templates can be created using either Liquid or JSON format. Detailed information about these formats will be covered in later sections.

Asset Files

Theme assets are stored in the theme's assets folder. These files are specific to the theme and support its functionality and appearance.

Asset Folder Structure

theme/
├── assets/
│   ├── custom.js
│   ├── banner.jpg
│   ├──logo.png
│   └──...

Note: For files that need to be accessed across multiple themes, you can use global files stored in Sidenav > Website > Files. Global files will be discussed in detail in later sections.

File Types

Assets can be categorized into two types:

  1. Binary Files

    • Can only be uploaded, not edited
    • Examples: images, fonts, videos
    • Upload only through file selection
  2. Non-Binary Files

    • Can be uploaded or created from scratch
    • Can be edited after creation
    • Examples:
      • CSS files
      • JavaScript files
      • JSON files
    • Creation options:
      • Upload existing file
      • Create blank file with proper extension

Referencing Assets

All files uploaded to the assets folder are automatically processed through Filestack. A CDN URL is generated for each file.These URLs can be referenced throughout your theme

To reference assets in your theme files, use the asset_url filter:

{{ 'filename.ext' | asset_url }}

Example

<link rel="stylesheet" href="{{ 'custom.css' | asset_url }}">
<script src="{{ 'custom.js' | asset_url }}"></script>
<img src="{{ 'banner.jpg' | asset_url }}" alt="Banner">

Reference: https://shopify.dev/docs/api/liquid/filters/asset_url

Components

Components are reusable UI elements that can be used across multiple pages. They are located in the components folder.

Component Folder Structure

theme/
├── components/
│   ├── header/
│   │   ├── index.liquid
│   │   └── styles.css

Component Example

{'%' render 'header' '%'}

You can pass variables to the component as follows:

{'%' render 'header', variable: 'value' '%'}

System Architecture

Theme-Company Relationships

Companies can have multiple themes but only one active theme at a time:

CompanyintidPKstringnamestringsubdomainApplicationThemeintidPKintcompany_idFKNULL for root themesstringnametextvariablesJSON CSS variablestextcustom_stylesheettextglobal_stylesheetstringversionintstatus0=draft, 1=activeApplicationThemeTemplateintidPKintcompany_idFKintapplication_theme_idFKintparent_idFKfor nested templatesstringnameintthemeable_typeenum: product, medium, page, etctextcontentLiquid or JSON templatestringformatliquid or jsonintstatus0=draft, 1=activebooleandefaultdefault for themeable_typeImageFileResourceFileReferenceProductMediumLibraryEnrollmentPackPageThemeUserCompanyhas many themeshas one current_themehas template overridescontains templateshas cover imageshas assetshas nested templates (parent_id)references fileshas imageslinks tocan have specific templatecan have specific templatecan have specific templatecan have specific templatecan have specific templatelegacy belongs toused by

Template Hierarchy and Inheritance

Templates follow a hierarchical resolution system:

Yes
No
Yes
No
Yes
No
Yes
No
Request for Product Page
URL template_id parameter?
Use ApplicationThemeTemplate.find template_id
Product has specific template from current company theme assigned?
Use product.application_theme_template
Company has active theme with default product template?
Use company.current_theme.default_product_template
Root theme has default product template?
Use root_theme.default_product_template
Error: No template found
Render Template

File and Asset Management

Theme assets are managed through the broader file resource system:

ApplicationTheme
├── has_many :images (cover images, theme previews)
├── has_many :file_resources (CSS files, JavaScript, fonts)
└── has_many :ordered_images (positioned theme gallery images)

ApplicationThemeTemplate
├── has_many :images (template preview images)
└── content field contains template code (Liquid/JSON)

Request Flow Architecture

UserRouterControllerThemeableTemplateResolverVariableServicePageBuilderLiquidEngineResponsePriority-based template resolutionalt[Liquid Template][JSON Template]GET /products/123PublicController or SharesControllerinclude Themeable concernset_theme_template_and_variables()theme_template_for(:product, product)Check URL paramsCheck content-specific assignmentCheck company theme defaultsCheck root theme fallbackApplicationThemeTemplatetheme_template_variables_for(template, product)Build global variablesBuild content variablesBuild request variablesVariables hashtemplate + variablesrender_template(template, variables)parse(content, variables)Rendered HTMLprocess_sections(json, variables)Rendered HTMLresolve_layout(template)apply_stylesheets()Final HTMLHTTP ResponseRendered PageUserRouterControllerThemeableTemplateResolverVariableServicePageBuilderLiquidEngineResponse

Step 1: Request Routing and Controller Setup

Request Entry Points:

  • PublicController - Handles company homepage, join pages, privacy policy
  • SharesController - Handles content pages (products, media, libraries, custom pages)

Controller Processing: Both controllers include the Themeable concern which provides theme resolution functionality:

  • set_theme_template_and_variables() - Main orchestration method
  • theme_template_for() - Template resolution logic
  • theme_template_variables_for() - Variable population

Step 2: Theme and Template Resolution

Theme Resolution:

# In Themeable concern
def current_theme_for_company
  @current_theme ||= @company&.current_theme || default_system_theme
end

def theme_template_for(content_type, content_item = nil)
  # 1. Check URL parameters for template override
  return find_template_by_id(params[:template_id]) if params[:template_id]

  # 2. Check content-specific template assignment
  return content_item.application_theme_template if content_item&.application_theme_template

  # 3. Find default template for content type in company's active theme
  ApplicationThemeTemplate.default_for_company_themes(
    company: @company,
    themeable_type: content_type
  ).first
end

Template Selection Logic:

  • Templates are resolved based on themeable_type (product, medium, home_page, etc.)
  • Default templates are marked with default: true within each theme
  • Only active templates are considered for rendering
  • Falls back to root theme defaults if company theme lacks specific templates

Step 3: Variable Population and Context Building

Variable Service Orchestration:

# Variables are created by specialized service classes
def theme_template_variables_for(template, content_item = nil)
  Themes::Templates::Variable.new(
    template: template,
    request: request,
    company: @company,
    record: content_item,
    params: params
  ).call
end

Variable Categories and Sources:

  • Base Variables: URLs, paths, request info, company details
  • Content Variables: Product data, page content, media info (based on content_item)
  • Global Variables: Site settings, localization, social media links
  • Theme Variables: CSS custom properties, layout settings from theme configuration

Step 4: Template Rendering Pipeline

Rendering Orchestration:

# PageBuilder handles the core rendering logic
def render_template(template, variables, layout = nil)
  if template.json?
    render_json_template(template, variables)
  else
    render_liquid_template(template, variables)
  end
end

Liquid Template Processing:

  • Templates processed through LiquidTemplate.parse(template.content, variables)
  • Variables accessible as {{ variable_name }} in templates
  • Support for control flow: {'%' if condition '%'}, {'%' for item in collection '%'}
  • Custom filters available for formatting, localization, and image processing
  • Layout injection via {{ content_for_layout }} if layout specified

JSON Template Processing: JSON templates use a sophisticated section-based rendering system that separates content structure from presentation:

  • Section-by-Section Rendering: The JSON structure defines discrete sections that are processed and rendered individually
  • Schema-Based Configuration: Each section type has a corresponding schema that defines available settings, blocks, and presets
  • Dynamic Block Management: Sections can contain configurable blocks with their own settings and rendering logic
  • Settings Inheritance: Block settings inherit defaults from their section schema and can be overridden per instance
  • Fluid Attributes: Blocks receive special fluid_attributes for frontend editing integration (section IDs, block types, etc.)

Processing Steps:

  1. JSON Parsing: Template content is parsed as JSON structure
  2. Section Iteration: Each section in the sections object is processed according to the order array
  3. Schema Loading: Section schema is loaded from corresponding sections themeable_type templates
  4. Settings Merging: Section settings are merged with schema defaults
  5. Block Processing: Dynamic blocks are processed with individual settings and fluid attributes
  6. Template Resolution: Each section type maps to a Liquid template that renders the final HTML
  7. Variable Injection: Processed settings become available as {{ section.settings.setting_name }} variables

Step 5: Layout Resolution and Content Wrapping

Layout Processing:

def resolve_theme_layout(template)
  return nil unless template.templateable_type?

  layout_name = template.layout_name || 'theme'
  theme = template.theme

  theme&.application_theme_templates&.find_by(
    name: layout_name,
    themeable_type: 'layouts'
  )&.published
end

Layout Integration:

  • Templates can specify layouts via {'%' layout 'theme' '%'} tags or JSON configuration
  • Layout templates wrap content using {{ content_for_layout }} variable
  • Supports nested layouts and template inheritance
  • Layout resolution respects theme hierarchy (company themes inherit root layouts)

Step 6: Stylesheet and Asset Integration

CSS Processing:

# ApplicationTheme methods for stylesheet processing
def global_stylesheet_with_variables
  cache_key = "theme_stylesheet:#{id}:global:#{cache_key_suffix}"
  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    stylesheet_with_variables(global_stylesheet)
  end
end

def stylesheet_with_variables(stylesheet)
  variables = safe_parse_json(self.variables)
  variables.each { |key, value| stylesheet.gsub!("$#{key}", value) }
  stylesheet
end

Asset Integration:

  • Global stylesheets with CSS variable substitution
  • Custom stylesheets for theme-specific overrides
  • File resources (fonts, images, scripts) linked to themes and templates
  • Caching with variable-based cache invalidation

Implementation Details

Data Model Structure

-- ApplicationTheme: Main theme container
CREATE TABLE application_themes (
    id SERIAL PRIMARY KEY,
    company_id INTEGER REFERENCES companies(id), -- NULL for root themes
    name VARCHAR NOT NULL,
    description TEXT,
    variables TEXT, -- JSON string with CSS variables
    custom_stylesheet TEXT,
    global_stylesheet TEXT,
    version VARCHAR DEFAULT '1.0',
    status INTEGER DEFAULT 0, -- 0: draft, 1: active
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Only one active theme per company
CREATE UNIQUE INDEX idx_app_themes_company_active
ON application_themes (company_id)
WHERE status = 1;

-- ApplicationThemeTemplate: Individual templates within themes
CREATE TABLE application_theme_templates (
    id SERIAL PRIMARY KEY,
    company_id INTEGER REFERENCES companies(id),
    application_theme_id INTEGER REFERENCES application_themes(id),
    parent_id INTEGER REFERENCES application_theme_templates(id), -- For nested templates
    name VARCHAR NOT NULL,
    themeable_type INTEGER NOT NULL, -- Enum: product, medium, home_page, etc.
    content TEXT, -- Template code (Liquid or JSON)
    head TEXT, -- HTML head content
    stylesheet TEXT, -- Template-specific CSS
    format VARCHAR DEFAULT 'liquid', -- 'liquid' or 'json'
    status INTEGER DEFAULT 0, -- 0: draft, 1: active
    applicable INTEGER DEFAULT 0, -- 0: everything, 1: specific
    "default" BOOLEAN DEFAULT FALSE, -- Default template for this themeable_type
    fluid BOOLEAN DEFAULT FALSE, -- System template flag
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Index for finding default templates by company and type
CREATE INDEX idx_app_theme_templates_theme_company_type_default
ON application_theme_templates (application_theme_id, company_id, themeable_type, "default")
WHERE status = 1;

-- Legacy Theme model (maintained for backwards compatibility)
CREATE TABLE themes (
    id SERIAL PRIMARY KEY,
    company_id INTEGER REFERENCES companies(id),
    application_theme_template_id INTEGER REFERENCES application_theme_templates(id),
    name VARCHAR NOT NULL,
    public BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

Key Schema Features:

  • Company Isolation: Templates and themes are scoped by company_id
  • Root Theme Support: Root themes have company_id: NULL and are shared across the platform
  • Template Hierarchy: Templates can have parent-child relationships via parent_id
  • Status Management: Draft/active status for both themes and templates
  • Default Template Resolution: Index optimized for finding default templates by type
  • Format Flexibility: Templates can be either Liquid or JSON format

Themeable Types Enumeration

# ApplicationThemeTemplate themeable_type enum
enum :themeable_type, {
  product: 0,           # Individual product pages
  medium: 1,            # Media post pages
  enrollment_pack: 2,   # Course/enrollment pages
  shop_page: 3,         # Shop/catalog pages
  navbar: 4,            # Navigation header
  library: 5,           # Library/playlist pages
  page: 6,              # Custom pages
  components: 7,        # Reusable components
  library_navbar: 8,    # Library-specific navigation
  sections: 9,          # Template sections
  locales: 10,          # Localization templates
  footer: 11,           # Page footer
  layouts: 12,          # Page wrapper layouts
  category_page: 13,    # Category browsing
  collection_page: 14,  # Collection browsing
  cart_page: 15,        # Shopping cart
  config: 16,           # Theme configuration
  home_page: 17,        # Homepage layout
  mysite: 18,           # MySite pages
  join_page: 19         # Registration pages
}

Caching Architecture

Multi-Level Caching Strategy:

  1. Stylesheet Caching (1 hour expiration):

    cache_key = "theme_stylesheet:#{theme_id}:global:#{updated_at.to_i}:#{variables_hash}"
    Rails.cache.fetch(cache_key, expires_in: 1.hour) do
      process_stylesheet_with_variables(stylesheet)
    end
    
  2. Global Embeds Caching (1 hour / 5 seconds in development):

    cache_key = "global_embeds:#{company_id}:#{global_embeds_updated_at}"
    Rails.cache.fetch(cache_key, expires_in: cache_duration) do
      fetch_and_process_global_embeds
    end
    
  3. Rendered Template Caching (24 hours expiration):

      variables_digest = Digest::SHA256.hexdigest(
        JSON.dump(normalize_for_cache(@theme_template_variables.except("content_for_header")))
      )
      cache_key = "page_builder/#{@theme_template.cache_key_with_version}/#{variables_digest}"
    
      Rails.cache.delete(cache_key) if params[:clear_cache].present?
    
      if params[:clear_all_cache].present?
        Rails.cache.delete_matched("page_builder/#{@theme_template.cache_key_with_version}/*")
      end
    
      Rails.cache.fetch(cache_key, expires_in: 24.hours) do
        PageBuilder.render_template(@theme_template, variables: @theme_template_variables)
      end
    
  4. Variable Optimization:

    • Language and country data memoized to prevent N+1 queries
    • Base URL resolution cached per request
    • Template resolution cached within request scope

Cache Invalidation:

  • Stylesheet cache invalidated when theme variables or CSS content changes
  • Global embeds cache invalidated when company embed settings change
  • Rendered template cache invalidated when any variable is changed, or template updated
  • Theme template cache invalidated on publish/unpublish actions

Publishing and Versioning System

Publishing Workflow:

# ApplicationTheme publishing
def publish
  update!(status: :active)
  application_theme_templates.each(&:publish) # Publish all templates
end

# ApplicationThemeTemplate publishing
def make_default
  ApplicationRecord.transaction do
    # Set this as the active default template
    active!
    update!(default: true)

    # Deactivate other default templates of same type
    application_theme.application_theme_templates
      .where(themeable_type: themeable_type)
      .where.not(id: id)
      .update_all(default: false)
  end
end

Theme Version Management:

  • Themes use semantic versioning (major.minor format)
  • Minor versions automatically increment on template changes
  • Major versions increment on root themes updates
  • Audit trail maintains complete change history
  • Auto-upgrade system for compatible theme versions

Theme Template Version Management:

Theme Template Versioning

When you make changes to a template, a new version is created.

Screenshot 2025-01-22 at 1 38 48 PM

You can revert to a previous version by:

  • Click on the 'Versions' dropdown on the theme editor.

  • Select the version you want to revert to.

  • Click on 'Preview' to preview the version to make sure it is what you want.

  • Click on 'Publish' to publish the version.

How to compare changes with a previous version

  • Click on the 'Versions' dropdown on the theme editor.

  • Click on Compare icon on the right to compare the changes.

Template Development Patterns

Liquid Template Structure

Basic Template Format:

{'%' comment '%'}
  Product template with layout, filters, and sections
{'%' endcomment '%'}

{'%' layout 'theme' '%'}

<article class="product-page">
  <header class="product-header">
    <h1>{{ product.name }}</h1>
    <div class="product-price">
      {{ product.price | money }}
    </div>
    <div class="product-meta">
      <span class="product-date">{{ product.created_at | date: '%B %d, %Y' }}</span>
      {'%' if product.tags.size > 0 '%'}
        <div class="product-tags">{{ product.tags | join: ', ' }}</div>
      {'%' endif '%'}
    </div>
  </header>

  <div class="product-content">
    <div class="product-images">
      {'%' for image in product.images '%'}
        <img src="{{ image.url | img_url: '400x300' }}"
             alt="{{ image.alt }}" />
      {'%' endfor '%'}
    </div>

    <div class="product-description">
      {{ product.description | truncate: 250 }}
    </div>
  </div>

  {'%' if product.available '%'}
    <div class="product-purchase">
      <button class="btn-purchase" data-product-id="{{ product.id }}">
        {{ 'products.add_to_cart' | t }}
      </button>
    </div>
  {'%' endif '%'}
</article>

Complete JSON Template Example:

{
  "name": "Product Template",
  "layout": "theme", 
  "sections": {
    "product-header": {
      "type": "product_header",
      "settings": {
        "show_vendor": true,
        "show_price": true,
        "title_size": "large",
        "background_color": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
        "text_alignment": "center",
        "padding": 24
      }
    },
    "product-gallery": {
      "type": "product_gallery",
      "settings": {
        "gallery_type": "slideshow",
        "show_thumbnails": true,
        "thumbnail_position": "bottom"
      },
      "blocks": {
        "image_1": {
          "type": "product_image",
          "settings": {
            "image_size": "large",
            "show_zoom": true
          }
        },
        "video_1": {
          "type": "product_video",
          "settings": {
            "video_url": "{{ product.featured_video.url }}",
            "autoplay": false
          }
        }
      },
      "block_order": ["image_1", "video_1"]
    },
    "product-info": {
      "type": "product_information",
      "settings": {
        "show_description": true,
        "show_features": true,
        "description_length": 300
      }
    }
  },
  "order": ["product-header", "product-gallery", "product-info"]
}

Corresponding Section Template (sections/product_header.liquid):

{'%' comment '%'}
  Product Header Section
  Note: Using 'background' CSS property instead of 'background-color' 
  to support both solid colors and gradients from color_background setting
{'%' endcomment '%'}

<div class="product-header"
     style="background: {{ section.settings.background_color }}">
  <div class="product-header-content">
    {'%' if section.settings.show_vendor and product.vendor '%'}
      <div class="product-vendor">{{ product.vendor }}</div>
    {'%' endif '%'}

    <h1 class="product-title product-title--{{ section.settings.title_size }}">
      {{ product.name }}
    </h1>

    {'%' if section.settings.show_price '%'}
      <div class="product-price">
        <span class="price">{{ product.price | money }}</span>
        {'%' if product.compare_at_price '%'}
          <span class="compare-price">{{ product.compare_at_price | money }}</span>
        {'%' endif '%'}
      </div>
    {'%' endif '%'}
  </div>
</div>

{'%' schema '%'}
{
  "name": "Product Header",
  "settings": [
    {
      "type": "header",
      "id": "content_group",
      "label": "Content Settings"
    },
    {
      "type": "checkbox",
      "id": "show_vendor",
      "label": "Show vendor",
      "default": true
    },
    {
      "type": "checkbox",
      "id": "show_price",
      "label": "Show price",
      "default": true
    },
    {
      "type": "header",
      "id": "style_group",
      "label": "Style Settings"
    },
    {
      "type": "select",
      "id": "title_size",
      "label": "Title size",
      "options": [
        { "value": "small", "label": "Small" },
        { "value": "medium", "label": "Medium" },
        { "value": "large", "label": "Large" }
      ],
      "default": "medium"
    },
    {
      "type": "color_background",
      "id": "background_color",
      "label": "Background color",
      "default": "#ffffff"
    },
    {
      "type": "text_alignment",
      "id": "text_alignment",
      "label": "Text alignment",
      "default": "left"
    },
    {
      "type": "range",
      "id": "padding",
      "label": "Section padding",
      "min": 0,
      "max": 100,
      "step": 4,
      "default": 20
    }
  ]
}
{'%' endschema '%'}

Rendered HTML Output Example:

When the above JSON template and section template are processed together, here's what the final HTML output would look like:

<!-- Product template with product-header section -->
<!DOCTYPE html>
<html>
<head>
  <title>Wireless Bluetooth Headphones</title>
  <!-- Theme stylesheets with CSS variables applied -->
  <style>
    :root {
      --primary-color: #2c5aa0;
      --secondary-color: #f8f9fa;
      --font-family: Inter, sans-serif;
      --heading-font: Playfair Display, serif;
    }
    .product-header { background-color: var(--primary-color); }
    .product-title--large { font-size: 2.5rem; font-family: var(--heading-font); }
  </style>
</head>
<body>
  <!-- Layout wrapper from theme.liquid -->
  <div class="theme-container">
    
    <!-- Content from product template sections -->
    
    <!-- product-header section rendered -->
    <!-- Note: Using 'background' property (not background-color) to support gradient -->
    <div class="product-header" 
         style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 
                text-align: center; 
                padding: 24px;">
      <div class="product-header-content">
        <div class="product-vendor">AudioTech</div>
        
        <h1 class="product-title product-title--large">
          Wireless Bluetooth Headphones
        </h1>
        
        <div class="product-price">
          <span class="price">$199.99</span>
          <span class="compare-price">$249.99</span>
        </div>
      </div>
    </div>
    
    <!-- product-gallery section rendered -->
    <div class="product-gallery" data-gallery-type="slideshow">
      <div class="main-gallery">
        <!-- image_1 block rendered -->
        <div class="gallery-item" 
             data-fluid-section-block-id="image_1"
             data-fluid-section-id="product-gallery"
             data-fluid-section-block-type="product_image">
          <img src="https://cdn.example.com/headphones-main-600x400.jpg" 
               alt="Wireless Bluetooth Headphones - Main View"
               class="product-image product-image--large" />
          <div class="image-zoom-overlay" style="display: block;"></div>
        </div>
        
        <!-- video_1 block rendered -->
        <div class="gallery-item"
             data-fluid-section-block-id="video_1" 
             data-fluid-section-id="product-gallery"
             data-fluid-section-block-type="product_video">
          <video controls preload="metadata">
            <source src="https://cdn.example.com/headphones-demo.mp4" type="video/mp4">
            Your browser does not support the video tag.
          </video>
        </div>
      </div>
      
      <div class="thumbnail-navigation" style="position: bottom;">
        <button class="thumbnail-btn active" data-target="image_1"></button>
        <button class="thumbnail-btn" data-target="video_1"></button>
      </div>
    </div>
    
    <!-- product-info section rendered -->
    <div class="product-information">
      <div class="product-description">
        <p>Experience premium sound quality with our latest wireless Bluetooth headphones. 
        Featuring active noise cancellation, 30-hour battery life, and comfortable over-ear design.</p>
      </div>
      
      <div class="feature-highlights">
        <h3>Key Features</h3>
        <ul>
          <li>Active Noise Cancellation</li>
          <li>30-hour battery life</li>
          <li>Quick charge technology</li>
          <li>Premium leather ear cushions</li>
        </ul>
      </div>
    </div>
    
  </div>
  
  <!-- JavaScript for section interactions -->
  <script>
    // Gallery navigation functionality
    document.querySelectorAll('.thumbnail-btn').forEach(btn => {
      btn.addEventListener('click', function() {
        const targetId = this.dataset.target;
        // Switch gallery view logic
      });
    });
  </script>
</body>
</html>

Key Points About the Rendered Output:

  1. CSS Variables Applied: The $primary_color variable from the JSON became #ffffff in the final CSS
  2. Settings Injection: section.settings.title_size became product-title--large CSS class
  3. Block Processing: Each block got data-fluid-* attributes for frontend editing
  4. Layout Wrapping: Content was wrapped in the theme layout structure
  5. Dynamic Content: Product data (product.name, product.price) was injected from variables
  6. Schema Validation: All settings matched their schema definitions (show_vendor: true, etc.)
  7. Type Safety: All default values matched their expected data types (booleans, strings, numbers)

Processing Flow Summary:

JSON Template → Section Resolution → Settings Merge → Block Processing → Liquid Rendering → Layout Application → Final HTML

This demonstrates how the abstract JSON configuration becomes concrete, interactive HTML that users see in their browsers.

Tips for Creating Better Section Schemas

Follow these tips to create sections that are easy for users to customize:

1. Organize Settings into Groups Use header settings to group related options together. This creates collapsible sections that make the editor easier to navigate.

{
  "settings": [
    {
      "type": "header",
      "id": "content_group",
      "label": "Content Settings"
    },
    // ... content-related settings ...
    {
      "type": "header",
      "id": "style_group",
      "label": "Style Settings"
    },
    // ... style-related settings ...
  ]
}

2. Always Set Good Defaults Every setting should have a default value so your section looks good right away. Users can then customize from there instead of starting from scratch.

3. Pick the Right Setting Type Choose the setting type that makes the most sense:

  • Use text_alignment for text alignment (gives a visual picker instead of a dropdown)
  • Use color_background when you want to allow both solid colors and gradients
  • Use textarea when users need formatting options like bold, italic, or links
  • Use range for numbers where there's a clear minimum and maximum value

4. Handle Colors Correctly When using color_background settings (which can be gradients), always use the background CSS property:

<!-- ✅ Correct -->
<div style="background: {{ section.settings.background_color }}">

<!-- ❌ Wrong - gradients won't render -->
<div style="background-color: {{ section.settings.background_color }}">

5. Use the Right Data Format Make sure your default values match what the setting expects:

  • Single resource selectors (like product) use number IDs: 123
  • Multiple resource selectors (like product_list) use number arrays: [123, 456]
  • Link lists use text arrays: ["/home", "/about"]

6. Limit Selection When Needed For settings that let users select multiple items, set a reasonable limit to keep your pages fast:

{
  "type": "product_list",
  "id": "featured_products",
  "label": "Featured Products",
  "default": [],
  "limit": 10
}

7. Write Clear Labels Use simple, descriptive labels that tell users exactly what each setting does. Good labels help users customize without confusion.

Reserved Section IDs

When creating new sections or templates, certain section IDs are reserved by the system and cannot be used for custom sections.

⚠️ Reserved IDs You Cannot Use:

  • main_navbar - Reserved for the main navigation bar
  • main_footer - Reserved for the main footer

Why These IDs Are Reserved:

Reserved sections are special system-level sections that provide critical functionality:

  • main_navbar: The main navigation bar that appears at the top of pages
  • main_footer: The main footer that appears at the bottom of pages

These sections are automatically included in templates and managed by the platform. Attempting to create sections with these IDs will cause conflicts and errors.

What You Can Do With Reserved Sections:

  • ✅ Edit their settings (colors, fonts, spacing, content)
  • ✅ Customize their appearance through the visual editor
  • ✅ Configure their content (text, links, menu items)

What You Cannot Do:

  • ❌ Delete reserved sections from templates
  • ❌ Create new sections with reserved IDs
  • ❌ Clone or duplicate sections with reserved IDs
  • ❌ Rename their IDs

Creating Custom Navigation or Footer Sections:

If you need additional navigation or footer sections, use any unique IDs (except the reserved ones):

{
  "sections": {
    "custom_navbar": {
      "type": "navbar",
      "settings": { }
    },
    "secondary_navbar": {
      "type": "navbar",
      "settings": { }
    },
    "page_footer": {
      "type": "footer",
      "settings": { }
    },
    "custom_footer": {
      "type": "footer",
      "settings": { }
    }
  }
}

Section ID Requirements:

When creating section IDs, ensure they:

  1. Are not main_navbar or main_footer
  2. Are unique within your template

Valid Section ID Examples:

"hero_section"          // ✅ Valid
"product_grid"          // ✅ Valid
"section1"              // ✅ Valid
"MySection"             // ✅ Valid
"custom-nav"            // ✅ Valid

Invalid Section ID Examples:

"main_navbar"     // ❌ Reserved ID
"main_footer"     // ❌ Reserved ID

Troubleshooting ID Conflicts:

If you encounter a "Section ID conflict" error:

  1. Verify your section ID is not main_navbar or main_footer
  2. Check that the ID is unique within your template
  3. Try using a different ID name

Why This Matters:

Using reserved IDs causes several issues:

  • ID Conflicts: The system already uses these IDs, causing database conflicts
  • Unexpected Behavior: The visual editor may not handle duplicate IDs correctly
  • Template Errors: Your template may fail to render or save properly
  • Data Loss: Settings for reserved sections may be overwritten or lost

CSS Variable Integration

Theme Variables Definition:

{
  "primary_color": "#007bff",
  "secondary_color": "#6c757d",
  "font_family": "Helvetica, Arial, sans-serif",
  "heading_font": "'Playfair Display', serif",
  "border_radius": "4px",
  "container_width": "1200px"
}

Stylesheet with Variable Substitution:

/* Global stylesheet with CSS variables */
:root {
  --primary-color: $primary_color;
  --secondary-color: $secondary_color;
  --font-family: $font_family;
  --heading-font: $heading_font;
  --border-radius: $border_radius;
  --container-width: $container_width;
}

.product-page {
  font-family: var(--font-family);
  max-width: var(--container-width);
  margin: 0 auto;
}

.product-header h1 {
  font-family: var(--heading-font);
  color: var(--primary-color);
}

.btn-purchase {
  /* Use background instead of background-color to support gradients */
  background: var(--primary-color);
  border-radius: var(--border-radius);
}

/* Example of section with color_background setting that might be a gradient */
.hero-section {
  /* ✅ Correct - works with both solid colors and gradients */
  background: var(--hero-background);
  
  /* ❌ Wrong - would not render gradients correctly */
  /* background-color: var(--hero-background); */
}

Component and Section System

Section Template (sections/product_gallery.liquid):

{'%' comment '%'}
  Reusable product gallery section
{'%' endcomment '%'}

<div class="product-gallery" data-gallery-type="{{ section.settings.gallery_type }}">
  <div class="main-image">
    <img src="{{ product.featured_image.url }}"
         alt="{{ product.featured_image.alt }}"
         id="main-product-image" />
  </div>

  {'%' if section.settings.show_thumbnails and product.images.size > 1 '%'}
    <div class="thumbnail-list">
      {'%' for image in product.images '%'}
        <img src="{{ image.url | img_url: '100x100' }}" 
             alt="{{ image.alt }}"
             class="thumbnail {'%' if forloop.first '%'}active{'%' endif '%'}"
             data-main-image="{{ image.url }}" />
      {'%' endfor '%'}
    </div>
  {'%' endif '%'}
</div>

{'%' schema '%'}
{
  "name": "Product Gallery",
  "settings": [
    {
      "type": "header",
      "id": "layout_header",
      "label": "Layout Settings"
    },
    {
      "type": "select",
      "id": "gallery_type",
      "label": "Gallery Type",
      "options": [
        { "value": "slideshow", "label": "Slideshow" },
        { "value": "grid", "label": "Grid" },
        { "value": "carousel", "label": "Carousel" }
      ],
      "default": "slideshow"
    },
    {
      "type": "checkbox",
      "id": "show_thumbnails",
      "label": "Show thumbnails",
      "default": true
    },
    {
      "type": "range",
      "id": "images_per_row",
      "label": "Images per row",
      "min": 2,
      "max": 6,
      "step": 1,
      "default": 3
    },
    {
      "type": "header",
      "id": "style_header",
      "label": "Style Settings"
    },
    {
      "type": "color_background",
      "id": "gallery_background",
      "label": "Gallery background",
      "default": "#ffffff"
    },
    {
      "type": "range",
      "id": "image_spacing",
      "label": "Image spacing (px)",
      "min": 0,
      "max": 40,
      "step": 4,
      "default": 8
    }
  ],
  "blocks": [
    {
      "type": "image",
      "name": "Gallery Image",
      "settings": [
        {
          "type": "image_picker",
          "id": "image",
          "label": "Image",
          "default": ""
        },
        {
          "type": "text",
          "id": "caption",
          "label": "Caption",
          "default": ""
        },
        {
          "type": "checkbox",
          "id": "enable_zoom",
          "label": "Enable zoom on hover",
          "default": true
        }
      ]
    }
  ]
}
{'%' endschema '%'}

Theme Customization Architecture

The themes system provides multiple levels of customization, allowing companies to progressively tailor their user experience from high-level branding changes down to individual template modifications. Each level builds upon the previous one, creating a flexible hierarchy that balances ease of use with customization depth.

Customization Hierarchy

[object Object]

Level 1: Theme Selection and Cloning Companies begin customization by selecting and cloning a root theme:

# Find available root themes
root_themes = ApplicationTheme.root.where(name: ['fluid', 'vox', 'base'])

# Clone a root theme for company customization
root_theme = ApplicationTheme.root.find_by(name: 'fluid')
company_theme = root_theme.deep_clone(company.id)
company_theme.name = "Acme Corp Custom Theme"
company_theme.save!

What gets cloned:

  • All application theme templates (product, home_page, sections, etc.)
  • Theme-level settings and variables
  • File resources and assets
  • Template relationships and hierarchy

What remains linked to root:

  • Template inheritance for future updates
  • Version compatibility for auto-upgrades
  • Schema definitions for sections and components

Level 2: CSS Variable Customization The most common customization approach involves modifying CSS variables to change colors, fonts, and layout properties:

# Update theme variables
company_theme.variables = {
  primary_color: '#2c5aa0',      # Brand primary color
  secondary_color: '#f8f9fa',    # Accent color
  font_family: 'Inter, sans-serif',  # Primary font
  heading_font: 'Playfair Display, serif',  # Header font
  border_radius: '8px',          # Rounded corner style
  container_width: '1440px',     # Maximum content width
  spacing_unit: '16px'           # Base spacing measurement
}.to_json

company_theme.save!

Variable Processing: Variables are injected into stylesheets during rendering:

/* Before processing */
.primary-button {
  background-color: $primary_color;
  font-family: $font_family;
  border-radius: $border_radius;
}

/* After processing */
.primary-button {
  background-color: #2c5aa0;
  font-family: Inter, sans-serif;
  border-radius: 8px;
}

Level 3: Custom Stylesheet Overrides For advanced styling needs, companies can add custom CSS that supplements or overrides base theme styles:

company_theme.custom_stylesheet = <<~CSS
  /* Brand-specific component styling */
  .product-card {
    box-shadow: 0 4px 12px rgba(44, 90, 160, 0.15);
    transition: transform 0.2s ease;
  }
  
  .product-card:hover {
    transform: translateY(-4px);
  }
  
  /* Custom responsive breakpoints */
  @media (max-width: 768px) {
    .container {
      padding: 0 12px;
    }
  }
CSS

company_theme.save!

Level 4: Template Customization Companies can override specific templates while inheriting others from the parent theme:

# Create a custom product template
custom_product_template = company_theme.application_theme_templates.create!(
  name: 'Premium Product Layout',
  themeable_type: :product,
  content: custom_liquid_content,
  format: :liquid
)

# Make it the default for all products
custom_product_template.make_default

# Or assign to specific products
premium_product = Product.find(123)
premium_product.application_theme_template = custom_product_template
premium_product.save!

Level 5: Content-Specific Template Assignment The most granular level allows assigning specific templates to individual content items:

# Create a special template for featured products
featured_template = company_theme.application_theme_templates.create!(
  name: 'Featured Product Showcase',
  themeable_type: :product,
  applicable: :specific,  # Only for specifically assigned items
  content: featured_product_liquid,
  format: :liquid
)

# Assign to featured products
featured_products = Product.where(featured: true)
featured_products.update_all(application_theme_template_id: featured_template.id)

Template Resolution Process

Understanding how the system chooses which template to render is crucial for effective customization. The system follows a priority-based hierarchy, checking each level until it finds a suitable template:

Priority 1: URL Parameter Override (Highest Priority)

/products/123?template_id=456
# Uses ApplicationThemeTemplate.find(456) regardless of other settings
# Use case: Testing, previewing, or temporary template switches

Priority 2: Content-Specific Assignment

# Product has specific template assigned
product.application_theme_template_id = 789
# Uses template 789 for this product specifically
# Use case: Featured products, premium content, special campaigns

Priority 3: Company Theme Default

# Find default template for this content type in company's active theme
ApplicationThemeTemplate.default_for_company_themes(
  company: product.company,
  themeable_type: :product
).first
# Use case: Standard company branding applied to all content of this type

Priority 4: Root Theme Fallback (Lowest Priority)

# If company theme lacks this template type, inherit from root theme
root_theme = ApplicationTheme.root.find_by(name: company_theme.name)
root_theme.application_theme_templates.product.find_by(default: true)
# Use case: Ensures pages always render even with incomplete customization

Workflow 1: Basic Company Branding (Most Common) Goal: Apply consistent brand colors, fonts, and basic styling

  1. Clone Base Theme: Select and clone appropriate root theme (fluid, vox, or base)
  2. Configure Variables: Update CSS variables for brand colors, fonts, spacing
  3. Add Custom CSS: Include brand-specific styling that can't be achieved through variables
  4. Test Preview: Use preview URLs to test changes before going live
  5. Publish: Activate the theme for all company pages

Timeline: 1-2 hours | Skill Level: Basic

Workflow 2: Advanced Template Customization (Advanced Users) Goal: Modify page layouts and add custom functionality

  1. Complete Basic Branding: Ensure foundation is solid before template changes
  2. Audit Current Templates: Identify which page types need layout modifications
  3. Create Custom Templates: Build modified versions of key templates (product, home_page, etc.)
  4. Set Template Defaults: Make custom templates the default for their content types
  5. Comprehensive Testing: Test all page types and responsive behavior
  6. Gradual Rollout: Consider phased publishing for major changes

Timeline: 1-2 weeks | Skill Level: Advanced

Workflow 3: Content-Specific Customization (Enterprise) Goal: Different layouts for different content categories or campaigns

  1. Establish Template Foundation: Complete advanced template customization first
  2. Define Content Categories: Identify which content needs special treatment
  3. Create Specialized Templates: Build templates with applicable: :specific setting
  4. Implement Assignment Strategy: Systematically assign templates to appropriate content
  5. Monitor and Optimize: Track performance and user engagement by template type
  6. Maintain Consistency: Ensure specialized templates maintain brand coherence

Timeline: 2-4 weeks | Skill Level: Expert

Theme Inheritance and Upgrade System

How Inheritance Works: Company themes maintain a connection to their root themes, allowing them to receive updates while preserving customizations:

# Check upgrade eligibility
company_theme.auto_upgradeable?
# => true if minor version is 0 (no template modifications)

# Check for available updates
company_theme.outdated?
# => true if root theme has newer version

# Automatic upgrade (safe customizations only)
if company_theme.auto_upgradeable? && company_theme.outdated?
  CompanyThemeUpgradeJob.perform_later(company_theme)
end

Impact of Customization Types on Upgrades:

Customization TypeVersion ImpactAuto-UpgradeNotes
CSS VariablesNone✅ CompatibleVariables preserved during upgrade
Custom StylesheetsNone✅ CompatibleCustom CSS appended to upgraded styles
New TemplatesNone✅ CompatibleCustom templates remain untouched
Modified TemplatesMinor +1❌ Manual ReviewPrevents automatic upgrades
Asset ChangesNone✅ CompatibleCompany assets preserved

Upgrade Strategies:

Automatic Upgrades (Recommended for most companies)

  • Stick to CSS variables and custom stylesheets for brand customization
  • Create new templates rather than modifying existing ones
  • Benefits: Always get latest features and bug fixes automatically

Manual Upgrade Control (For complex customizations)

  • Modify existing templates when layout changes are needed
  • Accept that upgrades require manual review and testing
  • Benefits: Maximum customization flexibility with controlled update process

Asset and File Management

Theme-Level Assets:

# Add theme-specific assets
company_theme.file_resources.create!(
  filename: 'brand-logo.svg',
  url: 'https://cdn.example.com/logo.svg',
  content_type: 'image/svg+xml'
)

# Add cover images for theme preview
company_theme.images.create!(
  image_url: 'https://cdn.example.com/theme-preview.jpg',
  position: 1
)

Template-Level Assets:

# Create template-specific JavaScript
product_template.assets.create!(
  filename: 'product_interactions.js',
  content: javascript_code,
  content_type: 'application/javascript'
)

# Reference in template
# => <script>{{ 'product_interactions.js' | asset_url }}</script>

Testing and Validation

Preview Before Publishing:

# Preview template changes before making them live
preview_url = ApplicationThemeTemplates::TemplateUrls.new(template).preview_url
# => /templates/123/preview?version=draft

Validation Checks:

  • Liquid Syntax: Templates are parsed for syntax errors before saving
  • Schema Validation: JSON templates validated against section schemas
  • Asset References: Broken asset links detected and reported
  • Variable Dependencies: Missing variables identified in stylesheets

Rollback Capabilities:

# Access version history
template.versions
# => [{ version: 1, author: "Jane Doe", published: true, created_at: "..." }]

# Revert to previous version
template.audits.find_by(version: 2).restore
template.publish

Practical Implementation Example

Complete Company Branding Implementation: Here's a real-world example of customizing a theme for "Acme Corp":

# 1. Clone root theme
root_theme = ApplicationTheme.root.find_by(name: 'fluid')
acme_theme = root_theme.deep_clone(company.id)
acme_theme.update!(
  name: "Acme Corp Theme",
  description: "Custom theme for Acme Corp branding"
)

# 2. Configure brand variables
acme_theme.variables = {
  # Brand Colors
  primary_color: '#c8102e',      # Acme red
  secondary_color: '#f5f5f5',    # Light gray
  accent_color: '#1f4e79',       # Navy blue
  
  # Typography
  font_family: 'Inter, sans-serif',
  heading_font: 'Montserrat, sans-serif',
  font_size_base: '16px',
  
  # Layout
  container_width: '1200px',
  border_radius: '6px',
  spacing_unit: '20px'
}.to_json

# 3. Add custom brand styling
acme_theme.custom_stylesheet = <<~CSS
  /* Acme-specific component styling */
  .hero-section {
    background: linear-gradient(135deg, var(--primary-color), var(--accent-color));
  }
  
  .product-card {
    border: 2px solid var(--primary-color);
    box-shadow: 0 4px 8px rgba(200, 16, 46, 0.1);
  }
  
  .btn-primary {
    background: var(--primary-color);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
  }
  
  .navbar {
    border-bottom: 3px solid var(--primary-color);
  }
CSS

# 4. Create custom product template for featured items
featured_template = acme_theme.application_theme_templates.create!(
  name: 'Acme Featured Product',
  themeable_type: :product,
  applicable: :specific,
  format: :liquid,
  content: <<~LIQUID
    {'%' layout 'theme' '%'}
    
    <div class="featured-product-hero">
      <div class="hero-badge">Featured Product</div>
      <h1 class="hero-title">{{ product.name }}</h1>
      <div class="hero-price">{{ product.price | money }}</div>
    </div>
    
    <div class="product-gallery-featured">
      {'%' for image in product.images limit: 5 '%'}
        <img src="{{ image.url | img_url: '600x400' }}" 
             alt="{{ image.alt }}" 
             class="gallery-image" />
      {'%' endfor '%'}
    </div>
    
    <div class="product-description-enhanced">
      {{ product.description }}
      
      {'%' if product.features '%'}
        <div class="feature-highlights">
          <h3>Key Features</h3>
          <ul>
            {'%' for feature in product.features '%'}
              <li>{{ feature }}</li>
            {'%' endfor '%'}
          </ul>
        </div>
      {'%' endif '%'}
    </div>
    
    <div class="cta-section">
      <button class="btn-primary btn-large" data-product-id="{{ product.id }}">
        {{ 'products.add_to_cart' | t }}
      </button>
    </div>
  LIQUID
)

# 5. Assign featured template to premium products
premium_products = Product.where(company: company, featured: true)
premium_products.update_all(application_theme_template_id: featured_template.id)

# 6. Publish theme
acme_theme.publish

Result:

  • All pages use Acme's brand colors and fonts automatically
  • Standard products use the default template with brand styling
  • Featured products get the enhanced layout with special CTAs
  • Theme can receive automatic updates since only safe customizations were used
  • Consistent branding across all customer touchpoints

This approach demonstrates how the multi-layered customization system enables sophisticated branding while maintaining system stability and upgrade compatibility.

API Design and Usage

Theme Management API

Get Company Themes:

GET /api/application_themes

Returns list of all themes available to the company (root + company-specific).

Create Company Theme:

POST /api/application_themes

Creates a new theme for the company. Request body:

{
  "name": "Custom Company Theme",
  "description": "Customized theme for our brand",
  "clone_from_id": 123, // Optional: clone from existing theme
  "variables": {
    "primary_color": "#ff6b6b",
    "secondary_color": "#4ecdc4"
  }
}

Update Theme:

PUT /api/application_themes/{theme_id}

Updates theme properties. Request body:

{
  "name": "Updated Theme Name",
  "variables": {
    "primary_color": "#e74c3c",
    "font_family": "'Open Sans', sans-serif"
  },
  "custom_stylesheet": ".custom-class { margin: 20px; }"
}

Publish Theme:

POST /api/application_themes/{theme_id}/publish

Activates the theme for the company, deactivating the current active theme.

Template Management API

Get Theme Templates:

GET /api/application_themes/{theme_id}/templates

Query parameters:

  • themeable_type: Filter by template type (product, home_page, etc.)
  • status: Filter by status (draft, active)

Create Template:

POST /api/application_themes/{theme_id}/templates

Request body:

{
  "name": "Custom Product Template",
  "themeable_type": "product",
  "content": "<div>{{ product.name }}</div>",
  "format": "liquid",
  "applicable": "specific" // or "everything"
}

Update Template:

PUT /api/application_theme_templates/{template_id}

Request body:

{
  "content": "{'%' layout 'theme' '%'}<h1>{{ product.name }}</h1>",
  "head": "<meta name='description' content='{{ product.description }}' />",
  "stylesheet": ".product-title { color: red; }"
}

Make Template Default:

POST /api/application_theme_templates/{template_id}/make_default

Sets this template as the default for its themeable_type within the theme.

Template Preview API

Preview Template:

GET /api/application_theme_templates/{template_id}/preview

Query parameters:

  • version: Specific version to preview (defaults to current)
  • record_id: ID of content item to preview with (for product/medium templates)

Returns rendered HTML of the template with sample or specified content.

Error Handling

Theme Validation Errors (422 Unprocessable Entity):

{
  "status": "unprocessable_entity",
  "errors": {
    "variables": ["Variables must be valid JSON"],
    "name": ["Name cannot be blank"]
  }
}

Template Validation Errors (422 Unprocessable Entity):

{
  "status": "unprocessable_entity",
  "errors": {
    "content": ["Content cannot be blank"],
    "themeable_type": ["Themeable type is not included in the list"]
  }
}

Theme Not Found (404 Not Found):

{
  "status": "not_found",
  "errors": {
    "base": ["Theme not found"]
  }
}

Active Theme Deletion (422 Unprocessable Entity):

{
  "status": "unprocessable_entity",
  "errors": {
    "base": ["Cannot delete a published theme"]
  }
}

Model Usage Examples

# Find company's active theme
company = Company.find(123)
active_theme = company.current_theme
# => ApplicationTheme with status: :active

# Get all templates for a theme
product_templates = active_theme.application_theme_templates.product
default_product_template = product_templates.find_by(default: true)

# Create a new company theme by cloning a root theme
root_theme = ApplicationTheme.root.find_by(name: 'fluid')
company_theme = root_theme.deep_clone(company.id)
company_theme.name = "Custom Fluid Theme"
company_theme.save!

# Customize theme variables
company_theme.variables = {
  primary_color: '#ff6b6b',
  secondary_color: '#4ecdc4',
  font_family: 'Inter, sans-serif'
}.to_json
company_theme.save!

# Create a custom template
custom_template = company_theme.application_theme_templates.create!(
  name: 'Custom Product Layout',
  themeable_type: :product,
  content: '{'%' layout "theme" '%'}<div class="custom-product">{{ product.name }}</div>',
  format: :liquid
)

# Make it the default template for products
custom_template.make_default

# Assign template to specific product
product = Product.find(456)
product.application_theme_template = custom_template
product.save!

# Publish the theme to make it active
company_theme.publish

# Find template for rendering a product
product = Product.find(456)
template = ApplicationThemeTemplate.default_for_company_themes(
  company: product.company,
  themeable_type: :product
).first

# Get template with all variables for rendering
variables = Themes::Templates::Variable.new(
  template: template,
  record: product,
  company: product.company,
  request: request
).call

# Render the template
html = PageBuilder.render_template(template, variables)

# Working with layouts
layout_template = active_theme.application_theme_templates.layouts.find_by(name: 'theme')
content_with_layout = PageBuilder.render_with_layout(content, layout_template, variables)

# Theme versioning
active_theme.update_version(:minor)  # Increment minor version
active_theme.update_version(:major)  # Increment major version

# Check if theme can be auto-upgraded
active_theme.auto_upgradeable? # => true if minor version is 0 and outdated
active_theme.outdated? # => true if root theme has newer version

# Get theme audit history
version_history = active_theme.versions
# => [{ version: 1, created_at: "...", author_name: "John Doe", published: true }]

Use Cases

Company Theme Customization

  • Clone root themes to create company-specific versions
  • Customize color schemes, typography, and layout through CSS variables
  • Override specific templates (product pages, homepage) while inheriting others
  • Maintain brand consistency across all customer-facing pages

Content-Specific Template Assignment

  • Assign premium templates to high-value products or featured content
  • Create landing page templates for marketing campaigns
  • Develop specialized layouts for different content categories
  • Support A/B testing through template variations

Multi-Brand Support

  • Different themes for different product lines or brands
  • Template inheritance for consistent base functionality
  • Brand-specific customizations through variable overrides
  • Centralized template management with distributed customization

Theme Development and Deployment

  • Version control for theme changes and rollbacks
  • Staged deployment through draft/active status system
  • Template preview and testing before going live
  • Audit trails for change tracking and compliance

Benefits

Flexibility: Companies can customize themes at multiple levels without affecting core functionality

Maintainability: Clear separation between theme structure and content enables safe updates

Performance: Multi-level caching and optimized variable resolution ensure fast page loads

Scalability: Template inheritance and variable systems support growing customization needs

Developer Experience: Comprehensive API and model interfaces for theme manipulation

User Experience: Consistent theming across all page types with flexible customization options