Row-Level Security Basics
Use Row-level security to protect and filter rows of data so users only see the records they are allowed to see. This control can help you to manage multi-tenant or role-sensitive applications.
Overview
Row-level security is a server-side security filter that pre-filters records before a user even loads a dashboard or widget. It is most commonly used to enforce tenant isolation in a commingled dataset architecture.
To enable row-level security, mark the row-level security columns in Composer, then securely pass the permissions block from your backend when you generate the widget configuration.
Supported Elements
Typical row-level security columns support the following actions:
- Tenant-based filtering - For example, tenant_id, org_id, and company_id.
- Row restrictions inside a tenant - For example, role or permission fields.
- Columns that act as a security boundary.
Core Properties
Important properties of row-level security include the following:
- Restrictions should be applied so the end user is not aware filters are enforced.
- End users should not be able to see or modify the row-level security filters.
- Row-level security values should be treated as sensitive and stored on the server side.
Tutorial Video
The following tutorial video shows how to set up row-level security.
Tags: Embedding, RLS, Authorization
High-Level Process
Row-level security requires the folloiwng general configuration steps.
Step 1: Designate Dataset Fields as row-level security Fields
In Qrvey Composer, pass the row-level security values when embedding the widget by adding them to the widget configuration (securely, from your backend).
-
Open your dataset in Qrvey Composer
-
Select the options menu for a column (for example, product ID), then select Enable record level security.
-
Enter a security group name (for example, product_id) to be referenced in the widget JSON.
- It can match the field name for clarity.
- Do not include spaces or special characters. Use underscores if needed.
[Screenshot: Qrvey Composer dataset showing the three-dot menu and the enable row-level security option]
-
Apply your changes. The row-level security configuration might take a few minutes to reflect in embedded views.
Step 2. Pass row-level security values when embedding (permissions block)
When you embed a widget, you must pass the row-level security values in the widget configuration. row-level security is nested under a property named permissions. The structure is an array because you might need to define row-level security for multiple datasets at once.
Minimal structure to pass row-level security:
{ "permissions": [ { "datasetId": "DATASET_ID_HERE", "recordPermissions": [ { "securityGroupName": "product_id", "values": [ "344" ] } ] } ] }
Notes about the JSON block:
- Permissions are displayed as an array of objects. Each object represents one dataset to filter.
- Inside each dataset object, the
recordPermissionsarray contains security group objects. Each dataset can contain multiple row-level security columns. - Each security group object must include a
securityGroupNameand a values array. - Do not include Boolean operators (AND/OR) when passing a single value. Keep values minimal and clear.
[Screenshot: Partner portal or widget config area where permissions are added to the widget JSON] Where to put this block
Because row-level security values are sensitive, do not embed the raw permissions block directly in the client-side code. Becaise the widget configuration is visible on the client, use the following techniques:
- Generate the widget configuration and sensitive properties from your backend.
- Encrypt or sign the configuration as a JWT that the client can pass to the embed component.
- Make sure the client only receives the signed/encrypted token, not raw row-level security values.
[Screenshot: Visual Studio Code showing the backend REST API generating a JWT and inserting the encrypted permissions block into the token payload]
Locate the Dataset ID
The datasetId used in the permissions block serves as the internal Qrvey dataset identifier. The dataset design page displays a Composer URL that contains 0 ID (second internal ID entry).
[Screenshot: Qrvey Composer URL with dataset ID highlighted]
Copy that dataset ID into the widget permissions block.
Testing row-level security
After you add the permissions block to your server-generated widget configuration, restart your backend service if needed and load the embedded widget. If configured correctly, the embedded view display only the records allowed by the row-level security values you passed.
Example test:
- Pass a single product IT (for example,
product_id = 344) in the permissions block. - Load the widget. The widget should render only the records for product 344.
- Add a filter for
product_idfrom the embedded interface. It should only display 344.
[Screenshot: Embedded widget showing only product 344 in filter dropdown]
Comparison of Behavior Between Composer and Embedded Widgets
Note: Marking a dataset field as an row-level security field does not lock down the dataset inside Qrvey Composer. Within Composer, you can still see and filter all values for development convenience. Row-level security takes effect for embedded users when the widget receives the permissions block.
This means developers can design and test dashboards in Composer using full access, while production embedded users receive the filtered view enforced by row-level security.
Tip: To allow the the embedded widget to show all records during testing, pass a wildcard for all security columns. Use an asterisk character to represent a wildcard:
"values": ["*"]
This lets embedded views behave like Composer until you start passing explicit row-level security values.
Best Practices
- Designate row-level security fields in the dataset first before applying changes.
- Store row-level security values on the server side. Never expose raw row-level security values on the client side.
- Embed only the signed or encrypted widget configuration or JWT in the client.
- Use dataset IDs from Composer URLs to populate the permissions block.
- Test by embedding and running client-side filters to confirm only permitted values appear.
- Use the asterisk wildcard for temporary testing to mimic Composer behavior.