Column-level Security (CLS) lets organizations control access to specific columns within a BigQuery table, ensuring that sensitive data is visible only to authorized principals.
BigQuery enforces Column-level Security through a three-layer architecture:
- Policy Tag (Taxonomy): a classification label within a Data Catalog Taxonomy (e.g., "SSN" under a "Sensitivity" taxonomy).
- Schema Association: the step that attaches the Policy Tag to a specific column in the table schema.
- Data Policy: a resource that links the Policy Tag to a masking rule and grants access to specific principals.
BigQuery doesn't apply masking directly to a column. Instead, it applies masking to the tag on that column. PlainID discovers Policy Tags as protected resources and the linked Data Policies in their native form, giving users visibility into data protection in Google BigQuery and laying the foundation for centralized Policy management.
BigQuery syntax, supported masking expressions, and limitations can change independently of the Platform. Always consult the Google BigQuery documentation for the current list of valid supported syntaxes and limitations before you deploy a Column-level Policy.
For general Native Policy concepts that Row-level and Column-level Policies share (the Native tab, the Protected Resource sub-tab, and how Vendor Compare deploys changes to BigQuery), see Google BigQuery Native Support.
Dynamic Data Masking
BigQuery supports the following masking rules, which PlainID exposes as options when you define a Column-level Policy:
| Masking Rule | Behavior |
|---|---|
SHA256 |
Replaces the column value with its SHA-256 hash |
ALWAYS_NULL |
Returns NULL for all values (full redaction) |
DEFAULT_MASKING_VALUE |
Returns 0 for numbers, empty string for strings |
LAST_FOUR_CHARACTERS |
Shows only the last four characters (e.g., for SSN) |
FIRST_FOUR_CHARACTERS |
Shows only the first four characters |
EMAIL_MASK |
Masks the local part of an email address |
DATE_YEAR_MASK |
Returns only the year portion of a date |
You can also create Routines (User Defined Functions) and set them as masking logic within a Data Policy in Google BigQuery.
Viewing Column-level Policies in PlainID
PlainID displays Column-level Policies in Native mode.
To view a Column-level Policy in Native:
- In the Policies Workspace, select the relevant Policy connected to Google BigQuery.
- On the top navigation bar, click Native.
- The Native Code panel displays the Policy Core Logic, Identities Filter, and UDF (if applicable) for the Column-level Policy. See Policy Core Logic, Identities Filter, and UDF (Conditional) below for the full JSON structure of each section.
In Learn Mode, the Native view is read-only. You can view the native Column-level Policy structure, but you can't make changes.
In Manage Mode, the Native view is editable. Selecting Edit on a Column-level Policy opens the Policy Core Logic and Identities Filter sections for editing as beautified JSON. You can technically edit the fields in the UDF section, when discovered and present, but edits have no effect: the Platform reads the routine directly from BigQuery, so changes made here don't apply.
Creating a Column-level Policy from Native
Switch your Google BigQuery application in the Integration Workspace to Manage Mode to create, edit, or deploy Column-level Policies from the Policy catalog section. See Managing POPs for more information on how to change the POP Mode.
You can create new Column-level Policies for Google BigQuery directly from their native structure. There are three steps when you create a Policy:
- Details
- Code
- What
This POP has no Wizard-based creation path.
Details Step
- From the Create Policy drop-down, select From Native.
- On the Policy Details step, input a:
- Display Name
- Change the Policy ID if required
- Enter an optional description
-
The Access Type is fixed to Allow. BigQuery Data Policies only grant visibility into masked or unmasked values, so there's no restrictive option to choose here.
-
In the Google POP Details section, under the Vendor Policy Kind dropdown, choose Masking Policy.
-
Enter a Vendor Policy Name.
-
Input the following.
- Project ID: Select the project ID inherited from the POP.
- Location: Select the location manually. This must match the location of the selected Taxonomy.
- Taxonomy: select from the taxonomies discovered by the POP.
- Policy Tag ID: select from the Policy Tags discovered for the selected taxonomy.
-
Input the custom details if applicable to your Policies (optional).
-
Click Continue.
Code Step
In the Code step, input the logic and relevant links. Replace the placeholder with the intended masking logic. Each section includes its predefined structure and a logic placeholder, for example:
{
"dataMaskingPolicy": {
"predefinedExpression": "{{logic placeholder}}"
}
}
You can add // style inline comments in this step for visibility. They don't show up in the Vendor Compare section and aren't deployed to the Vendor. PlainID retains comments, but a Policy override from the Vendor also overrides the comment.
A Column-level Native Policy in BigQuery consists of two required sections and one conditional section.
- Section 1: Policy Core Logic (always present): defines the masking behavior
- Section 2: Identities Filter (always present): defines the IAM bindings
- Section 3: UDF (conditional): present only when the Data Policy uses a
routineinstead of apredefinedExpression
Policy Core Logic
The Policy Core Logic defines the masking behavior applied to the column via the dataMaskingPolicy object. This can use either a predefinedExpression or a routine, but not both.
Using a predefined expression:
{
"dataMaskingPolicy": {
"predefinedExpression": "SHA256"
}
}
Using a custom routine:
{
"dataMaskingPolicy": {
"routine": "projects/498531790395/datasets/jon_dataset/routines/mask_last_four"
}
}
Field Reference:
| Field | Description | Value |
|---|---|---|
dataMaskingPolicy |
The masking policy object. Contains either predefinedExpression or routine. |
object |
predefinedExpression |
A built-in BigQuery masking expression. Use it to apply a standard masking rule. | string |
routine |
The fully qualified resource path of a custom UDF. Format: projects/<projectId>/datasets/<dataset>/routines/<routineName>. Use it when you define a custom masking function. |
string |
Identities Filter
The Identities Filter defines the IAM bindings that control which principals receive masked access. The Platform retrieves these bindings via a separate getIamPolicy request.
{
"bindings": [
{
"members": ["<principal_1>", "<principal_2>"],
"role": "roles/bigquerydatapolicy.maskedReader"
}
]
}
Example:
{
"bindings": [
{
"role": "roles/bigquerydatapolicy.maskedReader",
"members": [
"domain:plainid.com",
"group:qa@plainid.com",
"principalSet://cloudresourcemanager.googleapis.com/projects/498531790395/type/ServiceAccount",
"serviceAccount:bq-learn-research@plainid-orchestration.iam.gserviceaccount.com",
"user:jon.kraversky@plainid.com"
]
}
]
}
Field Reference:
| Field | Description | Value |
|---|---|---|
bindings |
An array of IAM binding objects, each of which assigns a role to a set of principals. | array of objects |
members |
The Google Principals assigned to the role. Values prefixed with user: or serviceAccount: are direct assignments. The Platform treats values prefixed with group:, domain:, or principalSet: as Identity Filters. |
array of strings |
role |
The role is fixed to roles/bigquerydatapolicy.maskedReader as part of the masking Policy. You don't set or choose this value. |
"roles/bigquerydatapolicy.maskedReader" |
UDF (Conditional)
The UDF section appears only when the Data Policy uses a routine in the Policy Core Logic section above. It describes the custom masking function applied to the column.
You can technically edit the fields in this section, but editing them has no effect: the Platform reads the routine directly from BigQuery, so changes made here don't apply. To change the masking behavior, edit the UDF directly in BigQuery.
Example:
{
"arguments": [
{
"dataType": { "typeKind": "STRING" },
"name": "input_val"
}
],
"creationTime": "1779708810580",
"dataGovernanceType": "DATA_MASKING",
"definitionBody": "LPAD(SUBSTR(input_val, -4), LENGTH(input_val), '*')",
"etag": "1pZPdwussEQJ2fUxO8qLHw==",
"language": "SQL",
"lastModifiedTime": "1779708810580",
"returnType": { "typeKind": "STRING" },
"routineReference": {
"datasetId": "jon_dataset",
"projectId": "plainid-orchestration",
"routineId": "mask_last_four"
},
"routineType": "SCALAR_FUNCTION"
}
Field Reference:
| Field | Description | Value |
|---|---|---|
arguments |
The input parameters the UDF accepts. | array of objects |
arguments.dataType.typeKind |
The BigQuery data type of the argument (e.g., STRING, INT64). |
string |
arguments.name |
The name of the argument, as used in the definitionBody. |
string |
creationTime |
The Unix timestamp (in milliseconds) when BigQuery created the UDF. Read-only. | string |
dataGovernanceType |
Identifies this UDF as a data masking function. Always "DATA_MASKING" for Column-level Policies. |
"DATA_MASKING" |
definitionBody |
The SQL expression that defines the masking logic applied to the column value. | string |
language |
The language used to define the UDF body. | string |
returnType.typeKind |
The BigQuery data type the UDF returns. | string |
routineReference.datasetId |
The dataset that contains the routine. | string |
routineReference.projectId |
The GCP project that contains the routine. | string |
routineReference.routineId |
The name of the routine. | string |
routineType |
The type of routine. Always "SCALAR_FUNCTION" for masking UDFs. |
"SCALAR_FUNCTION" |
What Step
In the What Step, add the relevant Policy Tag as the Asset Type. Ensure it's the same Policy Tag ID you set in the Details step.
How PlainID Deploys a Column-level Policy
When you deploy a Column-level Policy from PlainID, the Platform orchestrates the three-layer architecture described above:
Step 1: Taxonomy and Policy Tag
The Policy Tag and its Taxonomy must already exist in BigQuery. PlainID discovers them as protected resources so you can link a Column-level Policy to the correct Policy Tag, but it doesn't create or manage Taxonomies or Policy Tags themselves.
Step 2: Schema Association
PlainID retrieves the current table schema and attaches the Policy Tag to the target column. This is a schema modification operation (tables.update).
Step 3: Data Policy
PlainID creates or updates a Data Policy resource that links the Policy Tag to the selected masking rule, and assigns the maskedReader role to the target principals.
PlainID handles the Schema Association and Data Policy steps for you whenever you deploy a Column-level Policy. Taxonomies and Policy Tags, however, must already exist in BigQuery; PlainID discovers and links to them but doesn't create or manage them.
Deploying Policy Changes
When you discover or edit a Column-level Policy in Manage Mode, PlainID doesn't push the changes to BigQuery automatically. Use the Policy Deploy operation to:
- Accept the vendor's version of a single Policy, discarding the pending Platform change.
- Accept the Platform's version of a single Policy, or all pending Policies, and push the Data Policy update to BigQuery.
The Platform marks discrepancies between the vendor and Platform versions of a Policy, and you can review them side by side in Vendor Compare. For general information on how the Native tab, Protected Resource linking, and Vendor Compare work, see Google BigQuery Native Support.
The Platform doesn't validate the native masking and filter syntax when you save changes. Validation happens in BigQuery when you deploy the Policy. If BigQuery rejects the Policy, PlainID returns the error and displays it on the Policy in Side-by-Side and Vendor Compare. For more information, see our Policy Side Panel documentation.
Known Limitations
| Limitation | Details |
|---|---|
| Native Mode | You can only use this POP in Native Mode. |
| UDF Section | You can edit the fields in the UDF section of a Column-level Policy, but edits have no effect. Make changes to the underlying routine directly in BigQuery. |
| Linked Asset | You can't unlink or replace the Policy Tag linked to a Column-level Policy. |
| Vendor Policy Section | The Vendor Policy section shown in Vendor Compare is metadata only. You can't edit it, including through Policy-as-Code import. |