Migrating from Admin UI
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.
Running In Parallel
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.
AI-Assisted Migration
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:
- Use the provided prompt or Claude skill and specify which plugin you wish to migrate (do 1 at a time)
- Allow the AI tool to complete the migration
- Manually clean up & fix any issues that remain
Using this approach we were able to migrate complete plugins involving list/details views, widgets, and custom field components in around 20-30 minutes.
Full Prompt
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
Claude Skills
If you use Claude Code, you can use Agent Skills to 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 a the skill, run this from the root of your project:
npx degit vendure-ecommerce/vendure/.claude/skills#minor .claude/skills
This command uses degit to copy over the vendure-dashboard-migration skill to
your local ./claude/skills directory.
You can then have Claude Code 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
Manual Cleanup
It is very likely you'll still need to do some manual cleanup after an AI-assisted migration. You might run into things like:
- Non-optimum styling choices
- Issues with the tsconfig setup not being perfectly implemented.
- For more complex repo structures like a monorepo with plugins as separate libs, you may need to manually implement the initial setup of the config files.
Manual Migration
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
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 | react-hook-form | Form state management |
Custom Field Inputs
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() | react-hook-form | Access field state and errors |
List Pages
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
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
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
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 (Page Blocks)
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
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 |
Widgets
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 |