diff --git a/src/Core/Auth/UserFeatures/PremiumAccess/IPremiumAccessQuery.cs b/src/Core/Auth/UserFeatures/PremiumAccess/IPremiumAccessQuery.cs
new file mode 100644
index 0000000000..0f5983158a
--- /dev/null
+++ b/src/Core/Auth/UserFeatures/PremiumAccess/IPremiumAccessQuery.cs
@@ -0,0 +1,50 @@
+using Bit.Core.Entities;
+
+namespace Bit.Core.Auth.UserFeatures.PremiumAccess;
+
+///
+/// Query for checking premium access status for users.
+/// This is the centralized location for determining if a user can access premium features
+/// (either through personal subscription or organization membership).
+///
+///
+/// Note: This is different from checking User.Premium, which only indicates
+/// personal subscription status. Use these methods to check actual premium feature access.
+///
+///
+public interface IPremiumAccessQuery
+{
+ ///
+ /// Checks if a user has access to premium features (personal subscription or organization).
+ /// This is the definitive way to check premium access for a single user.
+ ///
+ /// The user to check for premium access
+ /// True if user can access premium features; false otherwise
+ Task CanAccessPremiumAsync(User user);
+
+ ///
+ /// Checks if a user has access to premium features (personal subscription or organization).
+ /// Use this overload when you already know the personal premium status and only need to check organization premium.
+ ///
+ /// The user ID to check for premium access
+ /// Whether the user has a personal premium subscription
+ /// True if user can access premium features; false otherwise
+ Task CanAccessPremiumAsync(Guid userId, bool hasPersonalPremium);
+
+ ///
+ /// Checks if a user has access to premium features through organization membership only.
+ /// This is useful for determining the source of premium access (personal vs organization).
+ ///
+ /// The user ID to check for organization premium access
+ /// True if user has premium access through any organization; false otherwise
+ Task HasPremiumFromOrganizationAsync(Guid userId);
+
+ ///
+ /// Checks if multiple users have access to premium features (optimized bulk operation).
+ /// Uses cached organization abilities and minimizes database queries.
+ ///
+ /// The users to check for premium access
+ /// Dictionary mapping user IDs to their premium access status (personal or through organization)
+ Task> CanAccessPremiumBulkAsync(IEnumerable users);
+}
+
diff --git a/src/Core/Auth/UserFeatures/PremiumAccess/PremiumAccessQuery.cs b/src/Core/Auth/UserFeatures/PremiumAccess/PremiumAccessQuery.cs
new file mode 100644
index 0000000000..9dc3045980
--- /dev/null
+++ b/src/Core/Auth/UserFeatures/PremiumAccess/PremiumAccessQuery.cs
@@ -0,0 +1,97 @@
+using Bit.Core.Entities;
+using Bit.Core.Repositories;
+using Bit.Core.Services;
+
+namespace Bit.Core.Auth.UserFeatures.PremiumAccess;
+
+///
+/// Query for checking premium access status for users using cached organization abilities.
+///
+public class PremiumAccessQuery : IPremiumAccessQuery
+{
+ private readonly IOrganizationUserRepository _organizationUserRepository;
+ private readonly IApplicationCacheService _applicationCacheService;
+
+ public PremiumAccessQuery(
+ IOrganizationUserRepository organizationUserRepository,
+ IApplicationCacheService applicationCacheService)
+ {
+ _organizationUserRepository = organizationUserRepository;
+ _applicationCacheService = applicationCacheService;
+ }
+
+ public async Task CanAccessPremiumAsync(User user)
+ {
+ return await CanAccessPremiumAsync(user.Id, user.Premium);
+ }
+
+ public async Task CanAccessPremiumAsync(Guid userId, bool hasPersonalPremium)
+ {
+ if (hasPersonalPremium)
+ {
+ return true;
+ }
+
+ return await HasPremiumFromOrganizationAsync(userId);
+ }
+
+ public async Task HasPremiumFromOrganizationAsync(Guid userId)
+ {
+ // Note: GetManyByUserAsync only returns Accepted and Confirmed status org users
+ var orgUsers = await _organizationUserRepository.GetManyByUserAsync(userId);
+ if (!orgUsers.Any())
+ {
+ return false;
+ }
+
+ var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
+ return orgUsers.Any(ou =>
+ orgAbilities.TryGetValue(ou.OrganizationId, out var orgAbility) &&
+ orgAbility.UsersGetPremium &&
+ orgAbility.Enabled);
+ }
+
+ public async Task> CanAccessPremiumBulkAsync(IEnumerable users)
+ {
+ var result = new Dictionary();
+ var usersList = users.ToList();
+
+ if (!usersList.Any())
+ {
+ return result;
+ }
+
+ var userIds = usersList.Select(u => u.Id).ToList();
+
+ // Get all org memberships for these users in one query
+ // Note: GetManyByManyUsersAsync only returns Accepted and Confirmed status org users
+ var allOrgUsers = await _organizationUserRepository.GetManyByManyUsersAsync(userIds);
+ var orgUsersGrouped = allOrgUsers
+ .Where(ou => ou.UserId.HasValue)
+ .GroupBy(ou => ou.UserId!.Value)
+ .ToDictionary(g => g.Key, g => g.ToList());
+
+ var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
+
+ foreach (var user in usersList)
+ {
+ var hasPersonalPremium = user.Premium;
+ if (hasPersonalPremium)
+ {
+ result[user.Id] = true;
+ continue;
+ }
+
+ var hasPremiumFromOrg = orgUsersGrouped.TryGetValue(user.Id, out var userOrgs) &&
+ userOrgs.Any(ou =>
+ orgAbilities.TryGetValue(ou.OrganizationId, out var orgAbility) &&
+ orgAbility.UsersGetPremium &&
+ orgAbility.Enabled);
+
+ result[user.Id] = hasPremiumFromOrg;
+ }
+
+ return result;
+ }
+}
+