> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grainql.com/llms.txt
> Use this file to discover all available pages before exploring further.

# User properties

> setProperty stores string attributes on a user for segments and personalization.

A user property is a string attribute stored on a user, for example `plan` or `location`. The
dashboard filters and groups users by it, and remote configuration reads it to pick a value for
the user. Analytics Web sets it with `setProperty`. Grain Tag has no method for user properties.

## Set a property

```typescript theme={null}
await grain.setProperty({
  plan: 'premium',
  location: 'US',
  signup_date: '2024-01-15',
});
```

The SDK sends the properties for the effective user ID: the ID from `identify` or `login`, or
the anonymous ID. The properties stay on the user until a later call overwrites them. Read
[Identify users](/core/user-identification).

Set a property when a user signs up and when the value changes.

```typescript theme={null}
grain.identify('user_8f3a2c1b');
await grain.setProperty({ plan: 'free', source: 'landing_page' });

await grain.setProperty({ plan: 'premium', upgrade_date: new Date().toISOString() });
```

## Values

Every value becomes a string before the SDK sends it. Compare a property as a string when you
read it.

| You pass | Grain stores |
| - | - |
| `'premium'` | `premium` |
| `25` | `25` |
| `true` | `true` |
| `{ theme: 'dark' }` | `{"theme":"dark"}`, the JSON form |
| `null` or `undefined` | An empty string |

## Set properties for another user

The `userId` option sends the properties to a user other than the current one.

```typescript theme={null}
await grain.setProperty({ status: 'inactive' }, { userId: 'user_2b7d9e40' });
```

<Warning>
  With `JWT` authentication, the `userId` option must equal the subject of the token. The API
  rejects a call for another user. Grain also rate-limits a source that sets properties for many
  distinct user IDs. Read [Security](/advanced/security).
</Warning>

## Properties in remote configuration

Remote configuration segments a user by properties. Pass the same properties in the `properties`
option of `getConfigAsync`, and the response holds the value for that segment.

```typescript theme={null}
const heroText = await grain.getConfigAsync('hero_text', {
  properties: { plan: 'premium', location: 'US' },
});
```

Read [Remote configuration](/core/remote-config) and [Personalization](/guides/personalization).

## Limits

| Limit | Value | Where |
| - | - | - |
| Properties per call | 4 | `setProperty` |
| Properties per call, minimum | 1 | `setProperty` |
| Length of a key or a value | 256 characters | The Grain API |

To store more than four properties, make more than one call.

```typescript theme={null}
await grain.setProperty({ plan: 'premium', status: 'active', location: 'US', language: 'en' });
await grain.setProperty({ theme: 'dark', notifications: 'enabled' });
```

## Failures

`setProperty` returns a promise. The promise resolves after the request, and it does not reject
on a failure. The SDK logs the failure on the console instead.

| Failure | What you see | What to do |
| - | - | - |
| More than 4 properties in one call | `Maximum 4 properties allowed per request`. Nothing is sent. | Split the call. |
| Empty object | `At least one property is required`. Nothing is sent. | Pass at least one property. |
| `userId` option differs from the `JWT` subject | The API rejects the request. | Remove the `userId` option. |
| Many distinct user IDs from one source | The API rate-limits the source. | Use `identify` for the current user and drop the `userId` option. |
| Network failure | The SDK retries `retryAttempts` times with exponential backoff, then logs the error. | Read [Errors and retries](/advanced/error-handling). |

## React

`useGrainAnalytics` returns the client. Call `setProperty` on it.

```tsx theme={null}
import { useGrainAnalytics } from '@grainql/analytics-web/react';

function SettingsPage() {
  const grain = useGrainAnalytics();

  const setTheme = async (theme: string) => {
    await grain.setProperty({ theme });
  };

  return <button onClick={() => setTheme('dark')}>Dark mode</button>;
}
```

Read [React hooks](/react/overview).

## Rules for the client

1. Identify the user before you set properties. Otherwise the properties go to the anonymous ID.
2. Set a property when its value changes, not on every page load.
3. Use the same key in `setProperty` and in the `properties` option of remote configuration.
   The segment matches on the key.
4. Send only properties that the user consented to. Read
   [Privacy and consent](/essentials/privacy-and-compliance).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.