Amazon QuickSight custom permissions are a nightmare to manage manually once you hit a few dozen users. If you are still clicking checkboxes in the console to stop analysts from exporting raw data or
Which automation pattern actually fits your setup?
I've looked at four ways to handle this. Most people default to the console, but that doesn't scale.
1. The "At Creation" approach (RegisterUser)
If you use a custom onboarding portal or a script to spin up users, don't wait for a second API call to set permissions. You can pass the permission profile directly during the RegisterUser call. This is the cleanest method for SaaS setups where you need to tie feature access (like GenBI or paginated reports) to a specific pricing tier immediately.
2. The "Blanket" approach (Account/Role Defaults)
If everyone in a specific role (e.g., all Readers) should have the same restrictions, stop managing them individually. Use UpdateAccountCustomPermission or UpdateRoleCustomPermission. This enforces a baseline for every existing and future user in that role. It's the lowest effort, but it lacks granularity.
3. The "Event-Driven" approach (EventBridge + Lambda)
This is for when you have complex logic—like "If user is in Group A, give them Profile X; if in Group B, give them Profile Y." Since QuickSight doesn't natively map groups to custom permission profiles, you have to bridge the gap. I've seen this implemented by triggering a Lambda via EventBridge whenever a user is added to a group (including IAM Identity Center groups), which then calls the permission API.
4. The "Cleanup" approach (Python Batch)
If you've already provisioned 500 users and realized they all have the wrong permissions, you need a retroactive script. You can't do this via the UI without losing your mind. A Python script using Boto3 to iterate through ListUsers and apply UpdateUserCustomPermission is the only sane way to fix a legacy mess.
Implementing the RegisterUser trigger
For those controlling the provisioning flow, avoid the "create then update" two-step process. It doubles your API calls and creates a window where the user has default (potentially too broad) permissions.
If you are using the AWS CLI, the command looks like this:
aws quicksight register-user \
--aws-account-id 123456789012 \
--namespace DEFAULT \
--principal "[email protected]" \
--role ARN:aws:quicksight:us-east-1:123456789012:role/Author \
--custom-permissions-name "Restricted-Author-Profile"
In this case, Restricted-Author-Profile must already exist in your account. If you try to pass a name that hasn't been defined in the custom permissions settings, the API will throw a ResourceNotFoundException.
Dealing with retroactive bulk updates
If you're in the "cleanup" phase, you'll likely run into rate limiting if you have thousands of users. Boto3 is the standard here, but you need to handle the pagination of the user list correctly.
Here is the logic I use for batch updating permissions for users in a specific group:
import boto3
client = boto3.client('quicksight')
ACCOUNT_ID = '123456789012'
GROUP_NAME = 'Financial-Analysts'
PERMISSION_PROFILE = 'No-Export-Profile'
# Get all users in the specific group
paginator = client.get_paginator('list_group_memberships')
for page in paginator.paginate(AwsAccountId=ACCOUNT_ID, GroupName=GROUP_NAME):
for member in page['GroupMemberships']:
user_arn = member['UserArn']
# Apply the custom permission profile to the user
client.update_user_custom_permission(
AwsAccountId=ACCOUNT_ID,
UserArn=user_arn,
CustomPermissionName=PERMISSION_PROFILE
)
One thing to watch out for: update_user_custom_permission is an asynchronous-style update in terms of how it reflects in the UI. Don't panic if the console doesn't update instantly; the API call is what matters.
When NOT to use custom permissions
Don't confuse these with Row-Level Security (RLS). Custom permissions control features (can they export? can they share?), not data (can they see the West Coast region?). If you are trying to restrict data access, you need a separate namespace mapping or RLS file. Using custom permissions to try and solve data privacy is a common mistake that leads to over-complicated architecture.
All Replies (4)
I want to try this tonight. My current script misses the namespace updates—is that a limitation of the SDK?
I'm so relieved. I wasted a whole month on manual updates before realizing the API existed. Does this play nice with Okta?
Finally! I spent hours fighting the UI before switching to the API. Does this work with the Terraform provider or just raw Python?
Pure relief seeing someone else struggle with the UI. I'm using the Terraform provider, but I keep hitting a weird 403 error...