Skip to main content
Milana gives you granular control over what is and is not captured in session recordings. There are three mechanisms:
  • Block — the element is replaced with a blank placeholder in the recording. Its contents are never sent to Milana.
  • Mask — the element’s text or input value is replaced with masked placeholders. The element’s structure stays visible, but its text and input values are not.
  • Ignore — user interactions on the element (clicks, inputs) are not recorded, but the element’s content is still visible in the replay.
Masking applies to text and form input values only — it does not mask HTML attribute values such as alt, title, href, and src.
Passwords (input[type="password"]), phone numbers (input[type="tel"]), and email addresses (input[type="email"]) are always masked and cannot be revealed.

Default CSS classes

The easiest way to apply privacy controls is to add a CSS class to the element in your HTML or JSX. The SDK respects these classes out of the box, with no additional configuration required.
ClassEffect
milana-blockElement is blocked — replaced with a placeholder, contents not recorded
milana-maskText content and input values are replaced with masked placeholders
milana-unmaskReveals matching elements at high or xhigh, unless another privacy rule masks or blocks them
milana-ignoreUser interactions on the element are not recorded

Block an element

<!-- The entire card, including all child content, will not be recorded -->
<div class="billing-card milana-block">
  <span>Card ending in 4242</span>
</div>

Mask text content

<!-- The user's full name is captured as masked placeholders -->
<p class="milana-mask">Jonathan Appleseed</p>

Mask an input

<!-- The value typed into this field is masked regardless of input type -->
<input type="text" class="milana-mask" placeholder="National ID number" />

Ignore interactions

<!-- Clicks and keystrokes on this element are not recorded -->
<textarea class="milana-ignore" placeholder="Private notes"></textarea>

Custom classes and selectors

If you cannot use the default class names, configure your own via the privacy option when initializing.
<MilanaProvider
  productId="prd_..."
  clientKey="key_..."
  sessionInfo={{ environment: "production", version: "1.0.0" }}
  options={{
    privacy: {
      blockClass: "my-private",
      blockSelector: "[data-sensitive]",
      ignoreClass: "no-record",
      ignoreSelector: "[data-no-record]",
      maskTextClass: "redact-text",
      maskInputClass: "redact-input",
      maskSelector: "[data-mask]",
      unmaskClass: "record-public",
    },
  }}
>
  <App />
</MilanaProvider>
The selector-based options (blockSelector, ignoreSelector, maskSelector, unmaskSelector) are matched against elements while the page is being recorded. Complex selectors can affect page performance — prefer simple, specific selectors.

Full example

<MilanaProvider
  productId="prd_..."
  clientKey="key_..."
  sessionInfo={{ environment: "production", version: "1.0.0" }}
  options={{
    privacy: {
      blockClass: "milana-block",
      blockSelector: "#stripe-elements, [data-payment-form]",
      maskTextClass: "milana-mask",
      maskInputClass: "milana-mask",
      maskInputTypes: {
        password: true,
        tel: true,
      },
      shouldTrackQueryParams: true,
      queryTrackingParamsDenyList: [/^invite$/i, /^signup_token$/i],
    },
  }}
>
  <App />
</MilanaProvider>
For the full list of privacy options and their defaults, see the Privacy Options Reference.

Masking levels

The controls above mask individual elements. If you would rather mask everything by default and reveal only the elements you trust, raise the maskingLevel.
LevelInput valuesDOM text
normal (default)Masked only when explicitly markedMasked only when explicitly marked
highAll maskedMasked only when explicitly marked
xhighAll maskedAll masked
<MilanaProvider
  productId="prd_..."
  clientKey="key_..."
  sessionInfo={{ environment: "production", version: "1.0.0" }}
  options={{
    privacy: {
      maskingLevel: "xhigh",
    },
  }}
>
  <App />
</MilanaProvider>

Reveal trusted elements

At high or xhigh, add the milana-unmask class or use unmaskSelector to reveal elements that are safe to record — navigation, buttons, headings, translated product copy, and other non-sensitive UI:
<nav class="milana-unmask">...</nav>
<MilanaProvider
  productId="prd_..."
  clientKey="key_..."
  sessionInfo={{ environment: "production", version: "1.0.0" }}
  options={{
    privacy: {
      maskingLevel: "xhigh",
      unmaskSelector: "nav, h1, h2, h3, [data-public]",
    },
  }}
>
  <App />
</MilanaProvider>
milana-unmask, unmaskClass, and unmaskSelector only reveal what the masking level masked. They never override an explicit mask (milana-mask, maskSelector), a blocked element (milana-block, blockSelector), or an always-masked input type (password, tel, email). Those stay masked no matter what. If you set a custom unmaskClass, it replaces the default milana-unmask class. Set unmaskClass to an empty string ("") to disable class-based unmasking.

Layout-preserving masking

By default, masking is implemented by replacing each character with *. Enable shouldUseLayoutPreservingMasking to replace masked text with width-matched placeholders that keep replays closer to the page’s original layout. It is recommended to enable this for applications that mask long text, especially when using high or xhigh masking.
<MilanaProvider
  productId="prd_..."
  clientKey="key_..."
  sessionInfo={{ environment: "production", version: "1.0.0" }}
  options={{
    privacy: {
      shouldUseLayoutPreservingMasking: true,
    },
  }}
>
  <App />
</MilanaProvider>
Layout-preserving masking changes how masked content is rendered, not which content is masked. It may preserve approximate word widths, hyphen positions, and line breaks. Use the milana-block class when an element should be recorded as a blank placeholder instead.

iframes

Privacy controls configured in the parent page do not propagate into iframes. See the Iframe Recording guide for setup instructions and how to apply privacy controls inside iframes.

Data residency

For full control over where session data is stored, you can bring your own storage bucket. See the BYO Storage guide.