# How to Create Custom Directives (e.g., `v-permission`) in Celeris Web

> Learn to create custom directives like v-permission in Celeris Web. Implement Vue directives with mounted hooks for permission checks and element removal. Register globally for streamlined access control.

- Repository: [Kirk Lin/celeris-web](https://github.com/kirklin/celeris-web)
- Tags: how-to-guide
- Published: 2026-03-05

---

**Create custom directives in Celeris Web by implementing a Vue directive with a `mounted` hook that checks permissions via `useAppPermission()` and removes unauthorized elements, then register it globally in [`apps/admin/src/main.ts`](https://github.com/kirklin/celeris-web/blob/main/apps/admin/src/main.ts).**

Celeris Web provides a flexible architecture for implementing role- and code-based UI permissions through custom Vue directives. The repository `kirklin/celeris-web` demonstrates this pattern with the existing `v-auth` directive, which you can mirror to create your own permission directives like `v-permission`.

## Understanding the Permission Directive Architecture

Custom directives in Celeris Web follow a standardized pattern that decouples UI rendering from permission logic. The system relies on the **permission store** located in [`apps/admin/src/store/modules/permission.ts`](https://github.com/kirklin/celeris-web/blob/main/apps/admin/src/store/modules/permission.ts), which exposes the `useAppPermission()` composable. This composable provides the `hasPermission` method that validates whether the current user possesses the required permission codes or roles.

When a directive is bound to an element, it executes during the `mounted` lifecycle hook. If the permission check fails, the directive removes the element from the DOM immediately, preventing unauthorized users from accessing restricted UI components. This approach ensures that permission checks happen at render time across the entire `apps/admin` application.

## Step-by-Step Implementation Guide

### Create the Directive Logic

Start by creating a new TypeScript file under `apps/admin/src/directives/`. Name it [`permission.ts`](https://github.com/kirklin/celeris-web/blob/main/permission.ts) to house your custom directive logic. This file will export the directive definition object and a registration helper.

The directive must implement Vue 3's `Directive` interface with a `mounted` hook. According to the source code in [`apps/admin/src/directives/permission.ts`](https://github.com/kirklin/celeris-web/blob/main/apps/admin/src/directives/permission.ts), the implementation should accept the element and binding value, then delegate the actual validation to the permission store.

### Implement the Permission Check

Inside your directive file, implement the core validation logic using the `useAppPermission` composable. The function receives the element and directive binding, extracts the required permission codes from `binding.value`, and removes the element if the user lacks authorization.

```typescript
import type { App, DirectiveBinding } from "vue";
import { useAppPermission } from "~/store/modules/permission";

function isPermission(el: Element, binding: DirectiveBinding<any>) {
  const { hasPermission } = useAppPermission();
  const required = binding.value;
  
  if (!required) return;
  
  if (!hasPermission(required)) {
    el.parentNode?.removeChild(el);
  }
}

export const permissionDirective = {
  mounted(el: Element, binding: DirectiveBinding<any>) {
    isPermission(el, binding);
  },
};

```

The `hasPermission` method accepts either a single permission code or an array of codes. It returns `true` if the user's permissions intersect with the required values, allowing you to support both single-role and multi-role scenarios within the same directive.

### Register the Directive Globally

Export a setup helper function that registers the directive on the Vue application instance. This pattern, as seen in [`apps/admin/src/directives/permission.ts`](https://github.com/kirklin/celeris-web/blob/main/apps/admin/src/directives/permission.ts), allows for clean registration in the main application entry point.

```typescript
export function setupPermissionDirective(app: App) {
  app.directive("permission", permissionDirective);
}

```

Open [`apps/admin/src/main.ts`](https://github.com/kirklin/celeris-web/blob/main/apps/admin/src/main.ts) and import this setup function. Invoke it before mounting the application to ensure the directive is available across all components:

```typescript
import { setupPermissionDirective } from "~/directives/permission";

// After creating the app instance
const app = createApp(App);

setupPermissionDirective(app);

app.mount("#app");

```

## Using the Directive in Templates

Once registered, apply the directive to any element using the `v-permission` syntax. The directive accepts permission codes from your constants or as string literals.

```html
<!-- Single permission check -->
<button v-permission="RoleConstants.ADMIN">Admin Only</button>

<!-- Multiple permissions (OR logic) -->
<button v-permission="[RoleConstants.USER, RoleConstants.EDITOR]">
  User or Editor
</button>

```

The bound value passes directly to `hasPermission`, which evaluates whether the current user's loaded permission codes—fetched via `permissionCodeApi` and stored in the permission module—include any of the required values.

## Advanced: Plugin-Style Installation

For reusable directive packages, Celeris Web provides the `withInstallDirective` utility in [`packages/web/utils/src/vue/install.ts`](https://github.com/kirklin/celeris-web/blob/main/packages/web/utils/src/vue/install.ts). This helper wraps your directive in a plugin-like object that supports both direct registration and manual installation.

```typescript
import { withInstallDirective } from "@celeris/vue-utils";

export const PermissionDirective = withInstallDirective(
  permissionDirective, 
  "permission"
);

// Usage in main.ts:
app.use(PermissionDirective);

```

The `withInstallDirective` function (defined at lines 33-36 of [`packages/web/utils/src/vue/install.ts`](https://github.com/kirklin/celeris-web/blob/main/packages/web/utils/src/vue/install.ts)) returns an object with an `install` method, enabling consistent plugin architecture across the Celeris Web ecosystem.

## Summary

- **Directive Location**: Create custom directives in `apps/admin/src/directives/` following the pattern established in [`permission.ts`](https://github.com/kirklin/celeris-web/blob/main/permission.ts).
- **Permission Validation**: Use `useAppPermission()` from [`apps/admin/src/store/modules/permission.ts`](https://github.com/kirklin/celeris-web/blob/main/apps/admin/src/store/modules/permission.ts) to access the centralized `hasPermission` method.
- **DOM Manipulation**: Remove unauthorized elements during the `mounted` hook by calling `el.parentNode?.removeChild(el)`.
- **Global Registration**: Import and invoke `setupPermissionDirective(app)` in [`apps/admin/src/main.ts`](https://github.com/kirklin/celeris-web/blob/main/apps/admin/src/main.ts) before mounting the application.
- **Flexible Values**: Support both single values and arrays in `v-permission="value"` bindings to accommodate different permission models.

## Frequently Asked Questions

### What is the difference between `v-auth` and `v-permission` in Celeris Web?

The existing `v-auth` directive and a custom `v-permission` directive serve identical architectural purposes in `kirklin/celeris-web`. The `v-auth` implementation in [`apps/admin/src/directives/permission.ts`](https://github.com/kirklin/celeris-web/blob/main/apps/admin/src/directives/permission.ts) provides the reference pattern for role-based access control. You can rename or duplicate this logic to create `v-permission` if your project requires semantic differentiation between authentication status and specific permission codes.

### How does the permission store integrate with custom directives?

The permission store in [`apps/admin/src/store/modules/permission.ts`](https://github.com/kirklin/celeris-web/blob/main/apps/admin/src/store/modules/permission.ts) loads user permission codes via `permissionCodeApi` and caches them in the Pinia state. When a custom directive calls `useAppPermission()`, it accesses this reactive store to evaluate permissions through the `hasPermission` method. This integration ensures that directives automatically respect the application's configured permission mode—whether role-based, backend-derived, or route-mapping—as defined in [`apps/admin/src/setting/projectSetting.ts`](https://github.com/kirklin/celeris-web/blob/main/apps/admin/src/setting/projectSetting.ts).

### Can I use `v-permission` with multiple roles or permission codes?

Yes, the `hasPermission` method accepts both single values and arrays. Pass an array of permission codes to the directive binding: `v-permission="[RoleConstants.ADMIN, RoleConstants.EDITOR]"`. The directive evaluates this as an OR condition, displaying the element if the user has any of the specified permissions. For AND logic requiring all permissions, you would need to extend the directive logic or chain multiple directives.

### Where should I register custom directives in a Celeris Web application?

Register custom directives in [`apps/admin/src/main.ts`](https://github.com/kirklin/celeris-web/blob/main/apps/admin/src/main.ts) using the `setupPermissionDirective(app)` pattern. Place the registration call after plugin initialization but before `app.mount()`. This ensures the directive is available globally across all `.vue` files without requiring local imports, maintaining consistency with how `v-auth` and other global directives are implemented in the repository.