Implement a Missing CMS Component
You are here because a CMS element or block in your storefront has no matching Vue component. In development mode this shows as a highlighted placeholder instead of the actual content.
This page will take you from placeholder to working component in a few minutes.
What is happening
The Shopware API returns a CMS tree of sections, blocks, and slots. Each node has a type field. The cms-base-layer package resolves a Vue component for each type by converting the name to PascalCase:
| API node | type value | expected component |
|---|---|---|
cms_section | sidebar | CmsSectionSidebar.vue |
cms_block | image-text | CmsBlockImageText.vue |
cms_slot | my-custom-slider | CmsElementMyCustomSlider.vue |
If no matching component exists, the placeholder appears. Your job is to create that component.
Is this a default Shopware CMS component?
The @shopware/cms-base-layer package ships implementations for all default Shopware 6 CMS blocks and elements. If you are seeing a placeholder for a type that ships with a standard Shopware 6 installation (not a custom plugin or your own block), this is a missing implementation in the package itself.
Missing a default component?
If the component type is part of core Shopware 6 CMS and is not covered by cms-base-layer, please open an issue so we can add it:
Add the cms-base label to the issue. Include the component name shown in the placeholder, the type value, and the apiAlias from the API response. You can copy the full content JSON from the copy AI prompt button in the placeholder.
If the component belongs to a custom plugin or you created the block yourself in the Shopware backend, continue with the steps below.
Step 1 — Find the component name
The placeholder in the browser already tells you exactly what to create:
⚠ missing implementation: CmsElementMyCustomSliderThat is the filename you need: CmsElementMyCustomSlider.vue.
Step 2 — Create the file
Create the file anywhere inside your components/ directory. Nuxt picks it up automatically as a global component:
your-project/
└── components/
└── CmsElementMyCustomSlider.vue ← create thisStep 3 — Define the props
Every CMS component receives a single content prop. Use the Shopware schema type matching the CMS node type:
<!-- components/CmsElementMyCustomSlider.vue -->
<script setup lang="ts">
import type { Schemas } from "#shopware";
const props = defineProps<{
content: Schemas["CmsSlot"];
}>();
</script><!-- components/CmsBlockMyCustomBanner.vue -->
<script setup lang="ts">
import type { Schemas } from "#shopware";
const props = defineProps<{
content: Schemas["CmsBlock"];
}>();
</script><!-- components/CmsSectionMyCustomLayout.vue -->
<script setup lang="ts">
import type { Schemas } from "#shopware";
const props = defineProps<{
content: Schemas["CmsSection"];
}>();
</script>Step 4 — Render the content
The content prop contains everything the API returned for that node. The exact fields depend on your CMS configuration in Shopware, but the structure is always:
content.config— editor-configured settings (alignment, display mode, etc.)content.data— resolved data (media objects, products, etc.)content.translated— translated field values
Use the copy AI prompt button on the placeholder to get a pre-filled prompt that includes the full content JSON for your specific element — paste it into any AI assistant to generate a working first draft.
A minimal working element:
<!-- components/CmsElementMyCustomSlider.vue -->
<script setup lang="ts">
import type { Schemas } from "#shopware";
const props = defineProps<{
content: Schemas["CmsSlot"];
}>();
// config values are typed as `unknown` — assert the shape you need
const title = props.content.config?.title?.value as string | undefined;
</script>
<template>
<div>
<h2 v-if="title">{{ title }}</h2>
<!-- render content.data here -->
</div>
</template>For blocks, use useCmsBlock to access named slots:
<!-- components/CmsBlockMyCustomBanner.vue -->
<script setup lang="ts">
import type { Schemas } from "#shopware";
const props = defineProps<{
content: Schemas["CmsBlock"];
}>();
const { getSlotContent } = useCmsBlock(props.content);
const heroContent = getSlotContent("hero");
</script>
<template>
<div class="my-banner">
<CmsGenericElement :content="heroContent" />
</div>
</template>Step 5 — Verify
Save the file. Vite will hot-reload and the placeholder will be replaced by your component. If it still shows, check that:
- the filename exactly matches the expected component name (PascalCase,
.vueextension) - the file is inside a directory that Nuxt scans for components
No restart needed
Nuxt's component auto-import picks up new files without restarting the dev server.