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, 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,
});

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