How to Create Custom Directives (e.g., `v-permission`) in Celeris Web
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.
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, 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 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, 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.
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, allows for clean registration in the main application entry point.
export function setupPermissionDirective(app: App) {
app.directive("permission", permissionDirective);
}
Open 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:
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.
<!-- 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. This helper wraps your directive in a plugin-like object that supports both direct registration and manual installation.
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) 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 inpermission.ts. - Permission Validation: Use
useAppPermission()fromapps/admin/src/store/modules/permission.tsto access the centralizedhasPermissionmethod. - DOM Manipulation: Remove unauthorized elements during the
mountedhook by callingel.parentNode?.removeChild(el). - Global Registration: Import and invoke
setupPermissionDirective(app)inapps/admin/src/main.tsbefore 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 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 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.
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 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.
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 →