diff --git a/src/configs/postgres.js b/src/configs/postgres.js
index d64042f74..afc000c63 100644
--- a/src/configs/postgres.js
+++ b/src/configs/postgres.js
@@ -26,6 +26,19 @@ module.exports = {
 			updatedAt: 'updated_at',
 			deletedAt: 'deleted_at',
 		},
+		pool: {
+			max: parseInt(process.env.DB_POOL_MAX),
+			min: parseInt(process.env.DB_POOL_MIN),
+			acquire: parseInt(process.env.DB_POOL_ACQUIRE_MS),
+			idle: parseInt(process.env.DB_POOL_IDLE_MS),
+			evict: parseInt(process.env.DB_POOL_EVICT_MS),
+		},
+		dialectOptions: {
+			application_name: process.env.APP_NAME,
+			keepAlive: true,
+			statement_timeout: parseInt(process.env.PG_STATEMENT_TIMEOUT_MS),
+			idle_in_transaction_session_timeout: parseInt(process.env.PG_IDLE_TX_TIMEOUT_MS),
+		},
 		//logging: false,
 		defaultOrgId: parseInt(process.env.DEFAULT_ORG_ID),
 	},
@@ -37,6 +50,19 @@ module.exports = {
 	production: {
 		url: process.env.DATABASE_URL,
 		dialect: 'postgres',
+		pool: {
+			max: parseInt(process.env.DB_POOL_MAX),
+			min: parseInt(process.env.DB_POOL_MIN),
+			acquire: parseInt(process.env.DB_POOL_ACQUIRE_MS),
+			idle: parseInt(process.env.DB_POOL_IDLE_MS),
+			evict: parseInt(process.env.DB_POOL_EVICT_MS),
+		},
+		dialectOptions: {
+			application_name: process.env.APP_NAME,
+			keepAlive: true,
+			statement_timeout: parseInt(process.env.PG_STATEMENT_TIMEOUT_MS),
+			idle_in_transaction_session_timeout: parseInt(process.env.PG_IDLE_TX_TIMEOUT_MS),
+		},
 		defaultOrgId: parseInt(process.env.DEFAULT_ORG_ID),
 	},
 }
diff --git a/src/constants/common.js b/src/constants/common.js
index 68580fbe8..03f4eccba 100644
--- a/src/constants/common.js
+++ b/src/constants/common.js
@@ -41,7 +41,9 @@ module.exports = {
 	refreshTokenLimit: 3,
 	otpExpirationTime: process.env.OTP_EXP_TIME, // In Seconds,
 	ADMIN_ROLE: 'admin',
+	TENANT_ADMIN_ROLE: 'tenant_admin',
 	ORG_ADMIN_ROLE: 'org_admin',
+	TENANT_ADMIN_ROLE: 'tenant_admin',
 	USER_ROLE: 'user',
 	SESSION_MANAGER_ROLE: 'session_manager',
 	PUBLIC_ROLE: 'public',
diff --git a/src/controllers/v1/admin.js b/src/controllers/v1/admin.js
index 1067e2b0e..c9e7b6e82 100644
--- a/src/controllers/v1/admin.js
+++ b/src/controllers/v1/admin.js
@@ -38,6 +38,49 @@ module.exports = class Admin {
 		}
 	}
 
+	/**
+	 * Assigns a role to a user.
+	 *
+	 * Extracts `organization_code` and `tenant_code` from `req.decodedToken`
+	 * instead of the request body and delegates the actual role assignment
+	 * to the service layer. Performs only an admin-access check.
+	 *
+	 * @async
+	 * @function assignRole
+	 * @param {import('express').Request} req - Express request object
+	 * @param {Object} req.decodedToken - Decoded JWT payload
+	 * @param {string} req.decodedToken.organization_code - Organization code from token
+	 * @param {string} req.decodedToken.tenant_code - Tenant code from token
+	 * @param {Object} req.body - Request body containing assignment data
+	 * @param {number|string} req.body.user_id - Target user ID
+	 * @param {number|string} req.body.role_id - Role ID to assign
+	 * @param {number|string} [req.body.organization_id] - Optional organization ID
+	 * @returns {Promise<Object>} Service response object
+	 */
+
+	async assignRole(req) {
+		try {
+			if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ADMIN_ROLE)) {
+				throw responses.failureResponse({
+					message: 'USER_IS_NOT_A_ADMIN',
+					statusCode: httpStatusCode.bad_request,
+					responseCode: 'CLIENT_ERROR',
+				})
+			}
+
+			const params = {
+				organization_code: req.decodedToken.organization_code,
+				tenant_code: req.decodedToken.tenant_code,
+			}
+
+			// Pass token-derived params separately per new service pattern
+			const user = await adminService.assignRole(params, req.body)
+			return user
+		} catch (error) {
+			return error
+		}
+	}
+
 	/**
 	 * create admin users
 	 * @method
diff --git a/src/controllers/v1/notification.js b/src/controllers/v1/notification.js
index 121036cef..ffdfbfb49 100644
--- a/src/controllers/v1/notification.js
+++ b/src/controllers/v1/notification.js
@@ -29,7 +29,13 @@ module.exports = class NotificationTemplate {
 
 	async template(req) {
 		try {
-			if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, [common.ADMIN_ROLE, common.ORG_ADMIN_ROLE])) {
+			if (
+				!utilsHelper.validateRoleAccess(req.decodedToken.roles, [
+					common.ADMIN_ROLE,
+					common.ORG_ADMIN_ROLE,
+					common.TENANT_ADMIN_ROLE,
+				])
+			) {
 				throw responses.failureResponse({
 					message: 'USER_IS_NOT_A_ADMIN',
 					statusCode: httpStatusCode.bad_request,
diff --git a/src/controllers/v1/org-admin.js b/src/controllers/v1/org-admin.js
index d39cf2167..926661b4d 100644
--- a/src/controllers/v1/org-admin.js
+++ b/src/controllers/v1/org-admin.js
@@ -21,7 +21,12 @@ module.exports = class OrgAdmin {
 	 */
 	async bulkUserCreate(req) {
 		try {
-			if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) {
+			if (
+				!utilsHelper.validateRoleAccess(req.decodedToken.roles, [
+					common.ORG_ADMIN_ROLE,
+					common.TENANT_ADMIN_ROLE,
+				])
+			) {
 				throw responses.failureResponse({
 					message: 'USER_IS_NOT_A_ADMIN',
 					statusCode: httpStatusCode.bad_request,
@@ -48,7 +53,12 @@ module.exports = class OrgAdmin {
 	 */
 	async getBulkInvitesFilesList(req) {
 		try {
-			if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) {
+			if (
+				!utilsHelper.validateRoleAccess(req.decodedToken.roles, [
+					common.ORG_ADMIN_ROLE,
+					common.TENANT_ADMIN_ROLE,
+				])
+			) {
 				throw responses.failureResponse({
 					message: 'USER_IS_NOT_A_ADMIN',
 					statusCode: httpStatusCode.bad_request,
@@ -72,7 +82,12 @@ module.exports = class OrgAdmin {
 	 */
 	async getRequestDetails(req) {
 		try {
-			if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) {
+			if (
+				!utilsHelper.validateRoleAccess(req.decodedToken.roles, [
+					common.ORG_ADMIN_ROLE,
+					common.TENANT_ADMIN_ROLE,
+				])
+			) {
 				throw responses.failureResponse({
 					message: 'USER_IS_NOT_A_ADMIN',
 					statusCode: httpStatusCode.bad_request,
@@ -102,7 +117,12 @@ module.exports = class OrgAdmin {
 	 */
 	async getRequests(req) {
 		try {
-			if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) {
+			if (
+				!utilsHelper.validateRoleAccess(req.decodedToken.roles, [
+					common.ORG_ADMIN_ROLE,
+					common.TENANT_ADMIN_ROLE,
+				])
+			) {
 				throw responses.failureResponse({
 					message: 'USER_IS_NOT_A_ADMIN',
 					statusCode: httpStatusCode.bad_request,
@@ -129,7 +149,12 @@ module.exports = class OrgAdmin {
 	 */
 	async updateRequestStatus(req) {
 		try {
-			if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) {
+			if (
+				!utilsHelper.validateRoleAccess(req.decodedToken.roles, [
+					common.ORG_ADMIN_ROLE,
+					common.TENANT_ADMIN_ROLE,
+				])
+			) {
 				throw responses.failureResponse({
 					message: 'USER_IS_NOT_A_ADMIN',
 					statusCode: httpStatusCode.bad_request,
@@ -153,7 +178,12 @@ module.exports = class OrgAdmin {
 	 */
 	async deactivateUser(req) {
 		try {
-			if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) {
+			if (
+				!utilsHelper.validateRoleAccess(req.decodedToken.roles, [
+					common.ORG_ADMIN_ROLE,
+					common.TENANT_ADMIN_ROLE,
+				])
+			) {
 				throw responses.failureResponse({
 					message: 'USER_IS_NOT_A_ADMIN',
 					statusCode: httpStatusCode.bad_request,
@@ -178,7 +208,12 @@ module.exports = class OrgAdmin {
 
 	async inheritEntityType(req) {
 		try {
-			if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) {
+			if (
+				!utilsHelper.validateRoleAccess(req.decodedToken.roles, [
+					common.ORG_ADMIN_ROLE,
+					common.TENANT_ADMIN_ROLE,
+				])
+			) {
 				throw responses.failureResponse({
 					message: 'USER_IS_NOT_A_ADMIN',
 					statusCode: httpStatusCode.bad_request,
diff --git a/src/controllers/v1/organization-feature.js b/src/controllers/v1/organization-feature.js
index 10d3749f5..da6f904df 100644
--- a/src/controllers/v1/organization-feature.js
+++ b/src/controllers/v1/organization-feature.js
@@ -89,7 +89,8 @@ module.exports = class OrganizationFeature {
 				  )
 				: await organizationFeatureService.list(
 						req.decodedToken.tenant_code,
-						req.decodedToken.organization_code
+						req.decodedToken.organization_code,
+						req.decodedToken.roles
 				  )
 		} catch (error) {
 			return error
diff --git a/src/controllers/v1/organization.js b/src/controllers/v1/organization.js
index e56eb6ea5..dddc9a15f 100644
--- a/src/controllers/v1/organization.js
+++ b/src/controllers/v1/organization.js
@@ -31,7 +31,7 @@ module.exports = class Organization {
 			let isAdmin = false
 			const roles = req.decodedToken.roles
 			if (roles && roles.length > 0) {
-				isAdmin = utilsHelper.validateRoleAccess(roles, common.ADMIN_ROLE)
+				isAdmin = utilsHelper.validateRoleAccess(roles, [common.ADMIN_ROLE, common.TENANT_ADMIN_ROLE])
 			}
 
 			if (!isAdmin) {
@@ -68,7 +68,7 @@ module.exports = class Organization {
 
 			if (roles && roles.length > 0) {
 				isAdmin = utilsHelper.validateRoleAccess(roles, common.ADMIN_ROLE)
-				isOrgAdmin = utilsHelper.validateRoleAccess(roles, common.ORG_ADMIN_ROLE)
+				isOrgAdmin = utilsHelper.validateRoleAccess(roles, [common.ORG_ADMIN_ROLE, common.TENANT_ADMIN_ROLE])
 			}
 
 			if (req.params.id != req.decodedToken.organization_id && isOrgAdmin) {
@@ -170,6 +170,7 @@ module.exports = class Organization {
 				isAdmin =
 					utilsHelper.validateRoleAccess(roles, common.ADMIN_ROLE) ||
 					utilsHelper.validateRoleAccess(roles, common.ORG_ADMIN_ROLE) ||
+					utilsHelper.validateRoleAccess(roles, common.TENANT_ADMIN_ROLE) ||
 					false
 			}
 			const result = await orgService.details(
@@ -215,7 +216,7 @@ module.exports = class Organization {
 
 			if (roles && roles.length > 0) {
 				isAdmin = utilsHelper.validateRoleAccess(roles, common.ADMIN_ROLE)
-				isOrgAdmin = utilsHelper.validateRoleAccess(roles, common.ORG_ADMIN_ROLE)
+				isOrgAdmin = utilsHelper.validateRoleAccess(roles, [common.ORG_ADMIN_ROLE, common.TENANT_ADMIN_ROLE])
 			}
 			if (!isAdmin && !isOrgAdmin) {
 				throw responses.failureResponse({
@@ -260,7 +261,7 @@ module.exports = class Organization {
 
 			if (roles && roles.length > 0) {
 				isAdmin = utilsHelper.validateRoleAccess(roles, common.ADMIN_ROLE)
-				isOrgAdmin = utilsHelper.validateRoleAccess(roles, common.ORG_ADMIN_ROLE)
+				isOrgAdmin = utilsHelper.validateRoleAccess(roles, [common.ORG_ADMIN_ROLE, common.TENANT_ADMIN_ROLE])
 			}
 			if (!isAdmin && !isOrgAdmin) {
 				throw responses.failureResponse({
diff --git a/src/database/migrations/20251002164809-create-feature-role-mapping-table.js b/src/database/migrations/20251002164809-create-feature-role-mapping-table.js
new file mode 100644
index 000000000..49ae14b78
--- /dev/null
+++ b/src/database/migrations/20251002164809-create-feature-role-mapping-table.js
@@ -0,0 +1,61 @@
+'use strict'
+
+/** @type {import('sequelize-cli').Migration} */
+module.exports = {
+	async up(queryInterface, Sequelize) {
+		await queryInterface.createTable('feature_role_mapping', {
+			id: {
+				type: Sequelize.INTEGER,
+				allowNull: false,
+				autoIncrement: true,
+			},
+			feature_code: {
+				type: Sequelize.STRING,
+				allowNull: false,
+			},
+			role_title: {
+				type: Sequelize.STRING,
+				allowNull: false,
+			},
+			organization_code: {
+				type: Sequelize.STRING,
+				allowNull: false,
+			},
+			tenant_code: {
+				type: Sequelize.STRING,
+				allowNull: false,
+			},
+			created_by: {
+				type: Sequelize.INTEGER,
+				allowNull: false,
+			},
+			updated_by: {
+				type: Sequelize.INTEGER,
+				allowNull: true,
+			},
+			created_at: {
+				allowNull: false,
+				type: Sequelize.DATE,
+			},
+			updated_at: {
+				allowNull: false,
+				type: Sequelize.DATE,
+			},
+			deleted_at: {
+				allowNull: true,
+				type: Sequelize.DATE,
+			},
+		})
+
+		// Add composite primary key
+		await queryInterface.addConstraint('feature_role_mapping', {
+			fields: ['id', 'tenant_code'],
+			type: 'primary key',
+			name: 'pk_feature_role_mapping',
+		})
+	},
+
+	async down(queryInterface) {
+		await queryInterface.dropTable('feature_role_mapping')
+	},
+}
diff --git a/src/database/migrations/20251002164938-add-new-feature.js b/src/database/migrations/20251002164938-add-new-feature.js
new file mode 100644
index 000000000..de0ca030d
--- /dev/null
+++ b/src/database/migrations/20251002164938-add-new-feature.js
@@ -0,0 +1,87 @@
+'use strict'
+
+/** @type {import('sequelize-cli').Migration} */
+module.exports = {
+	async up(queryInterface, Sequelize) {
+		// Check if SCP feature already exists
+		const scpFeature = await queryInterface.sequelize.query("SELECT code FROM features WHERE code = 'scp'", {
+			type: Sequelize.QueryTypes.SELECT,
+		})
+
+		// Insert SCP feature if it doesn't exist
+		if (scpFeature.length === 0) {
+			await queryInterface.bulkInsert('features', [
+				{
+					code: 'scp',
+					label: 'Self Creation Portal',
+					display_order: 9,
+					description: 'SCP capability',
+					created_at: new Date(),
+					updated_at: new Date(),
+				},
+			])
+		}
+
+		// Get all organizations
+		const tenants = await queryInterface.sequelize.query('SELECT code FROM tenants WHERE deleted_at IS NULL', {
+			type: Sequelize.QueryTypes.SELECT,
+		})
+
+		// Get default orgs for each organization
+		const organizationFeatureData = []
+		for (const tenant of tenants) {
+			const orgExist = await queryInterface.sequelize.query(
+				'SELECT code, tenant_code FROM organizations WHERE deleted_at IS NULL AND tenant_code = :tenantCode AND code = :orgCode',
+				{
+					replacements: {
+						tenantCode: tenant.code,
+						orgCode: process.env.DEFAULT_ORGANISATION_CODE || 'default_code',
+					},
+					type: Sequelize.QueryTypes.SELECT,
+				}
+			)
+
+			if (orgExist.length > 0) {
+				const orgFeatureExist = await queryInterface.sequelize.query(
+					'SELECT organization_code FROM organization_features WHERE tenant_code = :tenantCode AND organization_code = :orgCode AND feature_code = :featureCode',
+					{
+						replacements: {
+							tenantCode: tenant.code,
+							orgCode: process.env.DEFAULT_ORGANISATION_CODE || 'default_code',
+							featureCode: 'scp',
+						},
+						type: Sequelize.QueryTypes.SELECT,
+					}
+				)
+				if (orgFeatureExist.length === 0) {
+					organizationFeatureData.push({
+						organization_code: process.env.DEFAULT_ORGANISATION_CODE || 'default_code',
+						tenant_code: tenant.code,
+						feature_code: 'scp',
+						feature_name: 'SCP',
+						enabled: true,
+						display_order: 9,
+						created_at: new Date(),
+						updated_at: new Date(),
+					})
+				}
+			}
+		}
+
+		if (organizationFeatureData.length > 0) {
+			await queryInterface.bulkInsert('organization_features', organizationFeatureData)
+		}
+	},
+
+	async down(queryInterface) {
+		// Remove organization_feature records only for the SCP feature
+		await queryInterface.bulkDelete('organization_features', {
+			feature_code: 'scp',
+		})
+
+		// Remove SCP feature
+		await queryInterface.bulkDelete('features', {
+			code: 'scp',
+		})
+	},
+}
diff --git a/src/database/migrations/20251002165109-map-roles-to-features.js b/src/database/migrations/20251002165109-map-roles-to-features.js
new file mode 100644
index 000000000..48bb5d1c0
--- /dev/null
+++ b/src/database/migrations/20251002165109-map-roles-to-features.js
@@ -0,0 +1,139 @@
+'use strict'
+
+/** @type {import('sequelize-cli').Migration} */
+module.exports = {
+	async up(queryInterface, Sequelize) {
+		// Fetch all enabled features for all organizations from the `organization_features` table.
+		const organizationsFeatures = await queryInterface.sequelize.query(
+			'SELECT feature_code, tenant_code, organization_code FROM organization_features WHERE deleted_at IS NULL',
+			{ type: Sequelize.QueryTypes.SELECT }
+		)
+
+		// Fetch all possible features available in the system.
+		const allFeatures = await queryInterface.sequelize.query('SELECT code FROM features', {
+			type: Sequelize.QueryTypes.SELECT,
+		})
+		// Define a baseline set of default features.
+		let defaultFeatures = ['project', 'mentoring', 'survey', 'observation', 'reports', 'mitra', 'programs']
+		// Define the feature set for each user role. This is the core mapping logic.
+		const roleMappings = {
+			content_creator: ['scp', ...defaultFeatures],
+			reviewer: ['scp', 'learn', ...defaultFeatures],
+			creator: ['scp', 'learn', ...defaultFeatures],
+			rollout_manager: ['scp', ...defaultFeatures],
+			program_manager: ['scp', ...defaultFeatures],
+			program_designer: ['scp', ...defaultFeatures],
+			learner: ['learn', ...defaultFeatures],
+			mentee: defaultFeatures,
+			mentor: defaultFeatures,
+			session_manager: defaultFeatures,
+			report_admin: ['reports', 'learn', ...defaultFeatures],
+			state_manager: ['reports', 'learn', ...defaultFeatures],
+			district_manager: ['reports', 'learn', ...defaultFeatures],
+			admin: allFeatures.map((f) => f.code), // Admins get all features.
+			org_admin: allFeatures.map((f) => f.code), // Org admins also get all features.
+			tenant_admin: allFeatures.map((f) => f.code), // Tenant admins also get all features.
+		}
+
+		// Create a lookup map (`orgFeaturesMap`) for efficient access to enabled features per organization.
+		const orgFeaturesMap = new Map()
+		organizationsFeatures.forEach((item) => {
+			const orgKey = `${item.organization_code}|${item.tenant_code}`
+			if (!orgFeaturesMap.has(orgKey)) {
+				orgFeaturesMap.set(orgKey, new Set())
+			}
+			orgFeaturesMap.get(orgKey).add(item.feature_code)
+		})
+
+		// Extract unique organizations
+		const uniqueOrgsMap = new Map()
+		organizationsFeatures.forEach((item) => {
+			const key = `${item.organization_code}|${item.tenant_code}`
+			if (!uniqueOrgsMap.has(key)) {
+				uniqueOrgsMap.set(key, {
+					organization_code: item.organization_code,
+					tenant_code: item.tenant_code,
+				})
+			}
+		})
+		const uniqueOrgs = Array.from(uniqueOrgsMap.values())
+
+		console.log(`Total organization_features rows: ${organizationsFeatures.length}`)
+		console.log(`Unique organizations: ${uniqueOrgs.length}`)
+
+		// Prepare arrays to collect mappings and track duplicates/skipped
+		const featureRoleMappingData = []
+		const seenMappings = new Set()
+		const skippedMappings = []
+
+		// Main loop: Process each organization to create feature-role mappings
+		for (const org of uniqueOrgs) {
+			const orgKey = `${org.organization_code}|${org.tenant_code}`
+			const enabledFeatures = orgFeaturesMap.get(orgKey) || new Set()
+
+			// Fetch roles available for this tenant
+			const tenantRoles = await queryInterface.sequelize.query(
+				'SELECT title FROM user_roles WHERE tenant_code = ? AND deleted_at IS NULL',
+				{
+					replacements: [org.tenant_code],
+					type: Sequelize.QueryTypes.SELECT,
+				}
+			)
+			const tenantRoleTitles = tenantRoles.map((role) => role.title)
+
+			// Loop through each role and its features
+			for (const [role, features] of Object.entries(roleMappings)) {
+				if (tenantRoleTitles.includes(role)) {
+					for (const featureCode of features) {
+						const key = `${featureCode}|${role}|${org.organization_code}|${org.tenant_code}`
+
+						// ✅ Check if feature exists in organization_features
+						if (!enabledFeatures.has(featureCode)) {
+							skippedMappings.push({
+								reason: 'Feature not enabled for org',
+								feature: featureCode,
+								role: role,
+								org: org.organization_code,
+								tenant: org.tenant_code,
+							})
+							continue // Skip this mapping
+						}
+
+						if (!seenMappings.has(key)) {
+							seenMappings.add(key)
+							featureRoleMappingData.push({
+								role_title: role,
+								feature_code: featureCode,
+								organization_code: org.organization_code,
+								tenant_code: org.tenant_code,
+								created_by: 0,
+								updated_by: 0,
+								created_at: new Date(),
+								updated_at: new Date(),
+							})
+						}
+					}
+				}
+			}
+		}
+
+		console.log(`Total mappings to insert: ${featureRoleMappingData.length}`)
+		console.log(`Skipped mappings: ${skippedMappings.length}`)
+
+		// Log first few skipped for debugging
+		if (skippedMappings.length > 0) {
+			console.log('Sample skipped mappings:')
+			console.log(skippedMappings.slice(0, 10))
+		}
+
+		// Insert all valid mappings into the database
+		if (featureRoleMappingData.length > 0) {
+			await queryInterface.bulkInsert('feature_role_mapping', featureRoleMappingData)
+		}
+	},
+
+	// Revert the migration by deleting all feature-role mappings
+	async down(queryInterface) {
+		await queryInterface.bulkDelete('feature_role_mapping', null, {})
+	},
+}
diff --git a/src/database/migrations/20251003155747-add-feature-role-mapping-constraints.js b/src/database/migrations/20251003155747-add-feature-role-mapping-constraints.js
new file mode 100644
index 000000000..3157f3fb7
--- /dev/null
+++ b/src/database/migrations/20251003155747-add-feature-role-mapping-constraints.js
@@ -0,0 +1,80 @@
+'use strict'
+
+/** @type {import('sequelize-cli').Migration} */
+module.exports = {
+	async up(queryInterface, Sequelize) {
+		// Add foreign key constraint for feature_code
+		await queryInterface.addConstraint('feature_role_mapping', {
+			fields: ['feature_code'],
+			type: 'foreign key',
+			name: 'fk_feature_role_mapping_feature_code',
+			references: {
+				table: 'features',
+				field: 'code',
+			},
+			onUpdate: 'CASCADE',
+			onDelete: 'CASCADE',
+		})
+
+		// Add foreign key constraint for tenant_code
+		await queryInterface.addConstraint('feature_role_mapping', {
+			fields: ['tenant_code'],
+			type: 'foreign key',
+			name: 'fk_feature_role_mapping_tenant_code',
+			references: {
+				table: 'tenants',
+				field: 'code',
+			},
+			onUpdate: 'CASCADE',
+			onDelete: 'CASCADE',
+		})
+
+		// Add composite foreign key for organization_code (organization_code, tenant_code) -> organizations (code, tenant_code)
+		await queryInterface.sequelize.query(`
+			ALTER TABLE feature_role_mapping
+			ADD CONSTRAINT fk_feature_role_mapping_organization_code
+			FOREIGN KEY (organization_code, tenant_code)
+			REFERENCES organizations (code, tenant_code)
+			ON UPDATE CASCADE
+			ON DELETE CASCADE;
+		`)
+
+		// Add composite foreign key for role_title (tenant_code, role_title) -> user_roles (tenant_code, title)
+		//commenting this as of now because of issue in user_roles table which is using the organization_id as foreign key
+		// await queryInterface.sequelize.query(`
+		// 	ALTER TABLE feature_role_mapping
+		// 	ADD CONSTRAINT fk_feature_role_mapping_role_title
+		// 	FOREIGN KEY (tenant_code, role_title)
+		// 	REFERENCES user_roles (tenant_code, title)
+		// 	ON UPDATE CASCADE
+		// 	ON DELETE NO ACTION;
+		// `)
+
+		// Unique constraint for feature_code, role_title, organization_code, tenant_code
+		await queryInterface.sequelize.query(`
+			CREATE UNIQUE INDEX feature_role_org_tenant_unique
+			ON feature_role_mapping (feature_code, role_title, organization_code, tenant_code)
+			WHERE deleted_at IS NULL;
+		`)
+
+		await queryInterface.sequelize.query(`
+			ALTER TABLE feature_role_mapping
+			ADD CONSTRAINT fk_org_feature_role_mapping_organization_code
+			FOREIGN KEY (feature_code, tenant_code, organization_code)
+			REFERENCES organization_features (feature_code, tenant_code, organization_code)
+			ON UPDATE CASCADE
+			ON DELETE CASCADE;
+		`)
+	},
+
+	async down(queryInterface, Sequelize) {
+		// Drop foreign key constraints
+		await queryInterface.removeConstraint('feature_role_mapping', 'fk_feature_role_mapping_tenant_code')
+		await queryInterface.removeConstraint('feature_role_mapping', 'fk_feature_role_mapping_organization_code')
+		// await queryInterface.removeConstraint('feature_role_mapping', 'fk_feature_role_mapping_role_title')
+		await queryInterface.removeConstraint('feature_role_mapping', 'fk_feature_role_mapping_feature_code')
+		await queryInterface.removeConstraint('feature_role_mapping', 'fk_org_feature_role_mapping_organization_code')
+
+		await queryInterface.sequelize.query('DROP INDEX IF EXISTS feature_role_org_tenant_unique;')
+	},
+}
diff --git a/src/database/migrations/20251022160602-add-tenant-admin-role.js b/src/database/migrations/20251022160602-add-tenant-admin-role.js
new file mode 100644
index 000000000..7c1fe37f9
--- /dev/null
+++ b/src/database/migrations/20251022160602-add-tenant-admin-role.js
@@ -0,0 +1,139 @@
+'use strict'
+
+module.exports = {
+	up: async (queryInterface, Sequelize) => {
+		const transaction = await queryInterface.sequelize.transaction()
+
+		try {
+			// Step 1: Get all unique organization_ids per tenant from existing user_roles
+			const [orgsByTenant] = await queryInterface.sequelize.query(
+				`SELECT DISTINCT tenant_code, organization_id 
+         FROM user_roles 
+         WHERE deleted_at IS NULL 
+         AND tenant_code IN (
+           SELECT code FROM tenants WHERE deleted_at IS NULL
+         )
+         ORDER BY tenant_code, organization_id`,
+				{ transaction }
+			)
+
+			if (orgsByTenant.length === 0) {
+				console.log('No active organizations found. Skipping migration.')
+				await transaction.commit()
+				return
+			}
+
+			console.log(`Found ${orgsByTenant.length} organization-tenant combinations`)
+
+			// Step 2: Insert tenant_admin role for each tenant-organization combination
+			const userRoleInserts = orgsByTenant.map((org) => ({
+				title: 'tenant_admin',
+				label: 'Tenant Admin',
+				user_type: 1, // Adjust this value based on your user_type convention
+				status: 'ACTIVE',
+				organization_id: org.organization_id,
+				visibility: 'PUBLIC',
+				tenant_code: org.tenant_code,
+				translations: null,
+				created_at: new Date(),
+				updated_at: new Date(),
+			}))
+
+			await queryInterface.bulkInsert('user_roles', userRoleInserts, {
+				transaction,
+				ignoreDuplicates: true, // In case role already exists
+			})
+
+			console.log(`Inserted tenant_admin role for ${userRoleInserts.length} organization-tenant combinations`)
+
+			// Step 3: Get all admin permissions except admin module and admin-only permissions
+			// Excluding:
+			// - module = 'admin' (permission_ids: 22, 23, 26)
+			// - Admin-only feature permission (40)
+			// - Admin-only tenant permission (35)
+			// Including organization permissions (8, 28, 29, 30) as per requirement
+			const [adminPermissions] = await queryInterface.sequelize.query(
+				`SELECT DISTINCT 
+          permission_id,
+          module,
+          request_type,
+          api_path,
+          created_at,
+          updated_at,
+          created_by
+        FROM role_permission_mapping
+        WHERE role_title = 'admin'
+          AND module != 'admin'
+          AND permission_id NOT IN (35, 40)
+        ORDER BY permission_id`,
+				{ transaction }
+			)
+
+			console.log(`Found ${adminPermissions.length} permissions to copy for tenant_admin`)
+
+			// Step 4: Insert permissions for tenant_admin role
+			if (adminPermissions.length > 0) {
+				const permissionInserts = adminPermissions.map((perm) => ({
+					role_title: 'tenant_admin',
+					permission_id: perm.permission_id,
+					module: perm.module,
+					request_type: perm.request_type,
+					api_path: perm.api_path,
+					created_at: new Date(),
+					updated_at: new Date(),
+					created_by: perm.created_by,
+				}))
+
+				await queryInterface.bulkInsert('role_permission_mapping', permissionInserts, {
+					transaction,
+					ignoreDuplicates: true,
+				})
+
+				console.log(`Inserted ${permissionInserts.length} permissions for tenant_admin role`)
+			}
+
+			// Commit transaction
+			await transaction.commit()
+			console.log('Migration completed successfully')
+			console.log('Summary:')
+			console.log(`- Created tenant_admin roles: ${userRoleInserts.length}`)
+			console.log(`- Assigned permissions: ${adminPermissions.length}`)
+			console.log('- Excluded modules: admin')
+			console.log('- Excluded permissions: 35 (tenant), 40 (feature full CRUD)')
+		} catch (error) {
+			// Rollback transaction on error
+			await transaction.rollback()
+			console.error('Migration failed, rolled back:', error)
+			throw error
+		}
+	},
+
+	down: async (queryInterface, Sequelize) => {
+		const transaction = await queryInterface.sequelize.transaction()
+
+		try {
+			// Step 1: Delete all tenant_admin permissions from role_permission_mapping
+			const [deletePermResult] = await queryInterface.sequelize.query(
+				`DELETE FROM role_permission_mapping WHERE role_title = 'tenant_admin'`,
+				{ transaction }
+			)
+
+			console.log('Deleted all tenant_admin permissions')
+
+			// Step 2: Delete all tenant_admin roles from user_roles (soft delete if paranoid)
+			const [deleteRoleResult] = await queryInterface.sequelize.query(
+				`DELETE FROM user_roles WHERE title = 'tenant_admin'`,
+				{ transaction }
+			)
+
+			console.log('Deleted all tenant_admin roles')
+
+			await transaction.commit()
+			console.log('Rollback completed successfully')
+		} catch (error) {
+			await transaction.rollback()
+			console.error('Rollback failed:', error)
+			throw error
+		}
+	},
+}
diff --git a/src/database/models/Feature.js b/src/database/models/Feature.js
index e098c428c..a5d20a871 100644
--- a/src/database/models/Feature.js
+++ b/src/database/models/Feature.js
@@ -62,6 +62,11 @@ module.exports = (sequelize, DataTypes) => {
 			foreignKey: 'feature_code',
 			as: 'organization_features',
 		})
+		Feature.hasMany(models.FeatureRoleMapping, {
+			foreignKey: 'feature_code',
+			sourceKey: 'code',
+			as: 'featureRoleMappings',
+		})
 	}
 
 	return Feature
diff --git a/src/database/models/featureRoleMapping.js b/src/database/models/featureRoleMapping.js
new file mode 100644
index 000000000..7bafade2d
--- /dev/null
+++ b/src/database/models/featureRoleMapping.js
@@ -0,0 +1,93 @@
+module.exports = (sequelize, DataTypes) => {
+	const FeatureRoleMapping = sequelize.define(
+		'FeatureRoleMapping',
+		{
+			id: {
+				type: DataTypes.INTEGER,
+				primaryKey: true,
+				autoIncrement: true,
+				allowNull: false,
+			},
+			role_title: {
+				type: DataTypes.STRING,
+				allowNull: false,
+			},
+			feature_code: {
+				type: DataTypes.STRING,
+				allowNull: false,
+			},
+			organization_code: {
+				type: DataTypes.STRING,
+				allowNull: false,
+			},
+			tenant_code: {
+				type: DataTypes.STRING,
+				allowNull: false,
+				primaryKey: true,
+			},
+			created_by: {
+				type: DataTypes.INTEGER,
+				allowNull: true,
+			},
+			updated_by: {
+				type: DataTypes.INTEGER,
+				allowNull: true,
+			},
+		},
+		{
+			modelName: 'FeatureRoleMapping',
+			tableName: 'feature_role_mapping',
+			freezeTableName: true,
+			paranoid: false,
+			indexes: [
+				{
+					unique: true,
+					fields: ['feature_code', 'role_title', 'organization_code', 'tenant_code'],
+					where: {
+						deleted_at: null,
+					},
+				},
+			],
+		}
+	)
+
+	FeatureRoleMapping.associate = function (models) {
+		// Association with Feature model
+		FeatureRoleMapping.belongsTo(models.Feature, {
+			foreignKey: 'feature_code',
+			targetKey: 'code',
+			as: 'feature',
+		})
+
+		// Association with UserRole model
+		// FeatureRoleMapping.belongsTo(models.UserRole, {
+		// 	foreignKey: {
+		// 		name: 'role_title',
+		// 		allowNull: false,
+		// 	},
+		// 	targetKey: 'title',
+		// 	constraints: false,
+		// 	as: 'userRole',
+		// })
+
+		// Association with Organization model
+		FeatureRoleMapping.belongsTo(models.Organization, {
+			foreignKey: {
+				name: 'organization_code',
+				allowNull: false,
+			},
+			targetKey: 'code',
+			constraints: false,
+			as: 'organization',
+		})
+
+		// Association with Tenant model
+		FeatureRoleMapping.belongsTo(models.Tenant, {
+			foreignKey: 'tenant_code',
+			targetKey: 'code',
+			as: 'tenant',
+		})
+	}
+
+	return FeatureRoleMapping
+}
diff --git a/src/database/queries/featureRoleMapping.js b/src/database/queries/featureRoleMapping.js
new file mode 100644
index 000000000..21f59e2ea
--- /dev/null
+++ b/src/database/queries/featureRoleMapping.js
@@ -0,0 +1,88 @@
+/**
+ * name : featureRoleMapping.js
+ * author : Priyanka Pradeep
+ * created-date : 02-Oct-2025
+ * Description : feature-role-mapping database operations.
+ */
+
+const FeatureRoleMapping = require('@database/models/index').FeatureRoleMapping
+
+module.exports = class FeatureRoleMappingQueries {
+	/**
+	 * Create feature role mapping.
+	 * @method
+	 * @name create
+	 * @param {Object} data
+	 * @returns {Promise<Object>}
+	 */
+	static async create(data) {
+		try {
+			const result = await FeatureRoleMapping.create(data, {
+				returning: true,
+				raw: true,
+			})
+			return result
+		} catch (error) {
+			throw error
+		}
+	}
+
+	/**
+	 * Bulk create feature role mappings.
+	 * @method
+	 * @name bulkCreate
+	 * @param {Array} data
+	 * @returns {Promise<Array>}
+	 */
+	static async bulkCreate(data) {
+		try {
+			const result = await FeatureRoleMapping.bulkCreate(data, {
+				returning: true,
+				raw: true,
+			})
+			return result
+		} catch (error) {
+			throw error
+		}
+	}
+
+	/**
+	 * Find feature role mappings by user roles with relations.
+	 * @method
+	 * @name findByRoles
+	 * @param {Array} roles - Array of role titles
+	 * @param {String} organizationCode
+	 * @param {String} tenantCode
+	 * @returns {Promise<Array>}
+	 */
+	static async findAll(filter, options = {}) {
+		try {
+			const result = await FeatureRoleMapping.findAll({
+				where: filter,
+				...options,
+				raw: true,
+			})
+			return result
+		} catch (error) {
+			throw error
+		}
+	}
+
+	/**
+	 * Delete feature role mapping.
+	 * @method
+	 * @name delete
+	 * @param {Object} filter
+	 * @returns {Promise<Number>}
+	 */
+	static async delete(filter) {
+		try {
+			const result = await FeatureRoleMapping.destroy({
+				where: filter,
+			})
+			return result
+		} catch (error) {
+			throw error
+		}
+	}
+}
diff --git a/src/database/queries/notificationTemplate.js b/src/database/queries/notificationTemplate.js
index 7aed3e729..2e01c18d9 100644
--- a/src/database/queries/notificationTemplate.js
+++ b/src/database/queries/notificationTemplate.js
@@ -83,14 +83,22 @@ exports.findOneEmailTemplate = async (code, orgCode = null, tenantCode) => {
 		templateData = templateData.find((template) => template.organization_code === orgCode) || templateData[0]
 
 		if (templateData && templateData.email_header) {
-			const header = await this.getEmailHeader(templateData.email_header)
+			const header = await this.getEmailHeader(
+				templateData.email_header,
+				templateData.tenant_code,
+				templateData.organization_code
+			)
 			if (header && header.body) {
 				templateData['body'] = header.body + templateData['body']
 			}
 		}
 
 		if (templateData && templateData.email_footer) {
-			const footer = await this.getEmailFooter(templateData.email_footer)
+			const footer = await this.getEmailFooter(
+				templateData.email_footer,
+				templateData.tenant_code,
+				templateData.organization_code
+			)
 			if (footer && footer.body) {
 				templateData['body'] = templateData['body'] + footer.body
 			}
@@ -101,12 +109,14 @@ exports.findOneEmailTemplate = async (code, orgCode = null, tenantCode) => {
 	}
 }
 
-exports.getEmailHeader = async (header) => {
+exports.getEmailHeader = async (header, tenantCode, organizationCode) => {
 	try {
 		const filterEmailHeader = {
 			code: header,
 			type: 'emailHeader',
 			status: common.ACTIVE_STATUS,
+			tenant_code: tenantCode,
+			organization_code: organizationCode,
 		}
 
 		const headerData = await NotificationTemplate.findOne({
@@ -119,12 +129,14 @@ exports.getEmailHeader = async (header) => {
 	}
 }
 
-exports.getEmailFooter = async (footer) => {
+exports.getEmailFooter = async (footer, tenantCode, organizationCode) => {
 	try {
 		const filterEmailFooter = {
 			code: footer,
 			type: 'emailFooter',
 			status: common.ACTIVE_STATUS,
+			tenant_code: tenantCode,
+			organization_code: organizationCode,
 		}
 
 		const headerData = await NotificationTemplate.findOne({
diff --git a/src/database/queries/organization-feature.js b/src/database/queries/organization-feature.js
index bc55dc44e..090119c80 100644
--- a/src/database/queries/organization-feature.js
+++ b/src/database/queries/organization-feature.js
@@ -1,5 +1,7 @@
 'use strict'
 const OrganizationFeature = require('@database/models/index').OrganizationFeature
+const FeatureRoleMapping = require('@database/models/index').FeatureRoleMapping
+const { Op } = require('sequelize')
 
 exports.create = async (data) => {
 	try {
@@ -50,6 +52,30 @@ exports.findAllOrganizationFeature = async (filter, options = {}) => {
 	}
 }
 
+exports.findAllFeatureWithRoleMappings = async (filter, options = {}, roleTitles = []) => {
+	try {
+		const organizationFeature = await OrganizationFeature.findAll({
+			where: filter,
+			...options,
+			include: [
+				{
+					model: FeatureRoleMapping,
+					as: 'roleMappings',
+					...(roleTitles?.length > 0 && {
+						where: { role_title: { [Op.in]: roleTitles } },
+					}),
+					required: false,
+				},
+			],
+			raw: false,
+			nest: true,
+		})
+		return organizationFeature
+	} catch (error) {
+		return error
+	}
+}
+
 exports.hardDelete = async (feature_code, organization_code, tenant_code) => {
 	try {
 		return await OrganizationFeature.destroy({
diff --git a/src/database/queries/users.js b/src/database/queries/users.js
index 75a004fd7..ed2e6d1c8 100644
--- a/src/database/queries/users.js
+++ b/src/database/queries/users.js
@@ -98,7 +98,10 @@ exports.listUsers = async (roleId, organization_id, page, limit, search, tenant_
 
 		// Final query using the updated schema
 		let { count, rows: users } = await database.User.findAndCountAll({
-			where: userWhereClause,
+			where: {
+				...userWhereClause,
+				tenant_code, // Ensure this is in the main where clause
+			},
 			attributes: ['id', 'name', 'about', 'image'],
 			offset: parseInt(offset, 10),
 			limit: parseInt(limit, 10),
diff --git a/src/distributionColumns.sql b/src/distributionColumns.sql
index e573ff3a5..a1f466a47 100644
--- a/src/distributionColumns.sql
+++ b/src/distributionColumns.sql
@@ -1,37 +1,69 @@
 create extension citus
 
 -- Step 1: Create reference tables first
-SELECT create_reference_table('features');
+SELECT create_reference_table ('features');
 
 -- Step 2: Distribute primary, top-level tenant-scoped tables early
-SELECT create_distributed_table('tenants','code');
-SELECT create_distributed_table('users', 'tenant_code');
-SELECT create_distributed_table('organizations', 'tenant_code');
-SELECT create_distributed_table('entity_types', 'tenant_code');
+SELECT create_distributed_table ('tenants', 'code');
+
+SELECT create_distributed_table ('users', 'tenant_code');
+
+SELECT create_distributed_table ( 'organizations', 'tenant_code' );
+
+SELECT create_distributed_table ('entity_types', 'tenant_code');
 
 -- Step 3: Then distribute dependent tables (ensure matching shard keys!)
-SELECT create_distributed_table('entities', 'tenant_code');
-SELECT create_distributed_table('forms', 'tenant_code');
-SELECT create_distributed_table('notification_templates', 'tenant_code');
-SELECT create_distributed_table('file_uploads', 'tenant_code');
-SELECT create_distributed_table('user_sessions', 'tenant_code');
+SELECT create_distributed_table ('entities', 'tenant_code');
+
+SELECT create_distributed_table ('forms', 'tenant_code');
+
+SELECT create_distributed_table (
+        'notification_templates', 'tenant_code'
+    );
+
+SELECT create_distributed_table ('file_uploads', 'tenant_code');
+
+SELECT create_distributed_table ( 'user_sessions', 'tenant_code' );
 
 -- Organization-related tables
-SELECT create_distributed_table('organization_email_domains', 'tenant_code');
-SELECT create_distributed_table('organization_features', 'tenant_code');
-SELECT create_distributed_table('organization_registration_codes', 'tenant_code');
+SELECT create_distributed_table (
+        'organization_email_domains', 'tenant_code'
+    );
+
+SELECT create_distributed_table (
+        'organization_features', 'tenant_code'
+    );
+
+SELECT create_distributed_table (
+        'organization_registration_codes', 'tenant_code'
+    );
 
 -- Invitation / user-related
-SELECT create_distributed_table('invitations','tenant_code');
-SELECT create_distributed_table('organization_user_invites','tenant_code');
+SELECT create_distributed_table ('invitations', 'tenant_code');
+
+SELECT create_distributed_table (
+        'organization_user_invites', 'tenant_code'
+    );
+
+SELECT create_distributed_table ('user_roles', 'tenant_code');
+
+SELECT create_distributed_table (
+        'organization_role_requests', 'tenant_code'
+    );
 
-SELECT create_distributed_table('user_roles', 'tenant_code');
-SELECT create_distributed_table('organization_role_requests','tenant_code');
+SELECT create_distributed_table (
+        'user_organizations', 'tenant_code'
+    );
 
-SELECT create_distributed_table('user_organizations', 'tenant_code');
-SELECT create_distributed_table('user_organization_roles', 'tenant_code');
+SELECT create_distributed_table (
+        'user_organization_roles', 'tenant_code'
+    );
 --SELECT create_distributed_table('users_credentials','email'); -- ⚠️ distribution by email will break colocation
 
 -- Tenant-domain related
-SELECT create_distributed_table('tenant_domains','domain'); -- ⚠️ different colocation
+SELECT create_distributed_table ('tenant_domains', 'domain');
+-- ⚠️ different colocation
 
+SELECT create_distributed_table (
+        'feature_role_mapping', 'tenant_code'
+    );
\ No newline at end of file
diff --git a/src/envVariables.js b/src/envVariables.js
index 692b6dba2..1e8a32084 100644
--- a/src/envVariables.js
+++ b/src/envVariables.js
@@ -2,6 +2,43 @@ let table = require('cli-table')
 
 let tableData = new table()
 
+const databaseEnvironmentVariables = {
+	DB_POOL_MAX: {
+		message: 'Max connections per Sequelize pool',
+		optional: true,
+		default: 10,
+	},
+	DB_POOL_MIN: {
+		message: 'Min connections per Sequelize pool',
+		optional: true,
+		default: 2,
+	},
+	DB_POOL_ACQUIRE_MS: {
+		message: 'Pool acquire timeout (ms)',
+		optional: true,
+		default: 60000,
+	},
+	DB_POOL_IDLE_MS: {
+		message: 'Connection idle timeout (ms)',
+		optional: true,
+		default: 10000,
+	},
+	DB_POOL_EVICT_MS: {
+		message: 'Pool eviction interval (ms)',
+		optional: true,
+		default: 1000,
+	},
+	PG_STATEMENT_TIMEOUT_MS: {
+		message: 'Max query execution time (ms)',
+		optional: true,
+		default: 0,
+	},
+	PG_IDLE_TX_TIMEOUT_MS: {
+		message: 'Idle transaction timeout (ms)',
+		optional: true,
+		default: 0,
+	},
+}
 let enviromentVariables = {
 	APPLICATION_PORT: {
 		message: 'Required port no',
@@ -29,6 +66,7 @@ let enviromentVariables = {
 		optional: true,
 		default: 'MentorED',
 	},
+	...databaseEnvironmentVariables,
 	REGISTRATION_EMAIL_TEMPLATE_CODE: {
 		message: 'Required registration email template code',
 		optional: true,
diff --git a/src/helpers/userInvite.js b/src/helpers/userInvite.js
index 0adf80884..3f11a31cd 100644
--- a/src/helpers/userInvite.js
+++ b/src/helpers/userInvite.js
@@ -861,10 +861,10 @@ module.exports = class UserInviteHelper {
 										  )
 										: {} // fetch the user with organization and roles if any of the critical fields are updated
 							userFetch = userFetch?.[0] || {}
+							oldValues.organizations = existingUser.organizations
 
 							// if org or role is updated assign the old and new values
 							if (isOrgUpdate || isRoleUpdated) {
-								oldValues.organizations = existingUser.organizations
 								newValues.organizations = userFetch?.organizations
 							}
 							// if any keys are modified in user table or additional csv headers are present prepare the new values
@@ -915,7 +915,7 @@ module.exports = class UserInviteHelper {
 							if (oldValues?.phone) {
 								oldValues.phone = emailEncryption.decrypt(oldValues.phone)
 							}
-							if (Object.keys(oldValues).length > 0 || Object.keys(newValues).length > 0) {
+							if (Object.keys(newValues).length > 0) {
 								const eventBody = eventBodyDTO({
 									entity: 'user',
 									eventType: 'bulk-update',
diff --git a/src/locales/en.json b/src/locales/en.json
index 412b6b2af..5a864343b 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -191,6 +191,12 @@
 	"QUERY_FORBIDDEN_INJECTION_PATTERNS": "Query contains forbidden injection patterns.",
 	"QUERY_FORBIDDEN_PATTERNS": "Query contains forbidden SQL operations.",
 	"ADD_ORG_HEADER": "Please provide all required organization headers: {{orgCodeHeader}}, and {{tenantCodeHeader}} for admin override.",
+	"ORG_CODE_REQUIRED_FOR_TENANT_ADMIN": "Please provide header {{orgCodeHeader}} for admin override.",
 	"INVALID_ORG_ID": "Organization ID must be a valid positive integer.",
-	"INVALID_ORG_OR_TENANT_CODE": "The provided organization or tenant code is invalid or does not match."
+	"INVALID_ORG_OR_TENANT_CODE": "The provided organization or tenant code is invalid or does not match.",
+	"INVALID_ORG_CODE_FOR_TENANT": "The provided organization is invalid or does not match.",
+	"USER_ORGANIZATION_NOT_FOUND": "The user is not associated with the specified organization.",
+	"USER_ROLE_ALREADY_EXISTS": "The user already has this role assigned.",
+	"INVALID_ROLE_ID": "The provided role ID is invalid.",
+	"USER_ROLE_ASSIGNED_SUCCESSFULLY": "The role was assigned to the user successfully."
 }
diff --git a/src/middlewares/authenticator.js b/src/middlewares/authenticator.js
index bb5063d05..912d1a813 100644
--- a/src/middlewares/authenticator.js
+++ b/src/middlewares/authenticator.js
@@ -203,67 +203,108 @@ module.exports = async function (req, res, next) {
 		}
 		if (!decodedToken) throw unAuthorizedResponse
 
-		//check for admin user
+		// Check for admin and tenant admin roles
 		let isAdmin = false
+		let isTenantAdmin = false
 		if (decodedToken.data.roles) {
 			isAdmin = decodedToken.data.roles.some((role) => role.title === common.ADMIN_ROLE)
+			isTenantAdmin = decodedToken.data.roles.some((role) => role.title === common.TENANT_ADMIN_ROLE)
 		}
 
-		if (isAdmin) {
-			// For admin users, allow overriding tenant_code and organization_code via headers
-			// Header names are configurable via environment variables with sensible defaults
+		// Handle organization override for admin and tenant admin
+		if (isAdmin || isTenantAdmin) {
 			const orgCodeHeaderName = common.ORG_CODE_HEADER
 			const tenantCodeHeaderName = common.TENANT_CODE_HEADER
 
-			// Extract and sanitize header values (trim whitespace, case-insensitive header lookup)
 			const orgCode = (req.headers[orgCodeHeaderName.toLowerCase()] || '').trim()
 			const tenantCode = (req.headers[tenantCodeHeaderName.toLowerCase()] || '').trim()
 
-			// If any override header is provided (non-empty after trim), both must be present and non-empty
 			const hasAnyOverrideHeader = orgCode || tenantCode
+
 			if (hasAnyOverrideHeader) {
-				if (!orgCode || !tenantCode) {
-					throw responses.failureResponse({
-						message: {
-							key: 'ADD_ORG_HEADER',
-							interpolation: {
-								orgCodeHeader: orgCodeHeaderName,
-								tenantCodeHeader: tenantCodeHeaderName,
+				// For tenant admin, always use their token's tenant_code (ignore any header value)
+				if (isTenantAdmin && !isAdmin) {
+					// Always use tenant_code from token, ignoring any header value for security
+					const effectiveTenantCode = decodedToken.data.tenant_code
+
+					if (!orgCode) {
+						throw responses.failureResponse({
+							message: {
+								key: 'ORG_CODE_REQUIRED_FOR_TENANT_ADMIN',
+								interpolation: {
+									orgCodeHeader: orgCodeHeaderName,
+								},
 							},
-						},
-						statusCode: httpStatusCode.bad_request,
-						responseCode: 'CLIENT_ERROR',
+							statusCode: httpStatusCode.bad_request,
+							responseCode: 'CLIENT_ERROR',
+						})
+					}
+
+					// Query the database to find the organization within tenant admin's tenant
+					const overrideOrg = await organizationQueries.findOne({
+						code: orgCode,
+						tenant_code: effectiveTenantCode,
+						status: common.ACTIVE_STATUS,
+						deleted_at: null,
 					})
-				}
 
-				// Query the database to find the organization based on orgCode and tenantCode
-				const org = await organizationQueries.findOne({
-					code: orgCode,
-					tenant_code: tenantCode,
-					status: common.ACTIVE_STATUS,
-					deleted_at: null,
-				})
+					if (!overrideOrg) {
+						throw responses.failureResponse({
+							message: 'INVALID_ORG_CODE_FOR_TENANT',
+							statusCode: httpStatusCode.bad_request,
+							responseCode: 'CLIENT_ERROR',
+						})
+					}
 
-				if (!org) {
-					throw responses.failureResponse({
-						message: 'INVALID_ORG_OR_TENANT_CODE',
-						statusCode: httpStatusCode.bad_request,
-						responseCode: 'CLIENT_ERROR',
+					// Override organization details
+					decodedToken.data.organization_id = overrideOrg.id
+					decodedToken.data.organization_code = orgCode
+					// tenant_code remains unchanged for tenant admin
+				} else if (isAdmin) {
+					// Admin logic - can override both tenant and org
+					if (!orgCode || !tenantCode) {
+						throw responses.failureResponse({
+							message: {
+								key: 'ADD_ORG_HEADER',
+								interpolation: {
+									orgCodeHeader: orgCodeHeaderName,
+									tenantCodeHeader: tenantCodeHeaderName,
+								},
+							},
+							statusCode: httpStatusCode.bad_request,
+							responseCode: 'CLIENT_ERROR',
+						})
+					}
+
+					const overrideOrg = await organizationQueries.findOne({
+						code: orgCode,
+						tenant_code: tenantCode,
+						status: common.ACTIVE_STATUS,
+						deleted_at: null,
 					})
-				}
 
-				// Override the values from the token with sanitized header values and fetched orgId
-				decodedToken.data.tenant_code = tenantCode
-				decodedToken.data.organization_id = org.id // Use the ID from the database
-				decodedToken.data.organization_code = orgCode
+					if (!overrideOrg) {
+						throw responses.failureResponse({
+							message: 'INVALID_ORG_OR_TENANT_CODE',
+							statusCode: httpStatusCode.bad_request,
+							responseCode: 'CLIENT_ERROR',
+						})
+					}
+
+					// Override both tenant and organization details
+					decodedToken.data.tenant_code = tenantCode
+					decodedToken.data.organization_id = overrideOrg.id
+					decodedToken.data.organization_code = orgCode
+				}
 			}
-			req.decodedToken = decodedToken.data
 
-			// Admin users intentionally bypass role and permission validation below.
-			// This early return ensures admins proceed without further checks.
-			// If admin access rules change, remove or adjust this bypass accordingly.
+			req.decodedToken = decodedToken.data
 
-			return next()
+			// Only admin users bypass role and permission validation
+			// Tenant admins must go through permission checks
+			if (isAdmin) {
+				return next()
+			}
 		}
 
 		if (roleValidation) {
diff --git a/src/package.json b/src/package.json
index a942204dd..10b60fc53 100644
--- a/src/package.json
+++ b/src/package.json
@@ -16,8 +16,9 @@
 		"elevate-migrations": "module/migrations/bin/migrations.js",
 		"integration": "node app.js",
 		"test:integration": "jest --verbose ./integration-test  --config=integrationJest.config.js --runInBand",
-		"db:init": "sequelize-cli db:create || echo 'Database already exists or some issue while creating db, Please check' && sequelize-cli db:migrate ",
-		"db:seed:all": "sequelize-cli db:seed:all || echo 'Seeded data already exists or some issue while seeding the data, Please check' "
+		"db:init": "sequelize-cli db:create || echo 'Database already exists or some issue while creating db, Please check'; sequelize-cli db:migrate",
+		"db:seed:all": "sequelize-cli db:seed:all || (echo 'Seeded data already exists or some issue while seeding the data, Please check' && false)",
+		"db:migrate": "sequelize-cli db:migrate"
 	},
 	"author": "Aman Kumar Gupta <amangupta15jan@gmail.com>",
 	"license": "ISC",
diff --git a/src/services/account.js b/src/services/account.js
index 02d02d98e..88d9b71ec 100644
--- a/src/services/account.js
+++ b/src/services/account.js
@@ -1608,7 +1608,7 @@ module.exports = class AccountHelper {
 				})
 			} else {
 				let role = await roleQueries.findOne(
-					{ title: params.query.type.toLowerCase() },
+					{ title: params.query.type.toLowerCase(), tenant_code: tenantCode },
 					{
 						attributes: ['id'],
 					}
diff --git a/src/services/admin.js b/src/services/admin.js
index 133c8dbbc..d038f8b1a 100644
--- a/src/services/admin.js
+++ b/src/services/admin.js
@@ -18,6 +18,7 @@ const utils = require('@generics/utils')
 const rawQueryUtils = require('@utils/rawQueryUtils')
 
 // Database queries
+const { ForeignKeyConstraintError, UniqueConstraintError } = require('sequelize')
 const organizationQueries = require('@database/queries/organization')
 const roleQueries = require('@database/queries/user-role')
 const tenantQueries = require('@database/queries/tenants')
@@ -35,6 +36,7 @@ const userSessionsService = require('@services/user-sessions')
 const { broadcastEvent } = require('@helpers/eventBroadcasterMain')
 const emailEncryption = require('@utils/emailEncryption')
 const { eventBroadcaster } = require('@helpers/eventBroadcaster')
+
 const { generateUniqueUsername } = require('@utils/usernameGenerator')
 const responses = require('@helpers/responses')
 const userHelper = require('@helpers/userHelper')
@@ -42,7 +44,7 @@ const userHelper = require('@helpers/userHelper')
 // DTOs
 const UserTransformDTO = require('@dtos/userDTO')
 const organizationDTO = require('@dtos/organizationDTO')
-
+const { eventBodyDTO } = require('@dtos/userDTO')
 module.exports = class AdminHelper {
 	/**
 	 * Delete User
@@ -92,6 +94,119 @@ module.exports = class AdminHelper {
 		}
 	}
 
+	/**
+	 * Assigns a role to a user within an organization.
+	 * Params come from token. Body has user_id, role_id, optional organization_id.
+	 *
+	 * @param {Object} params
+	 * @param {string} params.tenant_code
+	 * @param {string} params.organization_code
+	 * @param {Object} body
+	 * @param {number|string} body.user_id
+	 * @param {number|string} body.role_id
+	 * @param {number|string} [body.organization_id]
+	 */
+	static async assignRole(params, body) {
+		try {
+			const { tenant_code, organization_code } = params
+			const { user_id, role_id, organization_id } = body
+
+			const user = await userQueries.findUserWithOrganization({ id: user_id, tenant_code })
+			if (!user) {
+				return responses.failureResponse({
+					message: 'USER_NOT_FOUND',
+					statusCode: httpStatusCode.bad_request,
+					responseCode: 'CLIENT_ERROR',
+				})
+			}
+
+			const orgs = Array.isArray(user.organizations) ? user.organizations : []
+			const org = orgs.find(
+				(o) => o?.code === organization_code || String(o?.id) === String(organization_id || '')
+			)
+
+			if (!org) {
+				return responses.failureResponse({
+					message: 'USER_ORGANIZATION_NOT_FOUND',
+					statusCode: httpStatusCode.bad_request,
+					responseCode: 'CLIENT_ERROR',
+				})
+			}
+
+			const alreadyHasRole = Array.isArray(org.roles)
+				? org.roles.some((r) => String(r.id) === String(role_id))
+				: false
+
+			if (alreadyHasRole) {
+				return responses.failureResponse({
+					message: 'USER_ROLE_ALREADY_EXISTS',
+					statusCode: httpStatusCode.bad_request,
+					responseCode: 'CLIENT_ERROR',
+				})
+			}
+
+			let mapping
+			try {
+				mapping = await userOrganizationRoleQueries.create({
+					tenant_code,
+					user_id: user.id,
+					organization_code: org.code,
+					role_id,
+				})
+			} catch (error) {
+				if (error instanceof ForeignKeyConstraintError) {
+					return responses.failureResponse({
+						message: 'INVALID_ROLE_ID',
+						statusCode: httpStatusCode.bad_request,
+						responseCode: 'CLIENT_ERROR',
+					})
+				}
+				if (error instanceof UniqueConstraintError) {
+					return responses.failureResponse({
+						message: 'USER_ROLE_ALREADY_EXISTS',
+						statusCode: httpStatusCode.bad_request,
+						responseCode: 'CLIENT_ERROR',
+					})
+				}
+				throw error
+			}
+
+			// Post-assignment operations (best-effort)
+			try {
+				const updatedUser = await userQueries.findUserWithOrganization({ id: user_id, tenant_code })
+				const newValues = utils.extractDelta(user, updatedUser)
+
+				const eventBody = eventBodyDTO({
+					entity: 'user',
+					eventType: 'update',
+					entityId: user.id,
+					oldValues: user,
+					newValues,
+					args: { created_at: updatedUser.created_at, updated_at: updatedUser.updated_at },
+				})
+
+				broadcastEvent('userEvents', { requestBody: eventBody, isInternal: true })
+
+				const redisUserKey = `${common.redisUserPrefix}${tenant_code}_${user.id.toString()}`
+				await utils.redisDel(redisUserKey)
+
+				// End all active sessions for this user in this tenant (handles Redis + ended_at)
+				await userHelper.removeAllUserSessions([user.id], tenant_code)
+			} catch (auxError) {
+				console.error('Error in post-assignment operations (non-critical):', auxError)
+			}
+
+			return responses.successResponse({
+				statusCode: httpStatusCode.ok,
+				message: 'USER_ROLE_ASSIGNED_SUCCESSFULLY',
+				result: { mapping },
+			})
+		} catch (error) {
+			console.error('Error in assignRole:', error)
+			throw error
+		}
+	}
+
 	/**
 	 * Creates a new admin user.
 	 *
diff --git a/src/services/organization-feature.js b/src/services/organization-feature.js
index 53da178fe..505ecdee6 100644
--- a/src/services/organization-feature.js
+++ b/src/services/organization-feature.js
@@ -8,10 +8,14 @@
 const httpStatusCode = require('@generics/http-status')
 const organizationFeatureQueries = require('@database/queries/organization-feature')
 const organizationQueries = require('@database/queries/organization')
+const featureRoleMappingQueries = require('@database/queries/featureRoleMapping')
 const responses = require('@helpers/responses')
 const utilsHelper = require('@generics/utils')
 const { UniqueConstraintError } = require('sequelize')
 const common = require('@constants/common')
+const { Op } = require('sequelize')
+const roleQueries = require('@database/queries/user-role')
+const { sequelize } = require('@database/models/index')
 
 module.exports = class organizationFeatureHelper {
 	/**
@@ -63,6 +67,7 @@ module.exports = class organizationFeatureHelper {
 	 */
 
 	static async create(bodyData, tokenInformation, isAdmin = false) {
+		const transaction = await sequelize.transaction()
 		try {
 			// validate that the feature exists in the default organization
 			if (!isAdmin && tokenInformation.organization_code != process.env.DEFAULT_TENANT_ORG_CODE) {
@@ -92,13 +97,47 @@ module.exports = class organizationFeatureHelper {
 			bodyData.created_by = tokenInformation.id
 
 			// Create the new organization feature
-			const createdOrgFeature = await organizationFeatureQueries.create(bodyData)
+			const createdOrgFeature = await organizationFeatureQueries.create(bodyData, { transaction })
+
+			// If roles are provided, create feature_role_mapping entries
+			if (bodyData.roles && Array.isArray(bodyData.roles) && bodyData.roles.length > 0) {
+				// Validate that all requested roles exist for this tenant
+				const validRoles = await roleQueries.findAll({
+					tenant_code: tokenInformation.tenant_code,
+					title: {
+						[Op.in]: bodyData.roles,
+					},
+				})
+
+				const validRoleTitles = validRoles.map((role) => role.title)
+				if (validRoleTitles.length === 0 || validRoleTitles.length !== bodyData?.roles?.length) {
+					await transaction.rollback()
+					return responses.failureResponse({
+						message: 'ROLE_NOT_FOUND',
+						statusCode: httpStatusCode.bad_request,
+						responseCode: 'CLIENT_ERROR',
+					})
+				}
+				// Prepare bulk insert data for feature_role_mapping
+				const featureRoleMappingData = validRoleTitles.map((role) => ({
+					feature_code: bodyData.feature_code,
+					role_title: role,
+					organization_code: tokenInformation.organization_code,
+					tenant_code: tokenInformation.tenant_code,
+					created_by: tokenInformation.id,
+					updated_by: tokenInformation.id,
+				}))
+
+				await featureRoleMappingQueries.bulkCreate(featureRoleMappingData, { transaction })
+			}
+			await transaction.commit()
 			return responses.successResponse({
 				statusCode: httpStatusCode.created,
 				message: 'ORG_FEATURE_CREATED_SUCCESSFULLY',
 				result: createdOrgFeature,
 			})
 		} catch (error) {
+			if (transaction) await transaction.rollback()
 			if (error.name === common.SEQUELIZE_FOREIGN_KEY_CONSTRAINT_ERROR) {
 				return responses.failureResponse({
 					message: 'FEATURE_NOT_FOUND',
@@ -112,7 +151,11 @@ module.exports = class organizationFeatureHelper {
 					responseCode: 'CLIENT_ERROR',
 				})
 			}
-			throw error
+			return responses.failureResponse({
+				message: error.message || error,
+				statusCode: httpStatusCode.bad_request,
+				responseCode: 'CLIENT_ERROR',
+			})
 		}
 	}
 
@@ -125,6 +168,7 @@ module.exports = class organizationFeatureHelper {
 	 * @returns {JSON} - Org feature update response.
 	 */
 	static async update(feature_code, bodyData, tokenInformation) {
+		const transaction = await sequelize.transaction()
 		try {
 			// Prepare filter query to identify the organization feature to update
 			let filterQuery = {
@@ -137,7 +181,8 @@ module.exports = class organizationFeatureHelper {
 
 			const [updatedCount, updatedOrgFeature] = await organizationFeatureQueries.updateOrganizationFeature(
 				filterQuery,
-				bodyData
+				bodyData,
+				{ transaction }
 			)
 
 			// Return error if no record was updated
@@ -149,12 +194,51 @@ module.exports = class organizationFeatureHelper {
 				})
 			}
 
+			// If roles are provided, update feature_role_mapping entries
+			if (bodyData?.roles && Array.isArray(bodyData?.roles)) {
+				// Validate that all requested roles exist for this tenant
+				const validRoles = await roleQueries.findAll({
+					tenant_code: tokenInformation.tenant_code,
+					title: { [Op.in]: bodyData.roles },
+				})
+				if (validRoles.length !== bodyData.roles.length) {
+					return responses.failureResponse({
+						message: 'ROLE_NOT_FOUND',
+						statusCode: httpStatusCode.bad_request,
+						responseCode: 'CLIENT_ERROR',
+					})
+				}
+
+				// First, delete existing mappings for this feature and organization
+				await featureRoleMappingQueries.delete(
+					{
+						feature_code: feature_code,
+						organization_code: tokenInformation.organization_code,
+						tenant_code: tokenInformation.tenant_code,
+					},
+					{ transaction }
+				)
+
+				// Then create new mappings if roles are provided
+				const featureRoleMappingData = bodyData.roles.map((role) => ({
+					feature_code: feature_code,
+					role_title: role,
+					organization_code: tokenInformation.organization_code,
+					tenant_code: tokenInformation.tenant_code,
+					created_by: tokenInformation.id,
+					updated_by: tokenInformation.id,
+				}))
+
+				await featureRoleMappingQueries.bulkCreate(featureRoleMappingData, { transaction })
+			}
+			await transaction.commit()
 			return responses.successResponse({
 				statusCode: httpStatusCode.ok,
 				message: 'ORG_FEATURE_UPDATED_SUCCESSFULLY',
 				result: updatedOrgFeature?.[0],
 			})
 		} catch (error) {
+			if (transaction) await transaction.rollback()
 			if (error.name === common.SEQUELIZE_FOREIGN_KEY_CONSTRAINT_ERROR) {
 				return responses.failureResponse({
 					message: 'FEATURE_NOT_FOUND',
@@ -167,14 +251,16 @@ module.exports = class organizationFeatureHelper {
 	}
 
 	/**
-	 * List organization features.
+	 * List organization features based on user roles.
 	 * @method
 	 * @name list
-	 * @param {Object} tokenInformation - Token Information
+	 * @param {String} tenantCode - Tenant Code
+	 * @param {String} orgCode - Organization Code
+	 * @param {Array} userRoles - User roles from token
 	 * @returns {JSON} - Organization feature list.
 	 */
 
-	static async list(tenantCode, orgCode) {
+	static async list(tenantCode, orgCode, userRoles = []) {
 		try {
 			let filter = {
 				organization_code: orgCode,
@@ -197,7 +283,6 @@ module.exports = class organizationFeatureHelper {
 
 			// Merge features with Map for efficiency
 			const featureMap = new Map(defaultOrgFeatures.map((feature) => [feature.feature_code, feature]))
-
 			// Override with current org features if they exist
 			if (currentOrgFeatures?.length) {
 				currentOrgFeatures.forEach((feature) => {
@@ -205,7 +290,72 @@ module.exports = class organizationFeatureHelper {
 				})
 			}
 
-			const organizationFeatures = Array.from(featureMap.values())
+			let organizationFeatures = Array.from(featureMap.values())
+
+			// Filter features based on user roles
+			if (userRoles?.length > 0) {
+				// Extract role titles from user roles
+				const roleTitles = userRoles.map((role) => role.title)
+				// Check if user has admin or org_admin role
+				const hasAdminAccess = roleTitles.some((role) =>
+					[common.ADMIN_ROLE, common.ORG_ADMIN_ROLE, common.TENANT_ADMIN_ROLE].includes(role)
+				)
+
+				// If user is not admin or org_admin, filter based on role mappings
+				if (!hasAdminAccess) {
+					// Fetch role mappings for both current org and default org in a single query
+					const filterQuery = {
+						tenant_code: tenantCode,
+						organization_code: {
+							[Op.in]: [orgCode, process.env.DEFAULT_ORGANISATION_CODE],
+						},
+						role_title: { [Op.in]: roleTitles },
+					}
+
+					const roleFeatureMappings = await featureRoleMappingQueries.findAll(filterQuery)
+
+					if (roleFeatureMappings?.length > 0) {
+						// Separate mappings and build role-feature maps in single pass
+						const currentOrgRoleFeatureMap = new Map()
+						const defaultOrgRoleFeatureMap = new Map()
+
+						// Build maps of roles to features for current and default organizations
+						roleFeatureMappings.forEach((mapping) => {
+							const targetMap =
+								mapping.organization_code === orgCode
+									? currentOrgRoleFeatureMap
+									: defaultOrgRoleFeatureMap
+							if (!targetMap.has(mapping.role_title)) {
+								targetMap.set(mapping.role_title, new Set())
+							}
+							targetMap.get(mapping.role_title).add(mapping.feature_code)
+						})
+
+						// For each role, determine accessible features
+						// If role has mappings in current org, use those; otherwise use default org mappings
+						const accessibleFeatureCodes = new Set()
+						roleTitles.forEach((role) => {
+							const currentOrgFeatures = currentOrgRoleFeatureMap.get(role)
+							if (currentOrgFeatures?.size > 0) {
+								// Current org has explicit mappings for this role - use only those
+								currentOrgFeatures.forEach((feature) => accessibleFeatureCodes.add(feature))
+							} else {
+								// No current org mappings for this role - fall back to default org
+								const defaultOrgFeatures = defaultOrgRoleFeatureMap.get(role)
+								defaultOrgFeatures?.forEach((feature) => accessibleFeatureCodes.add(feature))
+							}
+						})
+
+						// Filter to only accessible features
+						organizationFeatures = organizationFeatures.filter((feature) =>
+							accessibleFeatureCodes.has(feature.feature_code)
+						)
+					} else {
+						// No role mappings found - restrict to empty set for security
+						organizationFeatures = []
+					}
+				}
+			}
 
 			// Sort the organization features based on the display_order in ascending order
 			const sortedFeatures = organizationFeatures.sort((a, b) => a.display_order - b.display_order)
diff --git a/src/validators/v1/admin.js b/src/validators/v1/admin.js
index f7741b749..62385e17f 100644
--- a/src/validators/v1/admin.js
+++ b/src/validators/v1/admin.js
@@ -180,4 +180,18 @@ module.exports = {
 	executeRawQuery: (req) => {
 		req.checkBody('query').trim().notEmpty().withMessage('query field is empty')
 	},
+
+	assignRole: (req) => {
+		req.checkBody('user_id')
+			.notEmpty()
+			.withMessage('user_id field is empty')
+			.isNumeric()
+			.withMessage('user_id must be a number')
+
+		req.checkBody('role_id')
+			.notEmpty()
+			.withMessage('role_id field is empty')
+			.isNumeric()
+			.withMessage('role_id must be a number')
+	},
 }
