Skip to content

Controlling What Gets Copied: Masking & Clipboard Output

What a cell shows and what it copies are two separate hooks on a policy: renderString / renderNumber decide the display, and serializeForClipboard decides what Ctrl/Cmd+C puts on the clipboard. Masking only the display is not enough — copying falls back to the raw value. This demo puts both side by side; copy the grid and paste it into the box to compare.

Select A1:F3, copy it (Ctrl/Cmd+C), and paste into the box below to see exactly what left the grid.

📄 View Source Code
ColumnShowsCopiesHow
Emaila••••@acme.ioa••••@acme.iorenderString + serializeForClipboard
Card•••• 1234•••• 1234renderString + serializeForClipboard
API key••••••••[redacted]serializeForClipboard returns a placeholder
⚠ Phone090-••••-5678090-1234-5678renderString only — the full value leaks on copy
Amount$1,234.501234.5renderNumber only — the default copies the raw value

Mask the display and the clipboard together

Section titled “Mask the display and the clipboard together”
const maskCard = (value: unknown) => {
const digits = String(value ?? '').replace(/\D/g, '');
return digits ? `•••• ${digits.slice(-4)}` : '';
};
const CardPolicy: PolicyMixinType = {
// What the cell shows.
renderString: ({ value }) => maskCard(value),
// What Ctrl/Cmd+C copies. Without this, the raw value is copied.
serializeForClipboard: ({ point, sheet }) => maskCard(sheet.getSerializedValue({ point })),
};

Register it with useSpellbook({ policies: { card: new Policy({ mixins: [CardPolicy] }) } }) and assign it to a column with C: { policy: 'card' } in buildInitialCells.

serializeForClipboard can return anything — here a fixed placeholder, so the value never reaches the clipboard:

const SecretPolicy: PolicyMixinType = {
renderString: ({ value }) => (value ? '••••••••' : ''),
serializeForClipboard: () => '[redacted]',
};

When a policy defines no serializeForClipboard, copying uses the cell’s serialized raw value — not what is displayed. That is what you want for formatting (the Amount column copies 1234.5, which pastes cleanly into other spreadsheets), and exactly what you don’t want for a display-only mask (the ⚠ Phone column).

This controls the grid’s display and its clipboard output only. The real value is still in the sheet: it appears in the formula bar and the cell editor, and formulas, getCell and exports such as toValueMatrix return it. If a value must stay secret, don’t send it to the browser.