Skip to content

Nuxt aliases

New Semantics themes define two Nuxt aliases with fundamentally different purposes#extensions and #widgets/plugin-registry. They're often confused. This page explains what each alias is for and, importantly: which alias is not meant for user imports.

The two aliases

_theme/vue-base/nuxt.config.ts:

ts
import { resolve } from 'node:path'

export default defineNuxtConfig({
    alias: {
        // Cross-plugin imports
        '#extensions':              resolve(process.cwd(), '../../_public/extensions/core/backend'),

        // Auto-generated Pagebuilder widget map — DO NOT edit manually!
        '#widgets/plugin-registry': resolve(process.cwd(), 'app/widgets/plugin-registry.js'),
    }
})

Both aliases look similar at first glance (# prefix, "widget" in both names), but they serve completely different roles:

AliasTargetPurposeAre you allowed to import?
#extensions_public/extensions/core/backend/Cross-plugin imports without ../../../../ pathsYes
#widgets/plugin-registryapp/widgets/plugin-registry.jsAuto-generated widget map for the Pagebuilder render loopNo — not for user imports

#extensions — cross-plugin imports

This alias is for user imports: when plugin A needs something from another plugin B (composable, Vue component, helper file), it imports it through this alias instead of through long relative paths.

The problem without an alias

Without #extensions, e-learning needs five ../ to reach the shop plugin:

ts
// E-learning widget loading the shop user composable
import { registerUserCenterItem } from '../../../shop/widgets/user/template/useUserCenter'

It works, but it's fragile:

  • Breaks on restructuring
  • Hard to read
  • No IDE autocomplete from context

With #extensions

ts
import { registerUserCenterItem } from '#extensions/shop/widgets/user/template/useUserCenter'

Stable, readable, makes cross-plugin dependencies explicit.

Typical use cases

ts
// Import the user-center registry (shop plugin)
import { registerUserCenterItem } from '#extensions/shop/widgets/user/template/useUserCenter'

// Vue component from another plugin
import MyCoursesView from '#extensions/elearning/widgets/elearning/template/MyCoursesUserCenter.vue'

// Helper from a plugin
import { resolvePageUrl } from '#extensions/menueditor/widgets/menu/helpers/urls'

// Plugin-local store import
import { useSw6Store } from '#extensions/shopware6/widgets/shared/sw6Store'

Rule: everything under _public/extensions/core/backend/ is reachable via #extensions/....

Details on user-center registration: Widgets › User-Center Items.

#widgets/plugin-registry — NOT for user imports

This alias points to an auto-generated JavaScript file produced during the cache rebuild. It contains a widget_template → Vue component mapping and is consumed exclusively by the Pagebuilder render loop.

What the file looks like

js
// _theme/vue-base/app/widgets/plugin-registry.js
// AUTO-GENERATED — do not edit manually
// Theme: vue-base

import NewsletterWidget       from '/absolute/path/.../emailmarketing/widgets/newsletter/template/index.vue'
import TitleBannerElementWidget from '/absolute/path/.../titleBannerElement/widgets/.../index.vue'
import ShopWidget             from '/absolute/path/.../shop/widgets/shop/template/index.vue'
// … more imports per installed widget

export default {
    'newsletter':          NewsletterWidget,
    'titleBannerElement':  TitleBannerElementWidget,
    'shop':                ShopWidget,
    'checkout':            CheckoutWidget,
    // …
}

The file is generated by the core script vueRegistry.php (under _public/extensions/core/backend/cache_settings/script/) during the cache rebuild. The script scans every widgets/*/template/index.vue file and generates the imports automatically. The target path is theme-specific: _theme/{theme}/app/widgets/plugin-registry.js.

Who consumes it?

Only PagebuilderWidget.vue:

ts
import widgetRegistry from '#widgets/plugin-registry'

const Component = computed(() => widgetRegistry[widget.type])

At runtime, when rendering a placed widget, Vue looks up the matching component in the map via widget.type (e.g. "testimonial").

Don't use it in your own imports

#widgets/plugin-registry is not a stable import endpoint:

  • The file is fully overwritten on every cache rebuild
  • Keys are widget_template strings, not stable identifiers
  • It contains absolute paths from the build context — only works in the active project's theme

If you need another widget's component from your own code: import it directly via #extensions/{plugin}/widgets/{widget}/template/index.vue.

Custom theme overrides

The registry generator respects theme-specific overrides:

_theme/{theme}/app/custom/{plugin}/widgets/{widget_template}/template/index.vue

If an override file exists, it's imported instead of the original. The generated plugin-registry.js then contains the override path under the same widget_template key. That lets themes override any plugin widget without modifying plugin code.

Details: Widgets › Widget Anatomy › Custom theme overrides.

Decision guide

SituationAlias
"I want to use a composable from another plugin"#extensions/{plugin}/.../composable
"I want to import a Vue component from another plugin"#extensions/{plugin}/widgets/.../index.vue
"I want to display the shop component inside my own widget"#extensions/shop/widgets/.../index.vue
"I want to enumerate every Pagebuilder widget programmatically"NOT through #widgets/plugin-registry — build your own scan logic
"I want to load the widget component for widget_template=testimonial"Direct via #extensions/{plugin}/widgets/testimonial/template/index.vue
"I want to debug the widget registry in the frontend"One-off as a debug: import registry from '#widgets/plugin-registry'; console.log(registry) — but never in production

When does plugin-registry.js get rebuilt?

The vueRegistry rebuild runs on:

  1. /admin/cache → "Clear cache"
  2. Plugin install/uninstall (shop, e-learning, or your own plugin)
  3. A manual trigger (running vueRegistry.php directly)

Not automatically on:

  • Nuxt dev-server start (npm run dev only reloads Vue files, not the registry)
  • Repo checkout (git pull)
  • A change to an existing template/index.vue (that's already hot-reloaded; the registry stays valid)

Required on:

  • A new widget under widgets/{widget}/template/index.vue
  • A new custom theme override under _theme/{theme}/app/custom/...
  • Renaming a widget_template in a plugin

Details on the cache system: Design tokens › Cache integration.

TypeScript typing for #extensions

By default, the alias has no types — every file behind it must export its own. Example:

ts
// _public/extensions/core/backend/shop/widgets/user/template/useUserCenter.ts
export interface UserCenterItem { … }
export function registerUserCenterItem(item: UserCenterItem) { … }

On import, the type information comes along automatically:

ts
import { registerUserCenterItem, type UserCenterItem } from '#extensions/shop/widgets/user/template/useUserCenter'

For Vue components from other plugins, defineComponent/defineProps work just like they do for local imports.

Common issues

Importing #widgets/plugin-registry

ts
// WRONG — the registry is overwritten on cache rebuild, imports aren't stable
import widgetRegistry from '#widgets/plugin-registry'

// RIGHT — direct import via #extensions
import TestimonialWidget from '#extensions/widgetsBase/widgets/testimonial/template/index.vue'

If you absolutely need registry access (e.g. a custom widget picker), build your own scan logic — don't consume this map.

Plugin folder names with hyphens or mixed case

#extensions/MenuEditor/... does not match menueditor/. The alias performs no normalization — exact folder name, exact case.

Importing the alias from a third-party package

#extensions only works in theme code (_theme/vue-base/app/ and below) and in plugin code that the theme consumes. npm packages have no access to the alias — they must use absolute or relative paths.

#extensions path not adjusted in a project fork

A project fork (_theme/projekt01/) has its own nuxt.config.ts. The #extensions path ../../_public/extensions/core/backend stays the same, because the plugin directory is global. But when you create a new theme, do not omit the #extensions entry — otherwise every cross-plugin import from widget code breaks.

See also