Skip to content

Implement a Missing CMS Component

Implement a Missing CMS Component ​

You are here because a CMS element 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 nodetype valueexpected component
cms_sectionsidebarCmsSectionSidebar.vue
cms_blockimage-textCmsBlockImageText.vue
cms_slotmy-custom-sliderCmsElementMyCustomSlider.vue

For a custom element, type is the name it was registered with in the Administration, so registerCmsElement({ name: "dailymotion" }) expects CmsElementDailymotion.vue.

If no matching block or element component exists, the placeholder appears in development, and the browser console logs a warning with the component name to create and a link to the docs. In production nothing renders, so a missing component fails silently. A missing section is the exception: CmsPage renders a plain "There is no …" line for it in every mode. Your job is to create the 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:

πŸ‘‰ Create an issue on GitHub

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. The backend side of a custom element is described in Add custom CMS element.

Step 1 β€” Create the file ​

Create the file under your template’s global CMS components dir (e.g. app/components/cms/), which templates register with global: true so resolveComponent can find it:

your-project/
└── app/
    └── components/
        └── cms/
            └── {{ componentName }}.vue   ← create this

Step 2 β€” Define the props ​

Every CMS component receives a single content prop. Use the Shopware schema type matching the CMS node type:

vue
<!-- app/components/cms/{{ componentName }}.vue -->
<script setup lang="ts">
import type { Schemas } from "#shopware";

const props = defineProps<{
  content: Schemas["CmsBlock"];
}>();
</script>

For an element with its own settings, type config from the defaultConfig it was registered with. The backend guide registers its dailymotion element like this:

ts
type CmsElementRegistration = {
  name: string;
  defaultConfig: {
    dailyUrl: {
      source: "static";
      value: string;
    };
  };
};

declare const Shopware: {
  Service(service: "cmsService"): {
    registerCmsElement(config: CmsElementRegistration): void;
  };
};

Shopware.Service("cmsService").registerCmsElement({
  name: "dailymotion",
  defaultConfig: {
    dailyUrl: {
      source: "static",
      value: "",
    },
  },
});

Each key of defaultConfig arrives in content.config as an ElementConfig holding source and value, which is how the element example in Step 3 types its config.

Step 3 β€” 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:

vue
<!-- app/components/cms/{{ componentName }}.vue -->
<script setup lang="ts">
import type { ElementConfig } from "@shopware/composables";

import { computed, useCmsElementConfig } from "#imports";
import type { Schemas } from "#shopware";

type CmsElementMyCustomSlider = Omit<Schemas["CmsSlot"], "config"> & {
  config: {
    title?: ElementConfig<string>;
  };
};

const props = defineProps<{
  content: CmsElementMyCustomSlider;
}>();

const { getConfigValue } = useCmsElementConfig(props.content);
const title = computed(() => getConfigValue("title") || "");
</script>

<template>
  <div>
    <h2 v-if="title">{{ title }}</h2>
  </div>
</template>

Read the settings through getConfigValue rather than from content.config directly. It returns false for an entry whose source is mapped, because a mapped value comes from the surrounding entity, not from the element.

Step 4 β€” 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, .vue extension)
  • the file is inside a directory your nuxt.config.ts registers with global: true β€” app/components/cms/ in vue-starter-template. A file in plain app/components/ is auto-imported but not global, so resolveComponent does not find it.

No restart needed

Nuxt's component auto-import picks up new files without restarting the dev server.

Outside Nuxt nothing registers the file for you. Register the component globally, because resolveComponent only sees global components:

ts
import { createApp } from "vue";

import CmsElementDailymotion from "./components/cms/CmsElementDailymotion.vue";

const app = createApp({});
app.component("CmsElementDailymotion", CmsElementDailymotion);

Going deeper ​

Create Elements
Typed composables and helpers for working with CMS element data.
Create Blocks
How to build block layouts with named slots.
Customize existing components
Override a default component from cms-base-layer.
Rendering CMS Pages
How the CMS tree in the API response is resolved to components.
Was this page helpful?
UnsatisfiedSatisfied
Be the first to vote!
0.0 / 5 Β (0 votes)