BuildBaseBuildBase

User attributes

Store and update custom fields on the signed-in user, with a full change history.

User attributes are custom key-value fields on a user account - onboarding state, plan preferences, anything your app needs to remember about a person rather than a workspace.

import { useUserAttributes } from '@buildbase/sdk/react';

function OnboardingGate({ children }: { children: React.ReactNode }) {
  const { attributes, loading, updateAttribute } = useUserAttributes();

  if (loading) return <Spinner />;
  if (attributes?.onboarded) return <>{children}</>;

  return <OnboardingFlow onDone={() => updateAttribute('onboarded', true)} />;
}

Before you start

Define your attribute keys in the BuildBase console so they are available for audience segmentation and workflow conditions. Writing an undefined key still stores it, within the limits below, but it will not appear as a filterable field.

What the hook returns

FieldTypeDescription
attributesRecord<string, string | number | boolean>Current values
loadingbooleanTrue while the attributes fetch is in flight
errorstring | nullFetch error, if any
refetch()() => Promise<void>Re-read attributes from the server
updateAttribute()(key, value) => Promise<IUser>Write one attribute
updateAttributes()(updates) => Promise<IUser>Write several at once

Values are limited to string, number, and boolean. Store an object by serializing it yourself, and keep in mind that segmentation cannot filter inside a serialized blob.

Both update methods resolve to the full updated user, not just the attributes, so you can read other profile fields from the same response.

Warning

useUserAttributes() throws if it is called outside the provider tree, with "must be used within a UserProvider". If you see that error, the component is mounted above SaaSOSProvider.

Two returned fields are deprecated: isLoading combines the attributes and features pipelines, so a features fetch flips it; and refreshAttributes is the old name for refetch. Use loading and refetch.

Writing one value versus several

updateAttribute is a convenience wrapper. Prefer updateAttributes when you have more than one change - it is a single request, so there is no window where half the changes are visible:

const { updateAttributes } = useUserAttributes();

await updateAttributes({
  onboarded: true,
  plan_interest: 'scale',
  seats_estimate: 25,
});

Limits

Attributes are written by the signed-in user, from their own browser, so the server bounds what one person can store. A write over a limit is refused with a 400 and a message saying which one, and nothing in it is saved.

LimitValue
Keys in one updateAttributes() call100
Key length1 to 100 characters
Key charactersLetters, digits, _ and -, starting with a letter or digit
String value length1,024 characters
Undefined keys one user can hold100 (keys you defined in the console do not count)
All of one user's attributes, serialized64 KB

onboarded, plan_interest, onboardingStep and a console key such as plan-tier are all valid keys. A key with a ., a $ or a space is not.

The last two limits are on what the user holds rather than on one request, so a write that would take the user past either is refused. A user who already holds more than that from before these limits can still update the values they have, as long as the write does not make their attributes bigger.

Warning

A signed-in user can write any key you have not defined in the console, and any you defined as editable by them. Never read an attribute such as role or isAdmin to decide what someone may do: use roles and permissions for that.

Change history

Every attribute write is recorded. The history is readable per audience contact at GET /api/audience/:id/attributes-history, which is what the console renders on a contact's timeline.

That makes attributes usable as an audit trail - "when did this user actually finish onboarding" is answerable after the fact, not just "are they onboarded now".

Attributes versus feature flags

They look similar and are not interchangeable:

User attributesUser feature flags
Written byYour app, at runtimeThe console
PurposeRemember something about the userDecide what the user may see
Read withuseUserAttributes()useUserFeatures(), WhenUserFeatureEnabled

If your app decides the value, it is an attribute. If you want to change behavior without a deploy, it is a flag.

Next Steps