Skip to content

Sign-up fields and custom metadata

Purpose: collect what your application needs to know about a user at the moment they create their account — a company name, a team size, a marketing consent — without building a form. You define the fields; the hosted create-account page renders them; each answer lands on the new user's record, where your application can read user-owned answers with the person's access token.

The two metadata buckets

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

  • metadata — owned by you. Written from the dashboard and by a management-API token; never editable by the user. The right home for anything your application relies on.

  • userMetadata — owned by the user. They edit it themselves on the hosted /profile page or through the account API; you can also write it from the dashboard and management API. The right home for preferences and self-descriptions.

Each bucket is capped at 16 KB serialized and 8 levels deep. Neither bucket rides tokens. An authorized management-API user read returns both; the caller's account API returns only userMetadata.

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 and management API. Treat user-editable values as preferences or self-descriptions, never as authorization grants.

Defining sign-up fields

In the dashboard: SettingsCustom 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.

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 the account creation 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.

The field list is one tenant setting, editable in the dashboard or replaced wholesale through the management API. 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 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
TypeScript
const tenantId = Deno.env.get("UDIBO_TENANT_ID");
const response = await fetch(
  `https://www.udibo.com/api/identity/tenants/${tenantId}`,
  {
    method: "PATCH",
    headers: {
      authorization: `Bearer ${Deno.env.get("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

Your application updates either bucket on any user through the users API. The bucket you send replaces that bucket; a bucket you omit is untouched.

MethodPathScope
PATCH/api/identity/{tenantId}/users/{id}identity:users:write
TypeScript
const tenantId = Deno.env.get("UDIBO_TENANT_ID");
const userId = Deno.env.get("USER_ID");
const response = await fetch(
  `https://www.udibo.com/api/identity/${tenantId}/users/${userId}`,
  {
    method: "PATCH",
    headers: {
      authorization: `Bearer ${Deno.env.get("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.

Administrator credentials required. These management calls use the credential associated with an authorized Udibo dashboard session. A machine credential issued to an application in your own tenant is not accepted on /api/identity/…. Use the dashboard or an approved administrator workflow; keep administrator credentials out of your application integration.

Last verified 2026-09-11.