/web-app-features

How to Add Custom Roles to Your Web App

Learn how to add custom roles to your web app easily with our step-by-step guide for better user management and security.

Book a free  consultation
4.9
Clutch rating 🌟
600+
Happy partners
17+
Countries served
190+
Team members
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

How to Add Custom Roles to Your Web App

How to Add Custom Roles to Your Web App

 

Why Custom Roles Matter

 

Remember the days when user permissions were simply "admin" or "user"? Those binary days are long gone. Modern web applications need nuanced access control where marketing teams can access analytics but not billing, and support staff can help customers without seeing sensitive data.

 

Custom roles are the foundation of a secure, scalable permission system that aligns with how real organizations actually operate. They're not just a technical feature—they're a business enabler.

 

Planning Your Role System

 

Before writing a single line of code, consider these essentials:

 

  • Role vs. Permission: Roles (like "Editor") are collections of individual permissions (like "edit\_posts"). This separation gives you flexibility.
  • Hierarchy: Will some roles inherit permissions from others? Or will each role have explicitly defined permissions?
  • Granularity: How specific do your permissions need to be? "manage_users" or more granular like "invite_users", "delete\_users", etc.

 

Implementation Approach

 

Let's break this down into practical steps with real code examples:

 

1. Database Structure

 

You'll typically need three tables:

 

-- Users table (you likely already have this)
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    -- other user fields
);

-- Roles table
CREATE TABLE roles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL,
    description VARCHAR(255)
);

-- Permissions table
CREATE TABLE permissions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL,
    description VARCHAR(255)
);

-- Role-Permission junction table
CREATE TABLE role_permissions (
    role_id INT,
    permission_id INT,
    PRIMARY KEY (role_id, permission_id),
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
);

-- User-Role junction table
CREATE TABLE user_roles (
    user_id INT,
    role_id INT,
    PRIMARY KEY (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);

 

2. Backend Implementation

 

Here's how to implement role checking in different frameworks:

 

Node.js with Express:

 

// middleware/auth.js
const checkPermission = (requiredPermission) => {
  return async (req, res, next) => {
    try {
      // Assuming you store user info in req.user after authentication
      const userId = req.user.id;
      
      // Query to check if user has the permission (through their roles)
      const hasPermission = await db.query(`
        SELECT 1 FROM users u
        JOIN user_roles ur ON u.id = ur.user_id
        JOIN role_permissions rp ON ur.role_id = rp.role_id
        JOIN permissions p ON rp.permission_id = p.id
        WHERE u.id = ? AND p.name = ?
        LIMIT 1
      `, [userId, requiredPermission]);
      
      if (hasPermission.length > 0) {
        return next(); // User has permission, proceed
      }
      
      return res.status(403).json({ error: "Access denied" });
    } catch (error) {
      return res.status(500).json({ error: "Server error" });
    }
  };
};

// Usage in routes
// routes/products.js
const express = require('express');
const router = express.Router();
const { checkPermission } = require('../middleware/auth');

router.post('/products', checkPermission('create_product'), (req, res) => {
  // Handle creating a product
});

router.delete('/products/:id', checkPermission('delete_product'), (req, res) => {
  // Handle deleting a product
});

module.exports = router;

 

PHP Laravel:

 

// Create a Gate in AuthServiceProvider.php
use Illuminate\Support\Facades\Gate;

public function boot()
{
    $this->registerPolicies();

    // Define a dynamic gate that checks permissions
    Gate::define('permission', function ($user, $permission) {
        return $user->hasPermission($permission);
    });
}

// Add methods to your User model
// User.php
public function roles()
{
    return $this->belongsToMany(Role::class, 'user_roles');
}

public function hasPermission($permission)
{
    foreach ($this->roles as $role) {
        if ($role->permissions->contains('name', $permission)) {
            return true;
        }
    }
    
    return false;
}

// In your controller
public function deleteProduct(Product $product)
{
    if (Gate::denies('permission', 'delete_product')) {
        abort(403);
    }
    
    // Delete product logic
}

// Or use the @can directive in Blade
@can('permission', 'edit_product')
    <a href="/products/{{ $product->id }}/edit">Edit</a>
@endcan

 

Python Django:

 

# permissions.py
from functools import wraps
from django.core.exceptions import PermissionDenied
from django.db.models import Q

def has_permission(user, permission_name):
    # Check if user has the permission through any of their roles
    return user.roles.filter(
        permissions__name=permission_name
    ).exists()

def permission_required(permission_name):
    def decorator(view_func):
        @wraps(view_func)
        def _wrapped_view(request, *args, **kwargs):
            if has_permission(request.user, permission_name):
                return view_func(request, *args, **kwargs)
            raise PermissionDenied
        return _wrapped_view
    return decorator

# Usage in views
# views.py
from .permissions import permission_required

@permission_required('create_product')
def create_product(request):
    # Create product logic
    pass

 

3. Frontend Implementation

 

The frontend needs to adapt based on user permissions:

 

React Example:

 

// components/PermissionGate.jsx
import React, { useContext } from 'react';
import { AuthContext } from '../contexts/AuthContext';

const PermissionGate = ({ permission, children }) => {
  const { user } = useContext(AuthContext);
  
  // Check if user has permission
  const hasPermission = user?.permissions?.includes(permission);
  
  // Render children only if user has permission
  return hasPermission ? children : null;
};

// Usage in components
import PermissionGate from './PermissionGate';

function ProductsPage() {
  return (
    <div>
      <h1>Products</h1>
      
      <PermissionGate permission="view_products">
        {/* Product listing UI */}
      </PermissionGate>
      
      <PermissionGate permission="create_product">
        <button>Add New Product</button>
      </PermissionGate>
    </div>
  );
}

 

4. Role Management UI

 

Don't forget to build an interface for administrators to manage roles:

 

  • Create/edit/delete roles
  • Assign permissions to roles
  • Assign roles to users

 

Common Pitfalls and How to Avoid Them

 

  • Performance issues: Permission checks on every request can be expensive. Consider caching user permissions in session/JWT tokens and refreshing when roles change.
  • Tight coupling: Don't hardcode role names throughout your codebase. Use permission checks instead of role checks to keep things flexible.
  • Security gaps: Always enforce permissions on both frontend AND backend. Frontend checks are for UI only; backend checks are your actual security.
  • Role explosion: As your app grows, resist creating a new role for every edge case. Aim for composable permissions that can be mixed into different roles.

 

Testing Your Role System

 

Don't skip this crucial part:

 

// Example Jest test for Express middleware
test('checkPermission middleware denies access without permission', async () => {
  // Mock user without required permission
  const req = {
    user: { id: 1 }
  };
  
  // Mock database response (no permissions)
  db.query = jest.fn().mockResolvedValue([]);
  
  const res = {
    status: jest.fn().mockReturnThis(),
    json: jest.fn()
  };
  
  const next = jest.fn();
  
  // Call middleware
  await checkPermission('edit_product')(req, res, next);
  
  // Verify it denied access
  expect(res.status).toHaveBeenCalledWith(403);
  expect(next).not.toHaveBeenCalled();
});

 

Scaling Your Role System

 

As your application grows:

 

  • Consider adding resource-based permissions (e.g., "edit\_post:123" to allow editing only post #123)
  • Implement role inheritance if your role hierarchy becomes complex
  • Add permission groups to make large permission sets more manageable
  • Consider pre-computing permissions into a denormalized table for faster access checks

 

Concluding Thoughts

 

A well-designed role system grows with your business. What begins as a simple permission setup should be architected to evolve into fine-grained access control without requiring a complete rewrite.

 

The code I've shown isn't just about technical implementation—it's about translating organizational structures and business requirements into secure, manageable access controls that both protect your data and empower your users to do their jobs effectively.

 

Remember: your role system is the digital reflection of how responsibility and trust are distributed in your organization. Build it with the same care you'd use to design your actual org chart.

Ship Custom Roles 10x Faster with RapidDev

Connect with our team to unlock the full potential of code solutions with a no-commitment consultation!

Book a Free Consultation

Top 3 Custom Roles Usecases

Explore the top 3 practical use cases for adding custom roles to enhance your web app’s functionality.

 

Role-Based Access Control for Data Sensitivity Levels

 

Create granular permission structures aligned with data sensitivity classifications, allowing organizations to enforce precise access boundaries. This prevents the common "all-or-nothing" approach where employees either get excessive permissions or lack necessary access to perform their jobs effectively.

 
  • This enables compliance with data protection regulations (GDPR, HIPAA, etc.) by ensuring only authorized personnel can access sensitive information.
  • A medical practice might create custom roles that allow nurses to view patient vitals but restrict access to billing information, while giving billing staff the opposite permission set.

 

Department-Specific Workflow Permissions

 

Define role templates tailored to specific business processes rather than generic system-wide roles. This creates natural boundaries that match how your organization actually functions, reducing both security risks and administrative overhead.

 
  • Marketing teams can access analytics dashboards and campaign tools without touching financial systems, while finance teams can process payments without accessing customer communication channels.
  • This approach reduces the cognitive load on administrators who otherwise need to remember which of dozens of individual permissions should be assigned to each department.

 

Temporary Project-Based Access

 

Create time-bound custom roles for cross-functional initiatives that require temporary elevation of permissions. This maintains security while enabling project teams to move efficiently without constantly requesting access.

 
  • For a system migration project, create a dedicated role with elevated access that automatically expires after the project deadline, reducing the risk of forgotten persistent permissions.
  • This approach balances security with business agility — enforcing the principle of least privilege while acknowledging that rigid permission structures can become bottlenecks to productivity.


Recognized by the best

Trusted by 600+ businesses globally

From startups to enterprises and everything in between, see for yourself our incredible impact.

RapidDev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with.

They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

Arkady
CPO, Praction
Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost.

He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Donald Muir
Co-Founder, Arc
RapidDev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space.

They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Mat Westergreen-Thorne
Co-CEO, Grantify
RapidDev is an excellent developer for custom-code solutions.

We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Emmanuel Brown
Co-Founder, Church Real Estate Marketplace
Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 

This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Samantha Fekete
Production Manager, Media Production Company
The pSEO strategy executed by RapidDev is clearly driving meaningful results.

Working with RapidDev has delivered measurable, year-over-year growth. Comparing the same period, clicks increased by 129%, impressions grew by 196%, and average position improved by 14.6%. Most importantly, qualified contact form submissions rose 350%, excluding spam.

Appreciation as well to Matt Graham for championing the collaboration!

Michael W. Hammond
Principal Owner, OCD Tech

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â