The CMS module allows you to extend the Shopware Content Management System with custom elements and blocks.
Methods
registerCmsElement()
Register a custom CMS element that can be used in the Shopping Experiences editor.
Unique technical name for the element. Should include vendor prefix.Example: 'company-my-image-slider' generates location IDs:
company-my-image-slider-element - Element in the CMS
company-my-image-slider-preview - Preview in element selection
company-my-image-slider-config - Configuration modal
Display label shown when selecting the element. Use snippet keys for translation.
Default configuration object for the element. Same structure as plugin development.{
[key: string]: unknown;
}
Returns a promise that resolves when registration is complete
Example
import { ui } from '@shopware-ag/admin-sdk';
// Register custom image slider element
await ui.cms.registerCmsElement({
name: 'acme-image-slider',
label: 'acme.cms.elements.imageSlider.label',
defaultConfig: {
images: {
source: 'static',
value: [],
},
autoplay: {
source: 'static',
value: true,
},
delay: {
source: 'static',
value: 3000,
},
effect: {
source: 'static',
value: 'fade',
},
},
});
Rendering the Element
After registration, implement the rendering locations:
import { location } from '@shopware-ag/admin-sdk';
// Render element preview
location.startRendering('acme-image-slider-preview', (element) => {
return `
<div class="cms-element-preview">
<img src="/preview-image.jpg" />
<span>Image Slider</span>
</div>
`;
});
// Render element in CMS
location.startRendering('acme-image-slider-element', (element) => {
const config = element.config;
return `
<div class="image-slider" data-autoplay="${config.autoplay.value}">
${config.images.value.map(img =>
`<img src="${img.url}" alt="${img.alt}" />`
).join('')}
</div>
`;
});
// Render configuration modal
location.startRendering('acme-image-slider-config', (element, context) => {
return `
<div class="config-form">
<label>Autoplay</label>
<input type="checkbox"
checked="${element.config.autoplay.value}"
onchange="updateConfig('autoplay', this.checked)" />
<label>Delay (ms)</label>
<input type="number"
value="${element.config.delay.value}"
onchange="updateConfig('delay', this.value)" />
</div>
`;
});
registerCmsBlock()
Register a custom CMS block containing one or more element slots.
Unique technical name for the block. Recommended to use company prefix (e.g., ‘Swag’ for Shopware AG).
Display label shown in the CMS module
Block category: 'commerce', 'form', 'image', 'sidebar', 'text-image', 'text', 'video', or custom category name.Custom categories use snippet key: apps.sw-cms.detail.label.blockCategory.{categoryName}
slots
Array<{ element: string }>
required
Array of slot definitions. Each slot contains one element.[{ element: 'image' }, { element: 'text' }]
Optional CSS grid layout configurationCSS grid shorthand property. Examples:
'auto / auto' - 1 column
'auto / auto auto' - 2 columns
'auto auto / auto-flow auto' - 2 rows
Preview image URL (minimum 350px width recommended)
Returns a promise that resolves when registration is complete
Example
import { ui } from '@shopware-ag/admin-sdk';
// Register hero block with image and text
await ui.cms.registerCmsBlock({
name: 'acme-hero-block',
label: 'acme.cms.blocks.hero.label',
category: 'text-image',
slots: [
{ element: 'image' },
{ element: 'text' },
],
slotLayout: {
grid: 'auto / 1fr 1fr', // Two equal columns
},
previewImage: 'https://example.com/preview.jpg',
});
Two Column Block
await ui.cms.registerCmsBlock({
name: 'acme-two-column',
label: 'acme.cms.blocks.twoColumn.label',
category: 'text',
slots: [
{ element: 'text' },
{ element: 'text' },
],
slotLayout: {
grid: 'auto / auto auto',
},
});
Three Row Block
await ui.cms.registerCmsBlock({
name: 'acme-three-row',
label: 'acme.cms.blocks.threeRow.label',
category: 'image',
slots: [
{ element: 'image' },
{ element: 'image' },
{ element: 'image' },
],
slotLayout: {
grid: 'auto auto auto / auto',
},
});
Custom Category
// Register block with custom category
await ui.cms.registerCmsBlock({
name: 'acme-feature-block',
label: 'acme.cms.blocks.feature.label',
category: 'features', // Custom category
slots: [
{ element: 'acme-feature-card' },
{ element: 'acme-feature-card' },
{ element: 'acme-feature-card' },
],
slotLayout: {
grid: 'auto / repeat(3, 1fr)',
},
});
// Add translation for custom category
// Snippet key: apps.sw-cms.detail.label.blockCategory.features
Complete Example
Here’s a complete example registering both a custom element and block:
import { ui, location } from '@shopware-ag/admin-sdk';
// 1. Register custom element
await ui.cms.registerCmsElement({
name: 'acme-testimonial',
label: 'acme.cms.elements.testimonial.label',
defaultConfig: {
authorName: {
source: 'static',
value: '',
},
authorTitle: {
source: 'static',
value: '',
},
quote: {
source: 'static',
value: '',
},
avatar: {
source: 'static',
value: null,
},
},
});
// 2. Register block using the element
await ui.cms.registerCmsBlock({
name: 'acme-testimonials',
label: 'acme.cms.blocks.testimonials.label',
category: 'text',
slots: [
{ element: 'acme-testimonial' },
{ element: 'acme-testimonial' },
{ element: 'acme-testimonial' },
],
slotLayout: {
grid: 'auto / repeat(3, 1fr)',
},
previewImage: '/assets/testimonials-preview.jpg',
});
// 3. Render element preview
location.startRendering('acme-testimonial-preview', () => {
return `
<div class="testimonial-preview">
<div class="quote-icon">"</div>
<p>Testimonial preview</p>
</div>
`;
});
// 4. Render element
location.startRendering('acme-testimonial-element', (element) => {
const config = element.config;
return `
<div class="testimonial">
<blockquote>${config.quote.value}</blockquote>
<div class="author">
${config.avatar.value ?
`<img src="${config.avatar.value}" alt="${config.authorName.value}" />`
: ''}
<div>
<strong>${config.authorName.value}</strong>
<span>${config.authorTitle.value}</span>
</div>
</div>
</div>
`;
});
// 5. Render config form
location.startRendering('acme-testimonial-config', (element, context) => {
return `
<form class="cms-element-config">
<div class="form-group">
<label>Author Name</label>
<input type="text" value="${element.config.authorName.value}"
onchange="updateConfig('authorName', this.value)" />
</div>
<div class="form-group">
<label>Author Title</label>
<input type="text" value="${element.config.authorTitle.value}"
onchange="updateConfig('authorTitle', this.value)" />
</div>
<div class="form-group">
<label>Quote</label>
<textarea onchange="updateConfig('quote', this.value)">
${element.config.quote.value}
</textarea>
</div>
<div class="form-group">
<label>Avatar</label>
<button onclick="openMediaModal()">Select Image</button>
</div>
</form>
`;
});
Type Definitions
cmsRegisterElement
type cmsRegisterElement = {
responseType: void;
name: string;
label: string;
defaultConfig: {
[key: string]: unknown;
};
};
cmsRegisterBlock
type cmsRegisterBlock = {
responseType: void;
name: string;
label: string;
category?: ('commerce'|'form'|'image'|'sidebar'|'text-image'|'text'|'video') | string;
slots: Array<{ element: string }>;
slotLayout?: {
grid?: string;
};
previewImage?: string;
};
Best Practices
Always use vendor prefixes in element and block names to avoid conflicts (e.g., 'acme-slider' instead of 'slider').
Element configuration values must follow the { source: string, value: any } structure for CMS data binding.
Use CSS Grid’s shorthand property for slotLayout.grid. This provides flexible layout control. See MDN Grid Documentation.
Grid Layout Examples
// Single column
slotLayout: { grid: 'auto / auto' }
// Two equal columns
slotLayout: { grid: 'auto / 1fr 1fr' }
// Three columns
slotLayout: { grid: 'auto / repeat(3, 1fr)' }
// Two rows
slotLayout: { grid: 'auto auto / auto' }
// Sidebar layout (1:2 ratio)
slotLayout: { grid: 'auto / 1fr 2fr' }
// Complex grid
slotLayout: {
grid: 'repeat(2, auto) / repeat(3, 1fr)'
}