Verified on Shopware 6.7

What We're Building

We'll create a custom CMS block called "Feature Highlight" - a commonly requested component that displays a title, description, image, and a call-to-action button. The kind of block that marketing teams ask for constantly.

By the end, you'll understand the full lifecycle: registration, preview, config, storefront rendering.

File Structure

YourPlugin/
└── src/
    └── Resources/
        ├── app/
        │   └── administration/
        │       └── src/
        │           └── module/
        │               └── sw-cms/
        │                   ├── blocks/
        │                   │   └── feature-highlight/
        │                   │       ├── index.js
        │                   │       ├── component/
        │                   │       │   ├── index.js
        │                   │       │   └── sw-cms-block-feature-highlight.html.twig
        │                   │       └── preview/
        │                   │           ├── index.js
        │                   │           └── sw-cms-preview-feature-highlight.html.twig
        │                   └── elements/
        │                       └── feature-highlight/
        │                           ├── index.js
        │                           ├── component/
        │                           │   ├── index.js
        │                           │   └── sw-cms-el-feature-highlight.html.twig
        │                           └── config/
        │                               ├── index.js
        │                               └── sw-cms-el-config-feature-highlight.html.twig
        └── views/
            └── storefront/
                └── element/
                    └── cms-element-feature-highlight.html.twig

Step 1: Register the CMS Element

The element defines what data the block holds.

// elements/feature-highlight/index.js
import './component';
import './config';

Shopware.Service('cmsService').registerCmsElement({
    name: 'feature-highlight',
    label: 'Feature Highlight',
    component: 'sw-cms-el-feature-highlight',
    configComponent: 'sw-cms-el-config-feature-highlight',
    defaultConfig: {
        title: {
            source: 'static',
            value: 'Your Feature Title',
        },
        description: {
            source: 'static',
            value: 'Describe your feature here.',
        },
        buttonLabel: {
            source: 'static',
            value: 'Learn More',
        },
        buttonUrl: {
            source: 'static',
            value: '#',
        },
        media: {
            source: 'static',
            value: null,
            entity: {
                name: 'media',
            },
        },
    },
});

Step 2: Element Component (Admin Preview)

What editors see in the CMS editor:

// elements/feature-highlight/component/index.js
import template from './sw-cms-el-feature-highlight.html.twig';

Shopware.Component.register('sw-cms-el-feature-highlight', {
    template,

    mixins: [
        Shopware.Mixin.getByName('cms-element'),
    ],

    computed: {
        title() {
            return this.element.config.title.value;
        },
        description() {
            return this.element.config.description.value;
        },
        buttonLabel() {
            return this.element.config.buttonLabel.value;
        },
        mediaUrl() {
            const media = this.element.data?.media;
            return media?.url || '';
        },
    },

    created() {
        this.initElementConfig('feature-highlight');
        this.initElementData('feature-highlight');
    },
});
<!- sw-cms-el-feature-highlight.html.twig ->
<div class="sw-cms-el-feature-highlight">
    <div class="sw-cms-el-feature-highlight__image" v-if="mediaUrl">
        <img :src="mediaUrl" alt="">
    </div>
    <div class="sw-cms-el-feature-highlight__content">
        <h2>{{ title }}</h2>
        <p>{{ description }}</p>
        <button class="sw-button sw-button-primary">{{ buttonLabel }}</button>
    </div>
</div>

Step 3: Element Config (Sidebar Form)

The configuration sidebar where editors fill in values:

// elements/feature-highlight/config/index.js
import template from './sw-cms-el-config-feature-highlight.html.twig';

Shopware.Component.register('sw-cms-el-config-feature-highlight', {
    template,

    mixins: [
        Shopware.Mixin.getByName('cms-element'),
    ],

    created() {
        this.initElementConfig('feature-highlight');
    },
});
<!- sw-cms-el-config-feature-highlight.html.twig ->
<div class="sw-cms-el-config-feature-highlight">
    <sw-text-field
        label="Title"
        v-model="element.config.title.value">
    </sw-text-field>

    <sw-textarea-field
        label="Description"
        v-model="element.config.description.value">
    </sw-textarea-field>

    <sw-text-field
        label="Button Label"
        v-model="element.config.buttonLabel.value">
    </sw-text-field>

    <sw-text-field
        label="Button URL"
        v-model="element.config.buttonUrl.value">
    </sw-text-field>

    <sw-media-field
        label="Image"
        v-model="element.config.media.value">
    </sw-media-field>
</div>

Step 4: Register the Block

The block wraps the element and appears in the CMS block picker:

// blocks/feature-highlight/index.js
import './component';
import './preview';

Shopware.Service('cmsService').registerCmsBlock({
    name: 'feature-highlight',
    label: 'Feature Highlight',
    category: 'text-image',
    component: 'sw-cms-block-feature-highlight',
    previewComponent: 'sw-cms-preview-feature-highlight',
    defaultConfig: {
        marginBottom: '20px',
        marginTop: '20px',
        marginLeft: '20px',
        marginRight: '20px',
        sizingMode: 'boxed',
    },
    slots: {
        content: 'feature-highlight',
    },
});
// blocks/feature-highlight/component/index.js
import template from './sw-cms-block-feature-highlight.html.twig';

Shopware.Component.register('sw-cms-block-feature-highlight', {
    template,
});
<!- sw-cms-block-feature-highlight.html.twig ->
<div class="sw-cms-block-feature-highlight">
    <slot name="content">
        <sw-cms-slot name="content"></sw-cms-slot>
    </slot>
</div>

Step 5: Block Preview (Thumbnail in Picker)

// blocks/feature-highlight/preview/index.js
import template from './sw-cms-preview-feature-highlight.html.twig';

Shopware.Component.register('sw-cms-preview-feature-highlight', {
    template,
});
<!- sw-cms-preview-feature-highlight.html.twig ->
<div class="sw-cms-preview-feature-highlight">
    <div style="display:flex;gap:12px;padding:12px;background:#f9f9f9;border-radius:4px;">
        <div style="width:40px;height:40px;background:#ddd;border-radius:4px;"></div>
        <div style="flex:1;">
            <div style="height:8px;background:#ccc;width:60%;margin-bottom:6px;border-radius:2px;"></div>
            <div style="height:6px;background:#eee;width:80%;border-radius:2px;"></div>
        </div>
    </div>
</div>

Step 6: Storefront Rendering

Finally, how it renders on the actual storefront:

{# views/storefront/element/cms-element-feature-highlight.html.twig #}
{% block element_feature_highlight %}
    <div class="cms-element-feature-highlight">
        {% if element.data.media %}
        <div class="feature-highlight__image">
            {% sw_thumbnails 'feature-highlight-image' with {
                media: element.data.media,
                sizes: { 'default': '400px' }
            } %}
        </div>
        {% endif %}

        <div class="feature-highlight__content">
            <h2 class="feature-highlight__title">
                {{ element.config.title.value }}
            </h2>
            <p class="feature-highlight__description">
                {{ element.config.description.value }}
            </p>
            {% if element.config.buttonUrl.value %}
            <a href="{{ element.config.buttonUrl.value }}"
               class="btn btn-primary feature-highlight__button">
                {{ element.config.buttonLabel.value }}
            </a>
            {% endif %}
        </div>
    </div>
{% endblock %}

Step 7: Register Everything in main.js

// administration/src/main.js
import './module/sw-cms/blocks/feature-highlight';
import './module/sw-cms/elements/feature-highlight';

Build and Test

# Build admin
bin/build-administration.sh

# Clear cache
bin/console cache:clear

# Open admin → Content → Shopping Experiences
# Your "Feature Highlight" block should appear under "Text & Images"

Tips

  1. Always provide sensible defaults - editors shouldn't see blank blocks
  2. Use sw_thumbnails in storefront - never hardcode image URLs
  3. Category matters - put blocks in the right category so editors can find them
  4. Test with empty data - what happens when an editor leaves fields blank?

Need custom CMS blocks for your Shopware shop? We build them.