If you have existing extensions to the legacy Angular-based Admin UI, you will want to migrate to the new Dashboard to enjoy an improved developer experience, many more customization options, and ongoing support from the Vendure team.
The Angular Admin UI will not be maintained after July 2026. Until then, we will continue patching critical bugs and security issues. Community contributions will always be merged and released.
A recommended approach to migrating is to run both the Admin UI and the new Dashboard in parallel. This allows you to start building new features right away with the new Dashboard while maintaining access to existing features that have not yet been migrated.
To do so, follow the instructions to set up the Dashboard. Both plugins can now be used simultaneously without any special configuration.
We highly recommend using AI tools such as Claude Code, Codex etc to assist with migrations from the legacy Angular-based UI extensions to the new React-based Dashboard.
The results of AI-assisted migration are heavily dependent on the model that you use. We tested with Claude Code using Sonnet 4.5 & Codex using gpt-5-codex
In our testing, we were able to perform complete migrations quickly using the following approach:
Using this approach we were able to migrate complete plugins involving list/details views, widgets, and custom field components in around 20-30 minutes.
Give a prompt like this to your AI assistant and make sure to specify the plugin by path, i.e.:
Migrate the plugin at @src/plugins/my-plugin/
to use the new dashboard.
Then paste the following prompt in full:
The full prompt is quite large, so it can make sense to first clear the current LLM context,
e.g. with /clear in Claude Code or /new in Codex CLI
If you use an AI coding agent with Agent Skills, you can set up a specialized skill for migrating plugins. This has the advantage that you do not need to continually paste in the full prompt, and it can also be potentially more token-efficient.
To set up the skill, run this from the root of your project:
npx skills add vendurehq/vendure --skill vendure-dashboard-migration
This installs the vendure-dashboard-migration skill from the Vendure repository into your selected agent's skills directory.
You can then have your coding agent use the skill with a prompt like:
Use the vendure-dashboard-migration skill to migrate
@src/plugins/my-plugin to use the dashboard
The individual files in the skill contain the exact same content as the full prompt above, but are more easily reused and can be more token-efficient
It is very likely you'll still need to do some manual cleanup after an AI-assisted migration. You might run into things like:
If you would rather do a full manual migration, you should first follow the Dashboard Getting Started guide and the Extending the Dashboard guide.
The remainder of this document details specific features, and how they are now implemented in the new Dashboard.
Forms in the Angular Admin UI used vdr-form-field components within a form-grid class. In the Dashboard, forms use FormFieldWrapper with react-hook-form, wrapped in either DetailFormGrid for grid layouts or div containers with space-y-6 for vertical spacing.
| Admin UI | Dashboard | Imported From | Notes |
|---|---|---|---|
vdr-form-field | FormFieldWrapper | @vendure/dashboard | Uses react-hook-form |
form-grid (class) | DetailFormGrid | @vendure/dashboard | For grid layouts |
vdr-rich-text-editor | RichTextInput | @vendure/dashboard | |
| - | Input | @vendure/dashboard | Basic text input |
FormGroup | useForm | @vendure/dashboard | Form state management |
Custom field inputs now use the DashboardFormComponent type and are registered via customFormComponents.customFields in the Dashboard extension definition. Components receive value, onChange, and name props, and can use useFormContext() to access field state and errors.
| Admin UI | Dashboard | Imported From | Notes |
|---|---|---|---|
FormInputComponent<T> | DashboardFormComponent | @vendure/dashboard | Type for custom field components |
registerFormInputComponent() | customFormComponents.customFields | @vendure/dashboard | Registration method |
formControl (prop) | value, onChange, name (props) | - | Component receives these props |
| - | useFormContext() | @vendure/dashboard | Access field state and errors |
List pages migrate from TypedBaseListComponent to the ListPage component. The ListPage automatically generates columns from the GraphQL query fields. Use customizeColumns to customize specific columns (e.g., linking with DetailPageButton), defaultVisibility to control which columns show by default, and defaultColumnOrder to set column order.
| Admin UI | Dashboard | Imported From | Notes |
|---|---|---|---|
TypedBaseListComponent | ListPage | @vendure/dashboard | Main list component |
vdr-data-table-2 | ListPage | @vendure/dashboard | Auto-generates columns |
vdr-dt2-column | customizeColumns | - | Prop on ListPage |
[hiddenByDefault] | defaultVisibility | - | Prop on ListPage |
registerRouteComponent() | DashboardRouteDefinition | @vendure/dashboard | Route registration |
[routerLink] | DetailPageButton | @vendure/dashboard | For linking to detail pages |
Important: When using defaultVisibility, only specify visible columns with true. The id, createdAt, and updatedAt columns are handled automatically. If a custom cell function accesses fields other than the one being rendered, declare them in meta.dependencies.
Detail pages migrate from TypedBaseDetailComponent to the useDetailPage hook. The hook handles form initialization, entity loading, and mutations. Use detailPageRouteLoader for the route loader, and structure the page with Page, PageActionBar, PageLayout, PageBlock, and DetailFormGrid components.
| Admin UI | Dashboard | Imported From | Notes |
|---|---|---|---|
TypedBaseDetailComponent | useDetailPage() | @vendure/dashboard | Hook for detail page logic |
this.init() | useDetailPage() | @vendure/dashboard | Automatic initialization |
this.entity$ | entity | - | Returned from useDetailPage |
FormBuilder | form | - | Returned from useDetailPage |
dataService.mutate() | submitHandler | - | Returned from useDetailPage |
vdr-page-detail-layout | PageLayout | @vendure/dashboard | Layout component |
vdr-page-block | PageBlock | @vendure/dashboard | Content block |
registerRouteComponent() | detailPageRouteLoader() | @vendure/dashboard | Route loader helper |
Important: PageBlock already renders as a card, so never nest Card components inside it. Use refreshEntity to manually reload entity data after mutations. Ensure vertical spacing of 6 units for components not in DetailFormGrid.
Nav menu items are now configured via the navMenuItem property on route definitions within the routes array. Specify sectionId (e.g., 'catalog'), unique id, and title.
| Admin UI | Dashboard | Imported From | Notes |
|---|---|---|---|
addNavMenuSection() | navMenuItem | - | Defined on route in routes array |
label | title | - | Display text |
routerLink | path | - | Route path |
icon | - | - | Not supported in Dashboard |
Action bar items migrate from addActionBarItem to the actionBarItems array in the Dashboard extension. Each item specifies a pageId and a component function that receives context.
| Admin UI | Dashboard | Imported From | Notes |
|---|---|---|---|
addActionBarItem() | actionBarItems | - | Array in defineDashboardExtension |
locationId | pageId | - | Identifies target page |
label | - | - | Render button/component directly |
icon | - | lucide-react | Use icon components in button |
routerLink | Link / useNavigate() | @tanstack/react-router | For navigation |
Custom detail components (Angular CustomDetailComponent) are now implemented as page blocks via the pageBlocks array. Each block specifies id, title, location (pageId, column, position), and a component function that receives context with entity and form access.
| Admin UI | Dashboard | Imported From | Notes |
|---|---|---|---|
CustomDetailComponent | pageBlocks | - | Array in defineDashboardExtension |
entity$ (Observable) | context.entity | - | Available in component function |
detailForm | context.form | - | Available in component function |
registerCustomDetailComponent() | pageBlocks[].location | - | Positioning configuration |
Page tabs (registerPageTab) are not supported in the Dashboard. Consider alternative approaches such as creating a new route or using page blocks.
| Admin UI | Dashboard | Imported From | Notes |
|---|---|---|---|
registerPageTab() | - | - | Not supported; use routes or page blocks instead |
Dashboard widgets migrate from registerDashboardWidget to the widgets array. Each widget specifies id, name, component, and defaultSize. Widget components can use useWidgetFilters() and useLocalFormat() hooks, and should wrap content in DashboardBaseWidget.
| Admin UI | Dashboard | Imported From | Notes |
|---|---|---|---|
registerDashboardWidget() | widgets | - | Array in defineDashboardExtension |
title | name | - | Widget display name |
loadComponent | component | - | Widget component function |
supportedWidths | defaultSize | - | Object with w and h properties |
| - | DashboardBaseWidget | @vendure/dashboard | Wrapper component for widgets |
| - | useWidgetFilters() | @vendure/dashboard | Access date range filters |
| - | useLocalFormat() | @vendure/dashboard | Formatting utilities |