Skip to content
Udibo
On this page
View Markdown

Sign-up fields and custom metadata

Collect additional answers on your hosted create-account form — a company name, a team size, a marketing consent — without building a form. You define the fields; the form renders them and stores answers on the new user's record. Your application can read user-owned answers with the person's access token.

The hosted waitlist form uses the same field configuration and stores answers on the waitlist entry. Social sign-up follows a different path; see where fields are collected before relying on a required answer.

The two metadata buckets

Every user record carries two free-form JSON buckets, split by who may write them:

  • metadata — maintained by your administrators through the dashboard (and the management API; see below), and confidential from the person it describes: it is never returned to that person. Users cannot read or edit it through their account page, the account API, their profile, tokens or UserInfo. A configured sign-up field can still place the person's submitted answer in this bucket when the account is created, after which they cannot read it back.

  • userMetadata — owned by the user. The hosted /profile page shows each sign-up field you store here as its own control, so people can update their answers later; keys no field defines are left alone. Your application can write any key through the account API, and you can write it from the dashboard. The right home for preferences and self-descriptions.

Each bucket must be a JSON object at the root, capped at 16 KB serialized and 8 levels deep. Keys and text values must contain valid Unicode and no NUL characters. Neither bucket rides tokens. The dashboard's user page shows both to authorized administrators; every response a person gets about themselves carries only userMetadata.

Sign-up answers are user input in either bucket. Setting target: "metadata" controls later editing; it does not verify the answer. Your application must authorize access with permission checks, rather than treating a submitted answer as a role or permission grant.

Read and update the signed-in person's metadata

Call GET /api/account on your tenant's own origin, https://{tenantId}.udibo.com, with the person's bearer access token. The response is { "userMetadata": { ... } }. It requires no management scope or dashboard seat. The credential fixes the person: there is no user or tenant selector, and machine credentials are refused.

Send PATCH /api/account with a JSON body such as { "userMetadata": { "theme": "dark", "oldPreference": null } } to update that same bucket. Keys merge at the top level, a null value deletes its key, and a nested object replaces the previous value of that key. Concurrent merges to the same account apply one after another, so none of them loses another's keys. The response has the same shape as the read. The size and depth limits apply to the final bucket. Only userMetadata is accepted; naming another person or sending account fields such as email, verification state or display name is refused.

Use target: "userMetadata" for sign-up answers your application needs to read with the person's token. The admin-owned metadata bucket is absent from this surface, including sign-up answers stored there. It remains available to authorized administrators through the dashboard. Treat user-editable values as preferences or self-descriptions, never as authorization grants.

Username and name

The hosted create-account form always asks for an email address and a password. Whether it also asks for a username and a name is yours to choose, in the dashboard under Settings → Sign-up → Account details, or as signupProfile on the tenant through the Management API:

SettingChoicesDefault
usernamerequired, optional (may be left blank), hiddenrequired
namerequired, optional (may be left blank), hiddenrequired
nameFormatfirst-last (two fields) or full (one full-name field)first-last

A tenant that never sets these asks for a required username and a required first and last name, as every tenant did before the setting existed.

  • A username left blank or not asked for is made at random, user- and eight hexadecimal digits, so it reveals nothing of the email address. The person never sees it unless your application shows it.

  • A username stays required while people sign in with one. If the login identifier is Username only or Email or username, the username must be required; set the login identifier to Email only first. A save that breaks this is refused, keyed to the setting you changed.

  • A full name is stored as the user's display name, and the first and last name stay empty. Tokens and UserInfo carry it as name with no given_name or family_name.

  • A name left blank or not asked for leaves the name empty. The dashboard and tokens show the username instead, and an empty given_name or family_name claim is left out rather than sent blank.

  • A detail the tenant does not ask for is ignored if a request posts it anyway, so a form opened before you changed the setting cannot set it.

Invited people redeeming a waitlist invitation get the same form. Social sign-up ignores these settings: it keeps the name the provider sends whatever the profile says, and makes the username from the provider's email address, as before.

Defining sign-up fields

In the dashboard: Settings → Sign-up → Custom sign-up fields. Each field is:

SettingRules
keyWhere the answer lands in the bucket. Starts with a letter; letters, digits, underscore; up to 64 characters
labelWhat the form shows. Up to 100 characters
typetext, number, boolean, or select
requiredOff by default. A required boolean must be ticked
optionsFor select: 1–50 choices, and the submitted value must be one of them
targetWhich bucket the answer lands in: userMetadata or metadata. Required, with no default

Up to 50 fields per tenant. A text answer is capped at 1024 characters. Keys the sign-up form already owns — username, email, password, confirmPassword, firstName, lastName, displayName, type, redirect, intent — are reserved and refused. A new field may not use fullName, which the full-name option uses; a field already saved with that key keeps working.

On the ordinary hosted create-account form and the waitlist join form, values are validated and typed on submit: a number must parse, a select value must be one of the field's options, and a missing required field blocks that form's submission with a per-field message. The create-account page's data also exposes the field configuration, so a client rendering its own form can offer the same inputs.

Social sign-up does not collect these fields or enforce their required answers. It creates the account from the provider's profile. Redeeming a waitlist invitation does not ask for custom fields again or copy the entry's answers into the user's metadata.

If your application needs an answer from every user, collect and check it after sign-in across every method you offer. User-owned answers can be saved through the account API. Test both the local form and social sign-up before relying on your onboarding requirements.

The field list is one tenant setting, edited in the dashboard. Through the management API, listed below for reference, a PATCH replaces the whole list, so re-send every field you keep, each with its target. A field without a target is refused with a 400 and nothing is written, so re-sending a field can never quietly move its future answers into the user-editable bucket. To move a field, send it with the other target. In the dashboard, Stored in offers Editable by the user (userMetadata) and Admin only (metadata), and Required is a checkbox, off by default. Stored in is a required choice with nothing preselected, so re-adding an existing key names its bucket too.

MethodPathScope
PATCH/api/identity/tenants/{id}identity:tenants:write

Reference only — dashboard today.

TypeScript
const tenantId = process.env.UDIBO_TENANT_ID;
const response = await fetch(
  `https://www.udibo.com/api/identity/tenants/${tenantId}`,
  {
    method: "PATCH",
    headers: {
      authorization: `Bearer ${process.env.UDIBO_API_TOKEN}`,
      "content-type": "application/json",
    },
    body: JSON.stringify({
      signupFields: [
        {
          key: "company",
          label: "Company",
          type: "text",
          required: true,
          target: "userMetadata",
        },
        {
          key: "teamSize",
          label: "Team size",
          type: "select",
          options: ["1", "2-10", "11-50", "50+"],
          target: "metadata",
        },
      ],
    }),
  },
);
if (!response.ok) throw new Error(`update failed: ${response.status}`);

Changing the field list affects future sign-ups only; nothing rewrites the answers existing users already gave.

Writing metadata after sign-up

An administrator edits either bucket on the user's page in the dashboard. The users API operation below is the management API equivalent, for reference: the bucket you send replaces that bucket; a bucket you omit is untouched.

MethodPathScope
PATCH/api/identity/{tenantId}/users/{id}identity:users:write

Reference only — dashboard today.

TypeScript
const tenantId = process.env.UDIBO_TENANT_ID;
const userId = process.env.USER_ID;
const response = await fetch(
  `https://www.udibo.com/api/identity/${tenantId}/users/${userId}`,
  {
    method: "PATCH",
    headers: {
      authorization: `Bearer ${process.env.UDIBO_API_TOKEN}`,
      "content-type": "application/json",
    },
    body: JSON.stringify({ metadata: { plan: "starter", seats: 5 } }),
  },
);
if (!response.ok) throw new Error(`update failed: ${response.status}`);

{tenantId} / {id} is your tenant's id — the same one in your dashboard URL — and the token must hold administrative authority over that tenant.

Dashboard only today. These management API operations are listed for reference: they accept only the dashboard's own session or a token from Udibo's own sign-in, which you cannot get outside the dashboard. A token your tenant issues, including a client-credentials token, is refused with 401; see the management API.

Last verified 2026-10-30.