# How the v-permission Directive Works in vue-element-admin: Implementation and Extension Guide

> Learn how the v-permission directive in vue-element-admin controls access by checking user roles and removing DOM elements. Extend it for custom role-based access control.

- Repository: [花裤衩/vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
- Tags: deep-dive
- Published: 2026-02-27

---

**The `v-permission` directive in vue-element-admin checks the current user's roles against a required array and physically removes the DOM element when access is denied.**

The vue-element-admin repository provides a production-ready Vue.js admin dashboard that implements granular role-based access control through both routing guards and template directives. Understanding how the `v-permission` directive functions enables developers to secure UI components effectively and extend the system for complex authorization scenarios.

## Core Implementation of the v-permission Directive

The directive is implemented in [`src/directive/permission/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/directive/permission/permission.js) and operates entirely on the client side by interfacing with the Vuex store.

### Reading Roles from Vuex Store

The directive imports the global store to access the authenticated user's role list. It retrieves the roles array from the `user` module's getter:

```javascript
import store from '@/store'

// Inside checkPermission function:
const roles = store.getters && store.getters.roles

```

This roles array is populated after login through the `user/getInfo` action, typically fetching data from an authentication endpoint and committing it to the store.

### The checkPermission Helper Function

The core logic resides in the `checkPermission` function, which receives the DOM element (`el`) and the directive binding value (`value`). The binding value must be an **Array** of role strings (e.g., `['admin', 'editor']`):

```javascript
function checkPermission(el, binding) {
  const { value } = binding
  const roles = store.getters && store.getters.roles
  
  if (value && value instanceof Array && value.length > 0) {
    const permissionRoles = value
    const hasPermission = roles.some(role => {
      return permissionRoles.includes(role)
    })
    
    if (!hasPermission) {
      el.parentNode && el.parentNode.removeChild(el)
    }
  } else {
    throw new Error(`need roles! Like v-permission="['admin','editor']"`)
  }
}

```

The permission check uses `Array.prototype.some()` to determine if at least one user role exists in the required roles array. This implements an **OR** logic—access is granted if any single role matches.

### DOM Manipulation Strategy

When `hasPermission` evaluates to `false`, the directive performs aggressive DOM cleanup:

```javascript
if (!hasPermission) {
  el.parentNode && el.parentNode.removeChild(el)
}

```

This physically removes the element from its parent node, ensuring the forbidden element never appears in the render tree or DOM inspector. This approach prevents users from inspecting hidden elements, offering stronger security than CSS-based hiding (e.g., `display: none`).

### Lifecycle Hooks and Error Handling

The directive exports an object with two lifecycle hooks:

```javascript
export default {
  inserted(el, binding) {
    checkPermission(el, binding)
  },
  update(el, binding) {
    checkPermission(el, binding)
  }
}

```

- **`inserted`**: Runs when the bound element is inserted into the DOM, performing the initial permission check.
- **`update`**: Runs when the component updates and the binding value changes, re-evaluating permissions dynamically.

The directive includes strict type checking that throws a descriptive error if the binding value is not an array, guiding developers to use the correct syntax during development.

## Integration with the Routing Permission System

While `v-permission` protects individual UI elements, vue-element-admin implements a complementary routing guard system in [`src/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/permission.js) and [`src/store/modules/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/store/modules/permission.js) to protect entire navigation paths.

The router guard reads the same `store.getters.roles` array used by the directive, ensuring consistency between route-level and component-level access control. The `hasPermission` function in the permission store module checks `route.meta.roles`, and `filterAsyncRoutes` recursively filters the `asyncRoutes` array based on these permissions.

This architecture ensures that both the navigation menu (via filtered routes) and individual action buttons (via `v-permission`) derive authorization decisions from the **single source of truth** in the Vuex store.

## Extending v-permission for Custom Role-Based Access Control

The built-in directive supports basic role arrays, but real-world applications often require complex logic such as hierarchical roles, resource-specific permissions, or time-based access. You can extend the system by creating custom directives that reuse the core logic while adding specialized checks.

### Creating a Custom Directive with Additional Logic

To extend the directive while preserving the original behavior, create a new file (e.g., [`src/directive/permission/customPermission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/directive/permission/customPermission.js)) that imports the store and implements enhanced checking logic:

```javascript
// src/directive/permission/customPermission.js
import store from '@/store'

// Reuse the original role check logic
function coreCheck(requiredRoles) {
  const userRoles = store.getters && store.getters.roles
  return requiredRoles.some(r => userRoles.includes(r))
}

// Example: Additional check for a super-admin flag
function hasSuperAdmin() {
  const user = store.getters && store.getters.user
  return user && user.isSuperAdmin
}

function checkCustomPermission(el, binding) {
  const { value } = binding
  if (!Array.isArray(value)) {
    throw new Error('v-custom-permission expects an array of roles')
  }

  const allowed = coreCheck(value) || hasSuperAdmin()

  // Instead of removing the element, disable it visually
  if (!allowed) {
    el.setAttribute('disabled', 'true')
    el.classList.add('no-permission')
    el.title = 'You do not have permission to use this control'
  }
}

export default {
  inserted(el, binding) {
    checkCustomPermission(el, binding)
  },
  update(el, binding) {
    checkCustomPermission(el, binding)
  }
}

```

This example demonstrates extending the directive to support a **super-admin bypass** and **UI degradation** (disabling rather than hiding elements).

### Registering the Custom Directive

Register the new directive alongside the original in your directive index file:

```javascript
// src/directive/index.js
import Vue from 'vue'
import permission from './permission/permission'
import customPermission from './permission/customPermission'

Vue.directive('permission', permission)
Vue.directive('custom-permission', customPermission)

```

Now you can use both directives in templates:

```html
<!-- Original: removes element if not admin/editor -->
<button v-permission="['admin','editor']">Delete</button>

<!-- Extended: disables element for non-editors unless super-admin -->
<button v-custom-permission="['editor']">Advanced Settings</button>

```

### Adapting Complex Role Data Models

If your application uses complex role objects rather than simple strings (e.g., `{name: 'admin', scopes: ['read', 'write']}`), adapt the Vuex getter to flatten the data for the directive:

```javascript
// src/store/modules/user.js
const getters = {
  // Return array of role names for directive compatibility
  roleNames: state => state.roles.map(r => r.name),
  
  // Keep full objects for complex permission checks elsewhere
  roleObjects: state => state.roles
}

```

Then modify the directive to use `store.getters.roleNames` instead of `store.getters.roles`, ensuring compatibility with the existing array-based logic while preserving rich role data for other parts of the application.

## Summary

- The **`v-permission`** directive in [`src/directive/permission/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/directive/permission/permission.js) removes DOM elements when the user's Vuex roles array does not intersect with the required roles array.
- It uses **`Array.prototype.some()`** for OR-logic matching and physically removes nodes via `el.parentNode.removeChild(el)` rather than hiding them with CSS.
- The directive runs on both **`inserted`** and **`update`** lifecycle hooks to handle dynamic permission changes.
- It shares the **single source of truth** (`store.getters.roles`) with the router guard system in [`src/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/permission.js) and [`src/store/modules/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/store/modules/permission.js).
- You can extend the directive by creating custom implementations that import the store, reuse the core `checkPermission` logic, and add custom conditions like super-admin flags or resource-based checks.
- Complex role objects can be supported by flattening them in Vuex getters while keeping the directive's array-based interface intact.

## Frequently Asked Questions

### How does v-permission differ from the router permission guard?

The **`v-permission`** directive controls **UI element visibility** by removing individual buttons or components from the DOM based on roles, while the router permission guard in [`src/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/permission.js) controls **navigation access** by filtering route definitions before they render. The directive operates at the component level using `store.getters.roles`, whereas the router guard uses the same role list to generate accessible route maps via `filterAsyncRoutes` in [`src/store/modules/permission.js`](https://github.com/PanJiaChen/vue-element-admin/blob/main/src/store/modules/permission.js).

### Can I use v-permission with a single role string instead of an array?

No, the directive strictly requires an **array** of role strings. If you pass a single string like `v-permission="'admin'"`, the directive will throw the error: `need roles! Like v-permission="['admin','editor']"`. Always wrap role requirements in array syntax, even for single roles: `v-permission="['admin']"`.

### What happens if the user's roles change after the component is mounted?

The directive responds to role changes dynamically because it implements the **`update`** lifecycle hook. When the Vuex store updates `store.getters.roles` (for example, after a role refresh or user switch), the directive re-evaluates the permission check. If the new role set satisfies the requirement, the element remains; if not, it is removed from the DOM during the update cycle.

### Is it safe to rely solely on v-permission for security?

No, **`v-permission` is a UI convenience, not a security mechanism**. Since it runs entirely in the browser and can be bypassed by manipulating the DOM or JavaScript console, you must always enforce authorization on the server side for all sensitive operations. Use the directive to improve user experience by hiding irrelevant controls, but never depend on it to protect data or functionality from malicious users.