---
name: oro-frontend-dev
description: OroCommerce frontend development reference covering storefront themes, back-office themes, layouts, SCSS, JavaScript architecture (Chaplin/Backbone), page components, templates, and asset management. Use when customizing the storefront UI, modifying admin templates, writing JS components, or styling OroCommerce pages.
---

# OroCommerce Frontend Developer

Reference for frontend/UI development on OroCommerce.
Full docs: https://doc.oroinc.com/frontend/

## Two Theme Systems

OroCommerce has two separate theme architectures:

| Area | Framework | Templates | JS | Docs |
|---|---|---|---|---|
| Storefront | OroLayouts (SEO-friendly, no SPA) | Layout YAML + Twig blocks | Chaplin/Backbone | https://doc.oroinc.com/frontend/storefront/ |
| Back-office | Placeholders (SPA-like, Twig+Underscore) | Twig extends @OroUI | Chaplin/Backbone | https://doc.oroinc.com/frontend/back-office/ |

Themes are NOT interchangeable between storefront and back-office.

## Storefront Theming

### Theme Structure

```
Resources/views/layouts/{theme_name}/
    theme.yml              # theme definition (label, parent, icon)
    config/
        assets.yml         # SCSS entry points
        jsmodules.yml      # JS module registration
        datagrids.yml      # frontend datagrid configs
    templates/             # Twig block templates
    page/                  # per-page layout overrides
```

### theme.yml

Basic:
```yaml
label: 'My Custom Theme'
parent: default
icon: bundles/mybundle/images/theme-icon.png
groups: [commerce]
rtl_support: true
```

Full configuration with admin-selectable options:
```yaml
label: 'Acme Theme'
parent: default
groups: [commerce]
rtl_support: true

configuration:
  sections:
    header:
      label: Header
      options:
        promotional_content:
          label: oro_frontend.theme.default.configuration.header.promotional_content.label
          type: content_block_selector
          options:
            required: false
        standalone_main_menu:
          label: oro_frontend.theme.default.configuration.header.standalone_main_menu.label
          type: checkbox
          default: checked
          previews:
            checked: 'bundles/orofrontend/images/previews/menu-standalone.png'
            unchecked: 'bundles/orofrontend/images/previews/menu-hamburger.png'
    product_listing:
      label: 'oro.product.theme.default.configuration.product_listing.label'
      options:
        filters_position:
          label: 'oro.product.theme.default.configuration.product_listing.filters_position.label'
          type: radio
          default: sidebar
          values:
            top: 'oro.product.theme.default.configuration.product_listing.filters_position.values.top'
            sidebar: 'oro.product.theme.default.configuration.product_listing.filters_position.values.sidebar'
```

Configuration option types: `content_block_selector`, `menu_selector`, `checkbox`, `radio`, `select`.

### Layout Updates

Layout updates are YAML files that modify page structure:

```yaml
layout:
    actions:
        - '@setBlockTheme':
            id: page_container
            themes: '@MyBundle/layouts/my_theme/page/layout.html.twig'
        - '@add':
            id: my_custom_block
            parentId: page_content
            blockType: text
            options:
                text: '=data["locale"].getLanguage()'
        - '@remove':
            id: unwanted_block
        - '@move':
            id: existing_block
            parentId: new_parent
```

Place layout updates in route-specific directories to target individual pages:
```
Resources/views/layouts/{theme}/
    layout.yml                                    # global layout (all pages)
    oro_frontend_root/frontend_root.yml           # homepage
    oro_product_frontend_product_index/product_index.yml  # PLP
    oro_product_frontend_product_view/product_view.yml    # PDP
    oro_checkout_frontend_checkout/checkout.yml    # checkout
```

### Block Types

| blockType | Purpose |
|---|---|
| `container` | Wraps child blocks with a div |
| `text` | Static text or expression |
| `block` | Custom Twig-rendered block |
| `content_block` | References admin-managed content block by `alias` |
| `menu` | Renders a navigation menu |

### Layout Data Expressions

Access data providers and context variables in YAML options:

```yaml
options:
  vars:
    # Data provider methods (registered via layout_data_provider tag)
    topCategories: '=data["category_provider"].getRootCategoriesWithSlugs()'
    featuredCount: '=data["product_provider"].getFeaturedProductCount()'
    ctaLinks: '=data["cta_link_provider"].getLinks(data["menu"].getMenu("commerce_main_menu"))'

    # Context variables (route params, request attributes)
    category: '=data["category_provider"].getCategoryById(context["category_id"])'
```

### @addTree Action

Add multiple blocks at once:

```yaml
- '@addTree':
    items:
      footer_menu:
        blockType: menu
        options:
          item: '=data["menu"].getMenu("commerce_footer_links")'
          depth: 2
    tree:
      page_footer_base:
        footer_menu: ~
```

### @setBlockTheme with Multiple Files

```yaml
- '@setBlockTheme':
    themes:
      - 'templates/layout.html.twig'
      - 'templates/page-footer/footer_branding.html.twig'
      - 'templates/navigation/sidebar_menu.html.twig'
```

### Twig Block Naming Convention

Block names in layout Twig templates follow: `_{blockId}_widget`

```twig
{% block _my_custom_block_widget %}
    <div {{ block('block_attributes') }}>
        {{ block_widget(block) }}
    </div>
{% endblock %}
```

### SCSS / Stylesheets

Register SCSS in `config/assets.yml`:

```yaml
styles:
  inputs:
    - 'bundles/acmetheme/scss/styles.scss'
  output: 'css/styles.css'
```

Recommended SCSS directory organization:

```
scss/
    styles.scss           # main entry point (@import all)
    variables/
        _colors.scss      # color palette as Sass maps
        _typography.scss
        _mixins.scss
    components/
        _product-cards.scss
        _buttons.scss
    layout/
        _page-header.scss
        _page-footer.scss
        _product-view.scss
        _product-index.scss
        _checkout.scss
    navigation/
        _sidebar.scss
        _topbar.scss
```

Color palette using Sass maps (Oro pattern):

```scss
@use 'sass:map';

$theme-color-palette: (
    'brand': (
        'primary': #424242,
        'secondary': #A89571,
    ),
    'semantic': (
        'success': #005E1F,
        'error': #D32F2F,
    )
) !default;

$color-palette: map.deep-merge($color-palette, $theme-color-palette);
```

Access via `get-var-color('brand', 'primary')`. Build: `php bin/console oro:assets:build`.

### SVG Icons

Custom icons go in the theme's icons directory. Register via theme config.

## Back-Office Theming

### Twig Template Overrides

Override admin templates by mirroring directory structure under `Resources/views/`.

Common base templates to extend:
- `@OroUI/actions/index.html.twig` -- list/grid pages
- `@OroUI/actions/view.html.twig` -- detail/view pages
- `@OroUI/actions/update.html.twig` -- create/edit form pages

Use Oro UI macros for consistent rendering:

```twig
{% import '@OroUI/macros.html.twig' as UI %}
{{ UI.renderProperty('Label', value) }}
{{ UI.renderHtmlProperty('HTML Label', htmlValue) }}
```

### Placeholders

Inject content into predefined back-office page regions.

```yaml
# Resources/config/oro/placeholders.yml
placeholders:
    oro_view_additional_data:
        items:
            my_custom_section:
                order: 50
                template: '@AcmeExample/Placeholder/section.html.twig'
                applicable: '@acme.placeholder_filter->isApplicable'
```

Common placeholder targets:
- `oro_view_additional_data` -- entity view page extra sections
- `oro_widget_side_bar` -- sidebar widgets
- `oro_title_before` / `oro_title_after` -- title area

Conditional rendering via `applicable` filter (service method returning bool).
Placeholders are back-office only; use Layout Updates for storefront.

### SCSS

Override admin SCSS variables or add new stylesheets via theme settings.

## JavaScript Architecture

Built on Chaplin.js (extends Backbone.js). Key concepts:

### Page Components

The primary extension mechanism. Define in HTML via data attributes:

```html
<div data-page-component-module="mybundle/js/app/components/my-component"
     data-page-component-options="{{ options|json_encode }}">
</div>
```

Component lifecycle: created on `page:update`, disposed on page change.

### JS Module Registration

Register modules in `Resources/config/jsmodules.yml`:

```yaml
dynamic-imports:
    mybundle:
        - mybundle/js/app/components/my-component

aliases:
    jquery$: oroui/js/extend/jquery

# Override an existing module with custom implementation
map:
  '*':
    'orofrontend/js/app/datafilter/frontend-multiselect-decorator': 'acmetheme/js/custom-multiselect-decorator'
```

### App Modules

Execute code at application startup (auto-initialized on every page):

```yaml
app-modules:
    - acmetheme/js/app/modules/inventory-checker
    - acmetheme/js/app/modules/checkout-shipping-options
```

### AMD Module Pattern

Oro's JS uses AMD `define()` with the `oroui/js/mediator` for cross-component communication:

```javascript
define(function(require) {
    'use strict';

    const mediator = require('oroui/js/mediator');
    const $ = require('jquery');

    class MyComponent {
        constructor() {
            $(document).ready(() => {
                mediator.on('checkout:shipping-method:rendered', this.onShippingRendered);
            });
        }

        onShippingRendered() {
            // DOM manipulation after Oro renders shipping methods
        }
    }

    new MyComponent();
    return MyComponent;
});
```

Key mediator events: `checkout:shipping-method:rendered`, `filters-manager:after-applying-state`, `page:update`, `page:afterChange`.

### Page Events

System events during page lifecycle:
1. `page:beforeChange`
2. `page:request`
3. `page:update` -- content is updated, init components
4. `page:afterChange` -- all components initialized

### File Naming Convention

```
js/app/
    components/       # PageComponent implementations
    controllers/      # Chaplin controllers (rare -- PageController handles all)
    models/           # Backbone models and collections
    views/            # Backbone views
    modules/          # App modules (startup code)
templates/            # Underscore.js templates (*.html)
```

## Asset Build

```bash
php bin/console oro:assets:build          # build all themes
php bin/console oro:assets:build --watch   # watch mode for dev
php bin/console assets:install            # symlink public assets
```

Requires Node.js >= 22 and NPM > 10.

## RTL Support

Enable Right-to-Left UI via theme configuration.
See: https://doc.oroinc.com/frontend/rtl-support/

## Additional Resources

- Storefront how-to guides: https://doc.oroinc.com/frontend/storefront/how-to/
- JS modularity deep-dive: https://doc.oroinc.com/frontend/javascript/javascript-modularity/
- Storefront design/style guide (Figma): https://doc.oroinc.com/frontend/storefront-style-guide/
