Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/shopware/meteor/llms.txt

Use this file to discover all available pages before exploring further.

This guide shows you how to extend the Shopware CMS (Shopping Experiences) by creating custom CMS elements and blocks.

Overview

The Admin SDK allows you to:
  • Register custom CMS elements
  • Register custom CMS blocks
  • Create configuration interfaces for your elements
  • Render custom content in the CMS

CMS Elements

CMS elements are the building blocks of the Shopping Experiences. Each element represents a specific type of content.

Registering a CMS Element

import { cms } from '@shopware-ag/meteor-admin-sdk';

await cms.registerCmsElement({
    name: 'my-company-video-element',
    label: 'Video Player',
    defaultConfig: {
        videoUrl: {
            source: 'static',
            value: '',
        },
        autoplay: {
            source: 'static',
            value: false,
        },
    },
});

Element Location IDs

When you register an element, three location IDs are automatically generated:
// For name: 'my-company-video-element'

'my-company-video-element-element'  // Main element rendering
'my-company-video-element-preview'  // Preview in element selection
'my-company-video-element-config'   // Configuration modal

Complete CMS Element Example

Here’s a complete example for a video element:

1. Register the Element

import { cms } from '@shopware-ag/meteor-admin-sdk';

const ELEMENT_NAME = 'my-video-player';

await cms.registerCmsElement({
    name: ELEMENT_NAME,
    label: 'Video Player',
    defaultConfig: {
        videoUrl: {
            source: 'static',
            value: 'https://example.com/video.mp4',
        },
        autoplay: {
            source: 'static',
            value: false,
        },
        controls: {
            source: 'static',
            value: true,
        },
    },
});

2. Create the Preview Component

<!-- my-video-player-preview.vue -->
<template>
    <div class="video-preview">
        <div class="video-icon">
            <i class="icon-play"></i>
        </div>
        <p>Video Player Element</p>
    </div>
</template>

<style scoped>
.video-preview {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: #f5f5f5;
    border-radius: 4px;
}

.video-icon {
    font-size: 48px;
    color: #1890ff;
}
</style>

3. Create the Configuration Component

<!-- my-video-player-config.vue -->
<template>
    <div class="config-form">
        <sw-text-field
            label="Video URL"
            v-model="config.videoUrl.value"
            @input="updateConfig"
        />
        
        <sw-switch-field
            label="Autoplay"
            v-model="config.autoplay.value"
            @change="updateConfig"
        />
        
        <sw-switch-field
            label="Show Controls"
            v-model="config.controls.value"
            @change="updateConfig"
        />
    </div>
</template>

<script>
import { defineComponent } from 'vue';
import { data } from '@shopware-ag/meteor-admin-sdk';
import { SwTextField, SwSwitchField } from '@shopware-ag/meteor-component-library';

export default defineComponent({
    components: {
        'sw-text-field': SwTextField,
        'sw-switch-field': SwSwitchField,
    },
    data() {
        return {
            config: {
                videoUrl: { source: 'static', value: '' },
                autoplay: { source: 'static', value: false },
                controls: { source: 'static', value: true },
            }
        };
    },
    methods: {
        async loadConfig() {
            const elementData = await data.get({
                id: 'my-video-player__config-element',
            });
            
            if (elementData?.config) {
                this.config = elementData.config;
            }
        },
        
        updateConfig() {
            data.update({
                id: 'my-video-player__config-element',
                data: {
                    config: this.config
                }
            });
        }
    },
    mounted() {
        this.loadConfig();
    }
});
</script>

4. Create the Element Component

<!-- my-video-player-element.vue -->
<template>
    <div class="video-element">
        <video
            v-if="videoUrl"
            :src="videoUrl"
            :autoplay="autoplay"
            :controls="controls"
            class="video-player"
        />
        <div v-else class="no-video">
            <p>No video URL configured</p>
        </div>
    </div>
</template>

<script>
import { defineComponent } from 'vue';
import { data } from '@shopware-ag/meteor-admin-sdk';

export default defineComponent({
    data() {
        return {
            videoUrl: '',
            autoplay: false,
            controls: true,
        };
    },
    methods: {
        async loadConfig() {
            const elementData = await data.get({
                id: 'my-video-player__config-element',
            });
            
            if (elementData?.config) {
                this.videoUrl = elementData.config.videoUrl.value;
                this.autoplay = elementData.config.autoplay.value;
                this.controls = elementData.config.controls.value;
            }
        }
    },
    mounted() {
        this.loadConfig();
        
        // Subscribe to config changes
        data.subscribe('my-video-player__config-element', (response) => {
            if (response.data?.config) {
                this.videoUrl = response.data.config.videoUrl.value;
                this.autoplay = response.data.config.autoplay.value;
                this.controls = response.data.config.controls.value;
            }
        });
    }
});
</script>

<style scoped>
.video-element {
    width: 100%;
}

.video-player {
    width: 100%;
    height: auto;
}

.no-video {
    padding: 40px;
    text-align: center;
    background: #f5f5f5;
    color: #999;
}
</style>

CMS Blocks

CMS blocks are containers that hold one or more CMS elements in a specific layout.

Registering a CMS Block

import { cms } from '@shopware-ag/meteor-admin-sdk';

await cms.registerCmsBlock({
    name: 'my-company-two-column-block',
    label: 'Two Column Layout',
    category: 'text-image',
    slots: [
        {
            element: 'image',
        },
        {
            element: 'text',
        }
    ],
    slotLayout: {
        grid: 'auto / auto auto'
    },
    previewImage: 'https://example.com/preview.png'
});

Block Parameters

  • name: Unique identifier (use vendor prefix)
  • label: Display name in the block selector
  • category: Block category (see below)
  • slots: Array of element slots
  • slotLayout: CSS grid layout configuration
  • previewImage: Preview image URL (min width: 350px)

Block Categories

Available categories:
  • commerce - Commerce-related blocks
  • form - Form elements
  • image - Image blocks
  • sidebar - Sidebar elements
  • text-image - Text and image combinations
  • text - Text blocks
  • video - Video blocks
You can also create custom categories:
await cms.registerCmsBlock({
    name: 'my-custom-block',
    label: 'Custom Block',
    category: 'my-custom-category',  // Creates new category
    slots: [{ element: 'text' }],
});

// Snippet key for custom category:
// apps.sw-cms.detail.label.blockCategory.my-custom-category

Grid Layouts

The grid property uses CSS grid shorthand:
// 1 column layout
slotLayout: {
    grid: 'auto / auto'
}

// 2 column layout (equal)
slotLayout: {
    grid: 'auto / auto auto'
}

// 2 column layout (2:1 ratio)
slotLayout: {
    grid: 'auto / 2fr 1fr'
}

// 2 row layout
slotLayout: {
    grid: 'auto auto / auto-flow auto'
}

// 3 column layout
slotLayout: {
    grid: 'auto / repeat(3, 1fr)'
}

Complete Block Example

import { cms } from '@shopware-ag/meteor-admin-sdk';

// Register a hero block with image and text
await cms.registerCmsBlock({
    name: 'my-company-hero-block',
    label: 'Hero Section',
    category: 'text-image',
    slots: [
        {
            element: 'image',  // Left side
        },
        {
            element: 'text',   // Right side
        }
    ],
    slotLayout: {
        grid: 'auto / 1fr 1fr'  // Equal columns
    },
    previewImage: 'https://cdn.example.com/hero-preview.png'
});

// Register a custom element block
await cms.registerCmsBlock({
    name: 'my-company-video-block',
    label: 'Video Block',
    category: 'video',
    slots: [
        {
            element: 'my-video-player',  // Custom element
        }
    ],
    slotLayout: {
        grid: 'auto / auto'
    }
});

Real-World Example: Dailymotion Element

Here’s a complete example from the admin-sdk-app:
// Constants
const CMS_DAILYMOTION_ELEMENT_NAME = 'ex-dailymotion';
const PUBLISHING_KEY = `${CMS_DAILYMOTION_ELEMENT_NAME}__config-element`;

// Register element
await cms.registerCmsElement({
    name: CMS_DAILYMOTION_ELEMENT_NAME,
    label: 'Dailymotion video',
    defaultConfig: {
        dailyUrl: {
            source: 'static',
            value: '',
        },
    },
});

// Location IDs generated:
// - ex-dailymotion-element
// - ex-dailymotion-preview  
// - ex-dailymotion-config

Configuration Best Practices

  1. Use meaningful names: Include vendor prefix (e.g., my-company-element-name)
  2. Provide defaults: Always set sensible default values
  3. Source types: Use static for fixed values, mapped for dynamic data
  4. Validation: Validate user input in config components
  5. Live preview: Subscribe to config changes in element components

Config Object Structure

The default config structure:
{
    propertyName: {
        source: 'static' | 'mapped',
        value: any,
    }
}
Example:
defaultConfig: {
    // Static text value
    title: {
        source: 'static',
        value: 'Default Title',
    },
    // Mapped from entity
    productName: {
        source: 'mapped',
        value: 'product.name',
    },
    // Boolean value
    showBorder: {
        source: 'static',
        value: true,
    },
    // Number value
    columns: {
        source: 'static',
        value: 3,
    },
}

Next Steps

Build docs developers (and LLMs) love