Skip to main content
Data masking is a technique used for data protection, in which the original data is replaced with a version of the data which maintains its format and structure while removing any personally identifiable information (PII) or sensitive information. This guide shows you how you can mask data in ClickHouse using several approaches:
  • Masking policies (ClickHouse Cloud, 25.12+): Native dynamic masking applied at query time for specific users/roles
  • String replacement functions: Basic masking using built-in functions
  • Masked views: Creating views with transformation logic
  • Materialized columns: Storing masked versions alongside original data
  • Query masking rules: Masking sensitive data in logs (ClickHouse OSS)

Use masking policies (ClickHouse Cloud)

Masking policies are available in ClickHouse Cloud starting from version 25.12.
The CREATE MASKING POLICY statement provides a native way to dynamically mask column values for specific users or roles at query time. Unlike other approaches, masking policies don’t require creating separate views or storing masked data - the transformation happens transparently when users query the table.

Basic masking policy

To demonstrate masking policies, let’s create an orders table that contains customer information:
Now create a role for users who should see masked data:
Create a masking policy that applies to the masked_data_viewer role:
When a user with the masked_data_viewer role queries the orders table, they automatically see masked data:
Query
Response (for masked_data_viewer role)
Users without the masked_data_viewer role see the original, unmasked data.

Conditional masking

You can use the WHERE clause to apply masking only to specific rows. For example, to mask only high-value orders:

Multiple policies with priority

When multiple masking policies apply to the same column, use the PRIORITY clause to control which transformation is applied. Higher priority values are applied last:
In this example, for orders with total_amount > 100, the refined_masking policy (priority 10) overrides the basic_masking policy (priority 0) for the name column, while email continues to use the basic masking.

Hash-based masking

For cases where you need consistent masking (same input always produces the same masked output), use hash functions:

Managing masking policies

View all masking policies:
Drop a masking policy:
Replace an existing policy:
For more details, see the CREATE MASKING POLICY documentation.

Use string replacement functions

For basic data masking use cases, the replace family of functions offers a convenient way to mask data: For example, you can replace the name “John Smith” with a placeholder [CUSTOMER_NAME] using the replaceOne function:
Query
Response
More generically, you can use the replaceRegexpOne to replace any customer name:
Query
Response
Or you could mask a social security number, leaving only the last 4 digits using the replaceRegexpAll function.
Query
In the query above \3 is used to substitute the third capture group into the resulting string, which produces:
Response

Create masked VIEWs

A VIEW can be used in conjunction with the aforementioned string functions to apply transformations to columns containing sensitive data, before they’re presented to the user. In this way, the original data remains unchanged, and users querying the view see only the masked data. To demonstrate, let’s imagine that we have a table which stores records of customer orders. We want to make sure that a group of employees can view the information, but we don’t want them to see the full information of the customers. Run the query below to create an example table orders and insert some fictional customer order records into it:
Create a view called masked_orders:
In the SELECT clause of the view creation query above, we define transformations using the replaceRegexpOne on the name, email, phone and shipping_address fields, which are the fields containing sensitive information that we wish to partially mask. Select the data from the view:
Query
Response
Notice that the data returned from the view is partially masked, obfuscating the sensitive information. You can also create multiple views, with differing levels of obfuscation depending on the level of privileged access to information the viewer has. To ensure that users are only able to access the view returning the masked data, and not the table with the original unmasked data, you should use Role Based Access Control to ensure that specific roles only have grants to select from the view. First create the role:
Next grant SELECT privileges on the view to the role:
Because ClickHouse roles are additive, you must ensure that users who should only see the masked view don’t have any SELECT privilege on the base table via any role. As such, you should explicitly revoke base-table access to be safe:
Finally, assign the role to the appropriate users:
This ensures that users with the masked_orders_viewer role are only able to see the masked data from the view and not the original unmasked data from the table.

Use MATERIALIZED columns and column-level access restrictions

In cases where you don’t want to create a separate view, you can store masked versions of your data alongside the original data. To do so, you can use materialized columns. Values of such columns are automatically calculated according to the specified materialized expression when rows are inserted, and we can use them to create new columns with masked versions of the data. Taking the example before, instead of creating a separate VIEW for the masked data, we’ll now create masked columns using MATERIALIZED:
If you now run the following select query, you will see that the masked data is ‘materialized’ at insert time and stored alongside the original, unmasked data. It is necessary to explicitly select the masked columns as ClickHouse doesn’t automatically include materialized columns in SELECT * queries by default.
Query
Response
To ensure that users are only able to access columns containing the masked data, you can again use Role Based Access Control to ensure that specific roles only have grants to select on masked columns from orders. Recreate the role that we made previously:
Next, grant SELECT permission to the orders table:
Revoke access to any sensitive columns:
Finally, assign the role to the appropriate users:
In the case where you want to store only the masked data in the orders table, you can mark the sensitive unmasked columns as EPHEMERAL, which will ensure that columns of this type aren’t stored in the table.
If we run the same query as before, you’ll now see that only the materialized masked data was inserted into the table:
Query
Response

Use query masking rules for log data

For users of ClickHouse OSS wishing to mask log data specifically, you can make use of query masking rules (log masking) to mask data. To do so, you can define regular expression-based masking rules in the server configuration. These rules are applied to queries and all log messages before they’re stored in server logs or system tables (such as system.query_log, system.text_log, and system.processes). This helps prevent sensitive data from leaking into logs only. Note that it doesn’t mask data in query results. For example, to mask a social security number, you could add the following rule to your server configuration:
Last modified on June 23, 2026