Member attributes
Club-defined typed person attributes, how onboarding answers save to them, and how staff edit and filter them.
Each club defines its own list of member attributes. An attribute has a stable key, a
label, a fixed type and, for choices, a list of options. Values live on the person
record under the key, as typed JSON values. Onboarding questions can save their answer to
an attribute or to a built-in person field, so renaming a question or asking the same thing
in several onboardings never splits the data.
List attribute definitions
query PersonAttributes($clubId: ID! = "51000000-0000-4000-8000-000000000051") {
personAttributes(clubId: $clubId) {
id
key
label
type
options
}
}personAttributes(clubId) returns the club's definitions in creation order, then by label. Any staff
member with access to the club can read them; they are used by the settings page, the
onboarding editor, the people filters and the person editor.
| Type | Stored JSON value | Canonical text in the API |
|---|---|---|
TEXT | String, up to 10,000 chars | The text |
NUMBER | Number | Plain decimal, e.g. 42.5 |
DATE | YYYY-MM-DD string | YYYY-MM-DD |
BOOLEAN | true or false | true or false |
CHOICE | One of options | The option |
Numbers accept up to 15 integer digits and 6 decimals; a decimal comma is accepted on input and stored as a number.
Manage definitions
mutation CreatePersonAttribute($input: CreatePersonAttributeInput!) {
createPersonAttribute(input: $input) {
attribute {
id
key
label
type
options
}
error
}
}{
"input": {
"clubId": "51000000-0000-4000-8000-000000000051",
"key": "preferred_language",
"label": "Preferred language",
"type": "CHOICE",
"options": ["French", "English"]
}
}Keys start with a lowercase letter and contain up to 40 lowercase letters, digits or
underscores; they are unique per club. Labels contain 1–100 trimmed characters.
CHOICE attributes need 2–50 distinct options of 1–100 characters; other types take no
options. A key already used in the club returns error.
updatePersonAttribute(input: { clubId, id, label, options }) changes the label and, for
choices, the options. The key and type cannot change. Updated options are copied into every
onboarding question of the club that saves to this attribute. Existing person values are
kept even when their option is removed.
deletePersonAttribute(clubId, id) returns an error naming the onboardings that still
save to the attribute. Otherwise it deletes the definition and removes the value from every
person of the club.
These three mutations require the settings_manage permission and record
club.attribute_created, club.attribute_updated or club.attribute_deleted activity.
Read and edit a person's values
PersonDetail.attributes lists every attribute defined by the person's club, in definition
order, with key, label, type, options and the person's value as canonical text, or
null when unset. People without a club have no attributes.
mutation SetPersonAttributeValues($input: SetPersonAttributeValuesInput!) {
setPersonAttributeValues(input: $input) {
person {
attributes {
key
value
}
}
error
}
}{
"input": {
"id": "00000000-0000-4000-8000-000000000084",
"clubId": "51000000-0000-4000-8000-000000000051",
"values": [
{ "key": "newsletter", "value": "false" },
{ "key": "profession", "value": null }
]
}
}Only the listed keys change; null or blank clears a value. Each value is validated against
the attribute's type and the first invalid one returns error such as
Shoe size: Enter a number. without saving anything. At most 100 values are accepted at
once and each key may appear once. The mutation requires members_manage and records a
person.updated activity whose changes are keyed by attribute label.
Save onboarding answers
Short answer, long answer and options questions accept either personField or
attributeKey on OnboardingQuestionInput (never both). The target fixes the question type
and the applicant's input format, returned as OnboardingQuestion.format:
| Target | Question type | format | Options |
|---|---|---|---|
PHONE_NUMBER | SHORT_ANSWER | PHONE | None |
BIRTH_DATE | SHORT_ANSWER | DATE | None |
GENDER | OPTIONS | GENDER | female, male, nonbinary, other |
TEXT attribute | SHORT_ANSWER, LONG_ANSWER | TEXT | None |
NUMBER attribute | SHORT_ANSWER | NUMBER | None |
DATE attribute | SHORT_ANSWER | DATE | None |
BOOLEAN attribute | OPTIONS | BOOLEAN | true, false |
CHOICE attribute | OPTIONS | CHOICE | The attribute's options |
For options targets the server replaces the submitted options with the target's. Two
questions of one onboarding cannot save to the same target, and an unknown key or an
incompatible question type returns error from saveOnboarding. Questions without a target
keep format: TEXT (CHOICE for options) and their answers stay on the application only.
Applicants' answers are validated by format when each step is saved: numbers, real
YYYY-MM-DD dates, phone numbers of 4–50 digits, spaces, +, (, ), . or -, and
birth dates not in the future. Numbers are stored in canonical form.
On acceptance, each answer is written to its target: phoneNumber, birthDate and gender
columns, or the attribute under its key as a typed value. Answers whose attribute was deleted
since the application started are skipped.
Filter people
peoplePage(attributes: [PersonAttributeFilter!]) combines up to 20 filters with the
other people filters. Each filter names a key and:
TEXT:valuematches a case-insensitive substring;BOOLEANandCHOICE:valuematches exactly (true/falsefor booleans);NUMBERandDATE: inclusivefromandtobounds, either optional;- without a value or bound, the filter keeps people who have any value.
An unknown key matches nobody. Values and bounds are limited to 200 characters.
{
"clubId": "51000000-0000-4000-8000-000000000051",
"attributes": [
{ "key": "preferred_language", "value": "French" },
{ "key": "newsletter", "value": "true" }
]
}