How Vue-Element-Admin Implements Dynamic Route Generation Based on User Roles
Vue-element-admin generates dynamic routes at runtime by filtering asynchronous route trees against user roles retrieved from the backend, then injecting the permitted routes into Vue Router using router.addRoutes() within the src/permission.js navigation guard.
Vue-element-admin is a production-ready Vue.js admin dashboard that implements granular access control through runtime route generation. Instead of loading all navigation items upfront, the application constructs the sidebar menu dynamically based on the authenticated user's role set. This architecture relies on a permission-based routing system orchestrated by the router guard in src/permission.js and the Vuex permission module in src/store/modules/permission.js.
The Navigation Guard Flow in src/permission.js
The dynamic route generation process begins in src/permission.js within the router.beforeEach navigation guard. This hook intercepts every route transition to verify authentication state and determine whether the user's permission set has been fetched and processed.
When a valid token exists (verified via getToken()) but the store lacks role information (store.getters.roles is empty), the guard initiates the permission flow. If no token is present, the user is redirected to the login page immediately.
Fetching User Roles
Upon detecting an authenticated user without loaded permissions, the guard dispatches the user/getInfo action. This asynchronous call retrieves the current user's profile from the backend, including an array of role identifiers such as ['admin'] or ['editor'].
const { roles } = await store.dispatch('user/getInfo') // src/permission.js L36-L38
The extracted roles array becomes the authority key for the subsequent route filtering process.
Filtering Routes by Permission
The retrieved roles are passed to the permission/generateRoutes Vuex action defined in src/store/modules/permission.js. This module filters the application's asynchronous route tree to exclude inaccessible paths before they reach the Vue Router matcher.
const accessRoutes = await store.dispatch('permission/generateRoutes', roles) // src/permission.js L40-L42
Evaluating Route Permissions with hasPermission
Inside src/store/modules/permission.js, the hasPermission utility (lines 8-13) performs the atomic permission check. It determines whether a single route's meta.roles array intersects with the user's role list. Routes that do not define meta.roles are treated as public and always permitted.
// src/store/modules/permission.js L8-L13
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
Building the Accessible Route Tree
The filterAsyncRoutes function (lines 21-35) recursively traverses the entire async route configuration. It constructs a new array containing only routes where hasPermission returns true, effectively pruning entire navigation branches when parent routes are restricted.
// src/store/modules/permission.js L21-L35
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
The Admin Role Bypass
The generateRoutes action (lines 49-61) implements a special case for administrators. If the user's role list contains 'admin', the function immediately grants access to all async routes without filtering. Otherwise, it invokes filterAsyncRoutes to compute the accessible subset, commits the result to the Vuex state via SET_ROUTES, and returns the filtered array.
// src/store/modules/permission.js L49-L61
const actions = {
generateRoutes({ commit }, roles) {
return new Promise(resolve => {
let accessedRoutes
if (roles.includes('admin')) {
accessedRoutes = asyncRoutes || []
} else {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
}
Dynamic Route Injection
After generateRoutes resolves, the guard receives the accessRoutes array and calls router.addRoutes(accessRoutes) (lines 42-44 in src/permission.js). Because Vue Router's addRoutes method operates asynchronously, the guard triggers a final navigation with next({ ...to, replace: true }) (lines 46-48) to ensure the newly injected routes are active for the current navigation request.
router.addRoutes(accessRoutes) // src/permission.js L42-L44
next({ ...to, replace: true }) // src/permission.js L46-L48
Declaring Role Requirements in Route Config
Route definitions in src/router/index.js specify required permissions using the meta.roles property. This declarative approach allows developers to restrict access to specific navigation items by listing authorized roles in an array.
// src/router/index.js (asyncRoutes)
{
path: '/permission',
component: Layout,
redirect: '/permission/page',
alwaysShow: true,
meta: { title: 'Permission', icon: 'lock', roles: ['admin', 'editor'] },
children: [
{
path: 'page',
component: () => import('@/views/permission/page'),
meta: { title: 'Page Permission', roles: ['admin'] }
}
]
}
Routes without a roles field remain accessible to all authenticated users, enabling a mixed public and secure navigation scheme.
Runtime Route Regeneration
In scenarios where user permissions change without a full page reload—such as after a role promotion in an admin panel—the route set must be regenerated manually. This requires re-fetching user info, recomputing accessible routes through generateRoutes, and re-invoking router.addRoutes().
// Component logic after role change
await this.$store.dispatch('user/getInfo')
const { roles } = this.$store.getters
const accessRoutes = await this.$store.dispatch('permission/generateRoutes', roles)
this.$router.addRoutes(accessRoutes)
Summary
- The
src/permission.jsguard initiates dynamic route generation after login by verifying tokens and fetching user roles viauser/getInfo. src/store/modules/permission.jsfilters the route tree usingfilterAsyncRoutes, which relies onhasPermissionto evaluatemeta.rolesmetadata against the user's role array.- Administrators bypass filtering entirely, receiving all async routes, while other roles receive a pruned subset based on the recursive permission check.
router.addRoutes()injects the computed routes at runtime, followed by a replacement navigation (next({ ...to, replace: true })) to activate them immediately.- Routes declare required roles through the
meta.rolesarray insrc/router/index.js, supporting both parent and child-level permission granularity.
Frequently Asked Questions
Where does vue-element-admin check if a user is allowed to access a specific route?
The permission check occurs in two places: the hasPermission function in src/store/modules/permission.js evaluates meta.roles during the route generation phase, while the src/permission.js guard prevents navigation to routes that haven't been generated yet by verifying the store's authentication and permission state before each route transition.
What happens if a user has no roles assigned?
If the roles array is empty or the user lacks the specific roles listed in a route's meta.roles, filterAsyncRoutes removes those routes from the accessible set. The user will only see routes that omit the roles property entirely (public routes) or routes where meta.roles includes at least one of their assigned roles.
How do I add a new protected page to vue-element-admin?
Add the route definition to the asyncRoutes array in src/router/index.js and include a meta: { roles: ['admin', 'yourRole'] } property listing the authorized roles. The existing generateRoutes logic in src/store/modules/permission.js will automatically include it for users possessing the specified roles when they next log in or refresh the page.
Can the permission system work with custom logic instead of simple roles?
Yes. While the default implementation checks string role arrays, you can modify the hasPermission function in src/store/modules/permission.js to implement alternative schemes such as permission codes, bitwise flags, or function-based checks. The dynamic injection architecture via generateRoutes and addRoutes remains compatible with any permission evaluation logic you implement.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →