Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 5 Nov 2026. What's built · Install 0.3.0
jadpo
ReferencePersonal data

DataPersonal data

Marking personal fields

On this page

A field that identifies or describes a person says so in its block, with personal: and one category from a fixed list. The category decides how carefully the compiler treats the value. Here a user's display name identifies them, and their email address is contact data without saying so (Contact details and names need no mark).

domains/accounts/entities/user.jadpo:

example
type DisplayName = Text {    max_length: 80}entity User {    id: Uuid    email: Email {        unique: true    }    display_name: DisplayName {        personal: PersonalData.identifying    }    person: true}

Email and PersonalData are built in. DisplayName and User are defined here. person: true says each User record is a person, so erasure can find the marked field (The person).

Every marked field needs a category: there is no bare personal: true, and you can't add categories of your own. These are the fourteen:

PersonalData. For Sensitive
identifying Names, usernames, account numbers
contact Email, phone, postal address
financial Bank and card details, income, purchases
online IP address, device and cookie identifiers, browsing
demographic Date of birth, gender, nationality
government_id Passport, national insurance, driving licence yes
location Precise location yes
health Physical or mental health, including disability yes
biometric Fingerprints, face data used to identify yes
genetic Genetic data yes
beliefs Religion, political opinions, trade union membership yes
ethnicity Racial or ethnic origin yes
sex_life Sex life or sexual orientation yes
criminal Criminal convictions and offences yes

"Sensitive" covers GDPR's special categories (Articles 9 and 10) and the extra ones CCPA/CPRA lists: government IDs and precise location.

personal: goes on a field of an entity, an event or an email, or of an object, input or output a package declares, wherever Jadpo keeps or sends the value. A provider package marks what its API takes and gives, so an app sees what it is sending and receiving without reading the provider's documentation. Here the package says a customer's phone number is contact data, whatever the app fills it with:

packages/stripe/v2024_06_20/types/customer_request.jadpo
// packages/stripe/v2024_06_20/types/customer_request.jadpo/// A customer to create.public type CustomerRequest = Input {    phone: Text {        personal: PersonalData.contact    }}

The category is checked the same way everywhere (type.setting_value). An app's own object, input or output carries no mark (type.unknown_setting): it holds what a marked field gave it, and Jadpo follows the value back to that field.

Contact details and names need no mark

These standard types are personal data without a personal:, on a field of the type or of one built on it, and so is a value of one wherever it goes:

Type Counts as
Email, EmailWithName PersonalData.contact
PhoneNumber PersonalData.contact
PostalAddress PersonalData.contact
PersonName, GivenName, FamilyName PersonalData.identifying
  • It is hashed in logs, and the report lists each connection it reaches, the address an email is sent to among them. A connection that can't erase it needs an acceptance.
  • A model is sent one only where ai: { personal_data: { allowed: } } lists its category (Personal data sent to a model), a public cache can't keep one, and an audited entity can't hold one yet.
  • Erasure clears it as it clears a marked field, so on a record erasure keeps it is optional, or says erase: Erasure.retained on a person's own record (Erasure).
  • The first record that lets anyone read one, with no sign-in, says so to a person.

Here an order is kept when its buyer is erased, and what it was sent to goes:

example
entity User {    id: Uuid    email: Email    person: true}entity Order {    id: Uuid    buyer_id: User.id? {        on_delete: OnDelete.cleared    }    recipient: PersonName?    ship_to: PostalAddress?    delivery_phone: PhoneNumber?}

Written delivery_phone: PhoneNumber, it couldn't be cleared (data.erase_field_required):

example
entity User {    id: Uuid    email: Email    person: true}entity Order {    id: Uuid    buyer_id: User.id? {        on_delete: OnDelete.cleared    }    delivery_phone: PhoneNumber}
jadpo checkcheck failed: 1 error, 1 file

error Erasing a User can't clear Order.delivery_phone

--> main.jadpo:13:5

   |10 |     buyer_id: User.id? {   |     -------- keeps the record without the person13 |     delivery_phone: PhoneNumber   |     ^^^^^^^^^^^^^^ personal data that can't be `none`
reason
buyer_id is OnDelete.cleared, which means Erasure.anonymised: erasing a User keeps each order that refers to them, clearing the reference and every marked field. delivery_phone is a phone number, which counts as PersonalData.contact, and only an optional field (PhoneNumber?) can be cleared.
next
Make delivery_phone PhoneNumber?, or say erase: Erasure.deleted on buyer_id to delete the order instead (human)
rule
data.erase_field_required
help
https://jadpo.dev/docs/errors/data.erase_field_required

The other standard types that are personal data, such as a PostalCode or a Username, are hashed, listed and checked the same way, and erasure clears them only when marked.

A business's own address, phone number or manager's name is one of these too, and there is no mark that says one isn't personal data. Nothing here stops one being kept: an entity erasing a person never reaches may hold it, as a shop may hold its own phone number (What the compiler checks). It is listed as any other is, a person reads the notice and goes on, and a connection it reaches takes a one-line acceptance. On a record erasure keeps it is cleared like a person's, so a shop's number belongs on the shop, not copied onto each order. A category written on one, personal: PersonalData.contact or any other, is read as on any field.

Marked fields in logs

A marked field is written to logs as a keyed hash, whatever its type, and so is an Email or another standard type that is personal data with no mark. So a marked date_of_birth: CalendarDate is hashed, though logs otherwise show dates and numbers as values, and so is a marked field in a failure's context. The marking, not the type, decides.

The hash covers the UTF-8 bytes of the value's canonical text, as text.from gives it: 2026-10-25 for a date, GBP 10.50 for an amount. A type with no single text form, such as a PostalAddress, is hashed from its JSON as json.write writes it. So one value hashes the same in every log and on both executors, because every boundary goes through the standard types' contract.

Sensitive fields

A sensitive field is never put in a cache, and every read of one is recorded in the access records.

Records a person owns

You don't mark every field of a record a person owns. A todo's title is the user's data because the todo belongs to them (see The person), so it is exported and erased with them. Mark only the fields whose value points at, or describes, a person.

Markdown for agents: /docs/marking-personal-fields.md.Built from Jadpo main at aee2e8f.