---
title: "Set person properties"
description: "Record facts about a person — email, plan, signup date — on their profile with setPersonProperties(), and define the keys first."
canonical: "https://www.togohq.ai/docs/guides/person-properties"
applies_to: "@togohq/sdk-browser 1.x, @togohq/sdk-node 1.x"
updated: "2026-11-03T00:00:00Z"
---

# Set person properties

Record facts about a person — email, plan, signup date — on their profile with setPersonProperties(), and define the keys first.

## Set, set once, unset

```ts
analytics.setPersonProperties({
  set: { plan: 'pro', email: user.email },
  setOnce: { first_plan: 'free' },
  unset: ['trial_ends_at'],
});
```

| Operation | Does                                                                   |
| --------- | ---------------------------------------------------------------------- |
| `set`     | Overwrites the value. A `null` in `set` removes the key                |
| `setOnce` | Keeps the earliest value: later `setOnce` calls for the key do nothing |
| `unset`   | Removes the keys listed                                                |

Values are on the person's profile, never on event rows, so this is where an email address
belongs ([What never goes in an event](/docs/concepts/personal-data)).

## Define a key before you send it

**Only defined keys are kept.** `email` and `name` are defined in every project; any other key
must be defined first — a type and a description — or it is dropped. An agent defines one with
[`update_person_property`](/docs/reference/mcp-tools#update_person_property), and
[`list_person_properties`](/docs/reference/mcp-tools#list_person_properties) shows what is
defined already. The install flow defines the properties your app sends before it writes the
code ([Install Togo with your AI agent](/docs/install)).

A defined property's type never changes. Its allowed values and maximum length can be narrowed
only while no stored profile holds a value they would refuse.

## Before and after sign-in

`setPersonProperties()` works before `identify()`. A profile set while the visitor is anonymous —
a lead form's company size, say — is kept, and carries forward when they sign in.

## From the server

The server SDK takes the person as the second argument, the same as `track()`:

```ts
analytics.setPersonProperties({ set: { plan: 'pro' } }, { userId: user.id });
```

## Limits

Each call is one profile update, through the same consent gate as `track()`; an empty call sends
nothing. Values follow the event limits — strings, numbers, booleans or `null`, at most 1,024
characters a value.
