npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vue-nuxt-permission

v2.0.9

Published

A unified permission system for Vue 3 and Nuxt 3/4 with directives, guards, and async support.

Readme

Vue Nuxt Permission

A powerful, flexible permission management plugin for Vue 3 & Nuxt 3.

Provides a declarative directive (v-permission), route protection (globalGuard), permission evaluation utilities, Base64-encoded localStorage persistence, and caching.

GitHub License Vue Nuxt

Package: npm | Source: GitHub


Features

  • v-permission directive: Declarative show/hide/remove elements based on permissions
  • Route guards: Built-in globalGuard with fallback redirection
  • Permission utilities: Check permissions programmatically with hasPermission, hasAny, hasAll
  • Complex logic: Supports and, or, regex, startWith, exact matching modes
  • Security: Permissions stored as Base64-encoded values in localStorage
  • Performance: Built-in caching reduces re-evaluation overhead
  • Reactive: Works with both static arrays and reactive Ref<string[]>
  • TypeScript: Full type safety and intellisense support

Installation

Install Package

npm install vue-nuxt-permission

Or with Yarn or pnpm:

yarn add vue-nuxt-permission
# or
pnpm add vue-nuxt-permission

Setup for Nuxt 3

Add the module to your nuxt.config.ts:

export default defineNuxtConfig({
  modules: ["vue-nuxt-permission"],

  permission: {
    permissions: ["dashboard.view", "user.create", "user.edit"],
    developmentMode: process.env.NODE_ENV === "development",
  },
});

The plugin and directive are automatically registered across your entire app.

Alternatively, add via Nuxt CLI:

npx nuxi module add vue-nuxt-permission

Setup for Vue 3

Install the plugin in your app entry point:

// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { PermissionPlugin } from "vue-nuxt-permission";

const app = createApp(App);

app.use(PermissionPlugin, {
  permissions: ["user.create", "user.view", "user.edit", "admin.panel"],
  developmentMode: process.env.NODE_ENV === "development",
});

app.mount("#app");

The directive and composables are now available throughout your app.


Using the v-permission Directive

The v-permission directive controls element visibility based on user permissions.

Basic Usage

<template>
  <!-- Element is removed from DOM if permission is missing -->
  <button v-permission="'user.create'">Create User</button>

  <!-- Multiple permissions (OR logic by default - any permission matches) -->
  <button v-permission="['user.edit', 'user.update']">Edit User</button>

  <!-- Admin-only section -->
  <section v-permission="'admin.panel'">
    <h2>Admin Dashboard</h2>
  </section>
</template>

Understanding Remove vs Show Behavior

By default, elements without the required permission are removed from the DOM entirely.

Use the :show modifier to hide elements with display: none instead:

<template>
  <!-- Removed from DOM (not in page source) -->
  <button v-permission="'delete'">Delete</button>

  <!-- Hidden with CSS (in page source but not visible) -->
  <button v-permission:show="'delete'">Delete</button>
</template>

When to use each:

  • Default (remove): Use for sensitive features you don't want in the DOM at all
  • :show modifier: Use when you need the element in the DOM (for testing, styling, or keeping layout)

Directive Modifiers

| Modifier | Behavior | Use Case | | -------- | ----------------------------------- | ----------------------------------------- | | (none) | Removes element from DOM | Hide sensitive features completely | | :show | Hides with display: none | Keep in DOM for layout/testing | | .once | Checks permission only on mount | One-time permission check, ignore changes | | .lazy | Doesn't react to permission changes | Static permission check, ignore updates |

Example with modifiers:

<template>
  <!-- Removed from DOM, updates reactively -->
  <button v-permission="'edit'">Edit</button>

  <!-- Hidden instead of removed, updates reactively -->
  <button v-permission:show="'delete'">Delete</button>

  <!-- Removed from DOM, checked only once on mount -->
  <button v-permission.once="'admin'">Admin Setup</button>

  <!-- Hidden, won't react to permission changes -->
  <div v-permission:show.lazy="'premium'">Premium Feature</div>
</template>

Advanced: Complex Permission Objects

For more control, use permission objects with different evaluation modes:

<template>
  <!-- AND mode: User must have BOTH permissions -->
  <button
    v-permission="{ permissions: ['user.edit', 'user.approve'], mode: 'and' }"
  >
    Edit & Approve
  </button>

  <!-- Regex: Match patterns (e.g., any admin permission) -->
  <div v-permission="{ permissions: ['^admin\\..*'], mode: 'regex' }">
    All admin features here
  </div>

  <!-- Exact: Only exact matches (not substring) -->
  <button v-permission="{ permissions: ['admin'], mode: 'exact' }">
    Exact Admin
  </button>

  <!-- Start with pattern -->
  <section v-permission="{ permissions: ['user'], mode: 'startWith' }">
    User-related features
  </section>
</template>

Permission Utilities

Check permissions programmatically in your code using the composable or utility functions.

Using the Composable

Import usePermission() in any component:

import { usePermission } from "vue-nuxt-permission";

const { hasPermission, hasAny, hasAll } = usePermission();

// Check a single permission
if (await hasPermission("user.edit")) {
  // User can edit
}

// Check if user has ANY of the listed permissions
if (await hasAny(["admin.panel", "moderator.panel"])) {
  // User is either admin or moderator
}

// Check if user has ALL listed permissions
if (await hasAll(["user.edit", "user.approve"])) {
  // User can both edit and approve
}

Individual Utility Functions

You can also import functions directly:

import {
  hasPermission,
  clearPermissionCache,
  getCurrentPermissions,
} from "vue-nuxt-permission";

// Check permission (works outside components too)
const allowed = await hasPermission("user.delete");

// Get all current permissions
const perms = getCurrentPermissions();

// Clear the permission cache (useful after user login/logout)
clearPermissionCache();

// Update permissions dynamically
import { configurePermission } from "vue-nuxt-permission";
configurePermission(["user.view", "user.edit"]);

Sync vs Async behavior:

  • hasPermission() is async - always returns a Promise
  • Permissions are evaluated asynchronously, so always use await or .then()
  • Cache makes subsequent checks very fast

Controlling Element Visibility in Different Scenarios

Hide Admin Features

<template>
  <nav>
    <router-link to="/dashboard">Dashboard</router-link>
    <router-link to="/admin" v-permission="'admin.access'">Admin</router-link>
  </nav>
</template>

Show Different Content Based on Permissions

<template>
  <section v-if="isEditor">
    <!-- Show editing interface for editors -->
    <EditPanel v-permission="'content.edit'" />
  </section>

  <section v-if="isViewer" v-permission:show="'content.view'">
    <!-- Show read-only view for viewers -->
    <ViewPanel />
  </section>
</template>

<script setup>
import { usePermission } from "vue-nuxt-permission";

const { hasPermission } = usePermission();

const isEditor = await hasPermission("content.edit");
const isViewer = await hasPermission("content.view");
</script>

Disable Controls Instead of Hiding

<template>
  <button @click="deleteItem" :disabled="!canDelete">Delete</button>
</template>

<script setup>
import { usePermission } from "vue-nuxt-permission";

const { hasPermission } = usePermission();
const canDelete = await hasPermission("item.delete");

const deleteItem = async () => {
  if (await hasPermission("item.delete")) {
    // Delete the item
  }
};
</script>

Permission Modes Reference

Different evaluation modes for complex permission logic:

| Mode | Behavior | Example | | -------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------- | | or (default) | User needs ANY permission in the list | v-permission="['admin', 'editor']" → user is admin OR editor | | and | User needs ALL permissions in the list | v-permission="{ permissions: ['admin', 'verified'], mode: 'and' }" → admin AND verified | | exact | Exact string match only, no substring | v-permission="{ permissions: ['admin'], mode: 'exact' }" | | startWith | Permission starts with pattern | v-permission="{ permissions: ['user.'], mode: 'startWith' }" → any user.* permission | | endWith | Permission ends with pattern | v-permission="{ permissions: ['.edit'], mode: 'endWith' }" → any *.edit permission | | regex | Regular expression matching | v-permission="{ permissions: ['^admin\\..*'], mode: 'regex' }" → matches admin.* permissions |


Route Protection

Protect routes based on user authentication and permissions.

Using globalGuard with Vue Router

The globalGuard function automatically handles authentication and permission checks:

// router/index.ts
import { createRouter, createWebHistory } from "vue-router";
import { globalGuard } from "vue-nuxt-permission";

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // Public routes
    {
      path: "/login",
      component: () => import("@/views/Login.vue"),
      meta: { isAuthRoute: true }, // Redirect authenticated users away
    },

    // Protected routes (require authentication)
    {
      path: "/dashboard",
      component: () => import("@/views/Dashboard.vue"),
      meta: { requiresAuth: true },
    },

    // Routes with specific permissions
    {
      path: "/admin",
      component: () => import("@/views/Admin.vue"),
      meta: {
        requiresAuth: true,
        checkPermission: true,
        permissions: ["admin.access"], // User must have this permission
      },
    },
  ],
});

// Setup the guard
router.beforeEach((to, from, next) => {
  globalGuard(to, from, next, {
    authRoutes: ["/login", "/register"], // Routes only for unauthenticated users
    getAuthState: () => ({
      isAuthenticated: !!localStorage.getItem("token"),
      permissions: JSON.parse(localStorage.getItem("permissions") || "[]"),
    }),
  });
});

export default router;

Route Meta Fields:

| Meta Field | Type | Description | | ----------------- | ---------------- | -------------------------------------------------------------------------- | | requiresAuth | boolean | Route requires user to be logged in | | isAuthRoute | boolean | Route is for unauthenticated users (login/register); redirect if logged in | | checkPermission | boolean | Check permissions for this route | | permissions | string|string[] | Required permission(s) |

Common Patterns:

// Require authentication + specific permission
{
  path: "/admin",
  component: Admin,
  meta: {
    requiresAuth: true,
    checkPermission: true,
    permissions: ["admin.access"],
  },
}

// Require multiple permissions (user must have all)
{
  path: "/moderation",
  component: Moderation,
  meta: {
    requiresAuth: true,
    checkPermission: true,
    permissions: ["moderator.access", "moderator.ban"],
  },
}

// Public route
{
  path: "/about",
  component: About,
  // No meta needed - accessible to everyone
}

Using globalGuard with Nuxt 3 Middleware

In Nuxt 3, use a route middleware instead:

// middleware/auth.ts
export default defineRouteMiddleware(async (to, from) => {
  const { hasPermission } = usePermission();

  // Check if user is authenticated
  const token = useCookie("auth_token");
  if (!token.value) {
    return navigateTo("/login");
  }

  // Check if route requires specific permissions
  if (to.meta.requiresPermission) {
    const allowed = await hasPermission(to.meta.permissions as string);
    if (!allowed) {
      return navigateTo("/unauthorized");
    }
  }
});

Then apply to pages:

<!-- pages/admin.vue -->
<script setup>
definePageMeta({
  middleware: "auth",
  requiresPermission: true,
  permissions: "admin.access",
});
</script>

Creating Custom Guards

For advanced scenarios, create a custom guard with full control:

// guards/createGuard.ts
import { hasPermission } from "vue-nuxt-permission";

interface GuardOptions {
  loginPath?: string;
  homePath?: string;
  getAuthState: () => { isAuthenticated: boolean; permissions: string[] };
  onDenied?: (to: any, from: any) => void;
  onAllowed?: (to: any, from: any) => void;
}

export function createCustomGuard(options: GuardOptions) {
  return async (to: any, from: any, next: any) => {
    const state = options.getAuthState();

    // Not authenticated but route requires auth
    if (to.meta.requiresAuth && !state.isAuthenticated) {
      return next(options.loginPath || "/login");
    }

    // Already authenticated trying to access auth route
    if (to.meta.isAuthRoute && state.isAuthenticated) {
      return next(options.homePath || "/dashboard");
    }

    // Check permissions
    if (to.meta.checkPermission && to.meta.permissions) {
      const allowed = await hasPermission(
        to.meta.permissions,
        state.permissions
      );
      if (!allowed) {
        options.onDenied?.(to, from);
        return next("/unauthorized");
      }
      options.onAllowed?.(to, from);
    }

    return next();
  };
}

Usage:

import { createCustomGuard } from "@/guards/createGuard";

const guard = createCustomGuard({
  loginPath: "/login",
  homePath: "/dashboard",
  getAuthState: () => ({
    isAuthenticated: !!localStorage.getItem("token"),
    permissions: JSON.parse(localStorage.getItem("permissions") || "[]"),
  }),
  onDenied: (to) => console.warn("Access denied to:", to.path),
  onAllowed: (to) => console.log("Access granted to:", to.path),
});

router.beforeEach(guard);

Debugging & Troubleshooting

Enable Debug Logging

To see detailed logs during development, enable developmentMode:

// Nuxt 3
export default defineNuxtConfig({
  modules: ["vue-nuxt-permission"],
  permission: {
    permissions: ["user.view", "user.edit"],
    developmentMode: true, // Enable in development
  },
});

// Vue 3
app.use(PermissionPlugin, {
  permissions: ["user.view", "user.edit"],
  developmentMode: process.env.NODE_ENV === "development",
});

When enabled, you'll see console messages like:

[v-permission:core] Evaluated permission "user.edit": ALLOWED
[v-permission:core] Removing element from DOM

Common Issues & Solutions

Issue: Elements not hiding even though permission is missing

❌ Problem:
<button v-permission="'unknown.permission'">Delete</button>
<!-- Button still shows -->

✅ Solution: Make sure the permission exists in your configuration
export default defineNuxtConfig({
  permission: {
    permissions: ["user.edit", "unknown.permission"], // Add it
  },
});

Issue: Route guard not redirecting unauthorized users

❌ Problem:
// Guard isn't blocking access to /admin

✅ Solution: Ensure getAuthState returns correct values
router.beforeEach((to, from, next) => {
  globalGuard(to, from, next, {
    getAuthState: () => ({
      // Make sure this reflects actual auth state
      isAuthenticated: !!localStorage.getItem("token"),
      permissions: JSON.parse(localStorage.getItem("permissions") || "[]"),
    }),
  });
});

Issue: Permissions not updating after login

❌ Problem:
const { hasPermission } = usePermission();
// Permission still shows false after user logs in

✅ Solution: Update permissions after login, clear cache
import { configurePermission, clearPermissionCache } from "vue-nuxt-permission";

async function login(credentials) {
  const response = await fetch("/api/login", { method: "POST", body: JSON.stringify(credentials) });
  const user = await response.json();

  // Store new permissions
  localStorage.setItem("permissions", JSON.stringify(user.permissions));

  // Clear old cache and update
  clearPermissionCache();
  configurePermission(user.permissions);
}

Issue: Directive is reactive but I want it to check once on mount

❌ Problem:
<div v-permission="permission">Content</div>
<!-- Updates when permission changes -->

✅ Solution: Use the .once modifier
<div v-permission.once="permission">Content</div>
<!-- Only checks on mount -->

Issue: Element in DOM but I want it completely hidden

❌ Problem:
<div v-permission:show="'admin'">Admin</div>
<!-- Element is in DOM, only hidden with CSS -->

✅ Solution: Remove :show modifier
<div v-permission="'admin'">Admin</div>
<!-- Element removed from DOM completely -->

Issue: Composable says permission exists but directive hides element

❌ Problem:
const { hasPermission } = usePermission();
await hasPermission("edit") // Returns true
// But <div v-permission="'edit'"> is hidden

✅ Solution: This usually means the permissions aren't synchronized
// Make sure all calls use the same permission definitions
// Use the exact same string: "edit" vs "user.edit" are different

Quick Diagnostic Checklist

Before opening an issue:

  • [ ] Is developmentMode: true enabled to see logs?
  • [ ] Are permissions defined in the initial configuration?
  • [ ] Did you call clearPermissionCache() after updating permissions?
  • [ ] Are you using the exact same permission string everywhere? (case-sensitive)
  • [ ] Is the route guard's getAuthState() returning correct values?
  • [ ] Are you using await with hasPermission() since it's async?
  • [ ] Did you reload the page after updating permissions in localStorage?

Summary

Key Takeaways:

  1. Installation: Use Nuxt module or Vue plugin depending on your setup
  2. Directives: Use v-permission for UI elements, :show to hide instead of remove
  3. Permissions: Define all permissions upfront in configuration
  4. Utilities: Use hasPermission, hasAny, hasAll for programmatic checks
  5. Routes: Protect routes with metadata and globalGuard or custom middleware
  6. Debugging: Enable developmentMode and check console logs
  7. Updates: Clear cache with clearPermissionCache() after permission changes

For more detailed documentation, see the complete guide.


v2.0.0

  • Add .lazy and .once directive modifiers
  • Rename configurePermissionDirectiveconfigurePermission
  • Optional userPermissions param in hasPermission
  • Improved Base64 storage & caching
  • Enhanced examples and docs

Author

Kerolos Zakaria
PortfolioGitHubVS Code MarketplacenpmLinkedIn

License

MIT © 2025 Kerolos Zakaria