Skip to content

What Counts as PII, and Which Fields in My Database Are Actually Sensitive?

6 min read

PII (personally identifiable information) is any data that can identify a real person, on its own or combined with other data you hold. Email addresses, names, phone numbers, and IP addresses all count. A "sensitive" subset gets stricter treatment: health, sexual orientation, ethnicity, religion, political views, precise location, government IDs, biometrics, and anything financial. If a column can point at a human, treat it as personal data until you've decided otherwise on purpose.

Why this is confusing in a vibe-coded app

You never sat down and designed a data model. You asked an agent for "user accounts" and "let people save their preferences," and it created tables. Nobody labelled which columns hold personal data, because that was not the question you asked. So you end up with a schema full of fields and no idea which ones carry legal weight.

The tricky part is that PII is not about the column name. user_id sounds anonymous, but if you can join it back to an email, it identifies a person, so it is personal data. An IP address in your logs is personal data under GDPR even though it is "just a number." Free-text fields are the worst: a notes or bio column can contain anything a user typed, including a phone number or a health detail you never planned to store.

How to check

  1. List every column that holds user input or user identity. In Supabase, open the Table Editor, or run this against Postgres to dump your schema:

    select table_name, column_name, data_type
    from information_schema.columns
    where table_schema = 'public'
    order by table_name, ordinal_position;
    
  2. Sort each column into three buckets:

    • Direct identifiers: name, email, phone, username, address, IP, device ID.
    • Indirect identifiers: user_id, order IDs, timestamps, anything that links back to a person when combined with another table.
    • Sensitive: health, ethnicity, religion, sexual orientation, precise GPS, government ID, biometrics, payment card data, exact date of birth.
  3. Open your free-text columns and read real rows. bio, notes, message, feedback, description. If users can type freely, assume some of them typed personal or sensitive data, because they did.

  4. Check your logs and third-party tools too. IP addresses, emails in error logs, and full request bodies sent to analytics or an LLM are all personal data leaving your database.

The fix

  1. Write down a one-line classification per column. A short table or a comment in your schema is enough: column, bucket, why. This is the artefact a privacy policy, a data export, and a deletion feature all depend on.

  2. Stop collecting what you don't use. The safest personal data is the field you never stored. If a column is not driving a feature, drop it.

  3. Lock down the sensitive columns hardest. Restrict who and what can read them, keep them out of logs and analytics events, and never paste them into an LLM prompt without deciding that on purpose. See is it safe to put real user data in my LLM prompts and logs.

  4. Redact free-text before it leaves the database. If you send notes to a third party, assume it contains PII and treat it accordingly.

The trap to avoid

Do not assume a field is anonymous because you can't read a name in it. Hashing an email, or using a random user_id, does not make the record anonymous if you still hold the mapping back to the person. That is pseudonymous data, and the law still treats it as personal. True anonymisation means you could not re-identify the person even if you wanted to, which is rare and hard to achieve by accident.

Where this fits

Knowing which of your fields are personal data is the foundation for almost every launch obligation: your privacy policy, your data export, your delete-my-account flow, and your decision about whether GDPR applies to your app. Start by seeing the full picture in what user data is my vibe-coded app actually storing. The free Readiness Report inventories the personal and sensitive fields in your real schema and flags the ones leaking into logs or third parties, and the Finishing Pass hands you the classification and the fixes in order.