Privacy.Anonymize OpenBI
Personal data in a table's columns found and replaced, redacted, masked or hashed as the table loads, so it never reaches the model.
Group: Privacy. OpenBI’s own: Power Query M has no function of this name.
Syntax
Privacy.Anonymize(table as table, columns as any, optional options as nullable record) as table
| Parameter | Type | Description |
|---|---|---|
table | table | The table to anonymize. |
columns | any | The column, or a list of columns, that may hold personal data. |
options | nullable record optional | Detector, Operator, Entities, Whole, Type, Salt, PresidioUrl and Language: see Remarks. |
Returns
table. The same table, the named columns anonymized as text; the other columns as they were.
Remarks
- Detector "openbi" (the default) finds names, e-mail and IP addresses, Norwegian national identity numbers (fødselsnummer and D-number), phone numbers, account numbers, IBANs and card numbers, each number checked by its check digits. A name is a common first name followed by a capitalized word, or a first name after a greeting. "presidio" adds what Microsoft Presidio's analyzer finds, at PresidioUrl (OpenBI Studio's Privacy setting unless given) in Language ("en").
- Operator "replace" (the default) writes the kind in its place: <EMAIL>, <PERSON>, <PHONE>, <CARD>, <IBAN>, <ACCOUNT>, <NO_ID>, <IP>, <LOCATION>. "redact" removes it; "mask" turns its letters and digits into * but the last two; "hash" writes the SHA-256 of Salt and the value, 64 hexadecimal digits: the same value always gets the same hash, so joins and counts still work.
- Entities limits it to some kinds, as {"EMAIL", "PHONE"}. Whole = true treats each whole value as personal (an e-mail or customer column), named by Type (PII unless given).
- It runs as part of the table's SQL, so a refresh reads the personal data only to anonymize it. When Presidio is asked for and doesn't answer, the load fails: nothing goes through un-anonymized.
- Detection follows rules, check digits and lists of names: a name it has never seen, written alone, can slip through. Presidio finds more. In Transform data, Home › Anonymize writes this step.
Compared with Power Query
An OpenBI function: Power Query has none like it, so a query that uses it runs in OpenBI only, not in Power BI Desktop or the Power BI service.
Examples
Names, phone numbers and e-mail addresses in comments
Privacy.Anonymize(
#table({"Ticket", "Comment"}, {
{1, "Call Emma Hansen on +47 912 34 567"},
{2, "Mail sent to kari@example.no"},
{3, "Printer jammed again"}}),
{"Comment"})
| Ticket | Comment |
|---|---|
| 1 | Call <PERSON> on <PHONE> |
| 2 | Mail sent to <EMAIL> |
| 3 | Printer jammed again |
Masked, and only e-mail addresses redacted
let
Tickets = #table({"Ticket", "Comment"}, {{1, "Call Emma Hansen on +47 912 34 567"}, {2, "Mail sent to kari@example.no"}}),
Masked = Privacy.Anonymize(Tickets, {"Comment"}, [Operator = "mask"]),
Redacted = Privacy.Anonymize(Tickets, {"Comment"}, [Entities = {"EMAIL"}, Operator = "redact"])
in
Table.Combine({Masked, Redacted})
| Ticket | Comment |
|---|---|
| 1 | Call **** ****en on +** *** ** *67 |
| 2 | Mail sent to ****@*******.no |
| 1 | Call Emma Hansen on +47 912 34 567 |
| 2 | Mail sent to |
A customer column hashed: counts still add up
Each e-mail address becomes the same hash every time, so the orders still group by customer.
let
Orders = #table({"Customer", "Amount"}, {{"kari@example.no", 120}, {"ola@example.no", 80}, {"kari@example.no", 45}}),
Hashed = Privacy.Anonymize(Orders, "Customer", [Whole = true, Operator = "hash", Salt = "shop-2026"]),
ByCustomer = Table.Group(Hashed, {"Customer"}, {{"Orders", Table.RowCount, Int64.Type}, {"Amount", each List.Sum([Amount]), type number}})
in
ByCustomer[[Orders], [Amount]]
| Orders | Amount |
|---|---|
| 2 | 165 |
| 1 | 80 |