HelloJohn / docs
Organizations

Organization Metadata

Attach custom data to organizations using public and private metadata fields.

Organization Metadata

HelloJohn allows you to attach arbitrary key/value data to organizations using two metadata fields: public_metadata and private_metadata.


Metadata Types

FieldAccessUse
public_metadataReadable by org members via frontend SDKFeature flags, UI preferences, plan info
private_metadataBackend only (secret key required)Internal data, billing info, integration config

Both fields accept any valid JSON object.


Setting Metadata

Via API

curl -X PATCH "https://api.hellojohn.dev/v1/organizations/org_01HABCDEF777666" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321" \
  -H "Content-Type: application/json" \
  -d '{
    "public_metadata": {
      "plan": "pro",
      "features": ["sso", "audit-logs"],
      "seat_limit": 50
    },
    "private_metadata": {
      "stripe_customer_id": "cus_abc123",
      "internal_account_id": 90125
    }
  }'

Metadata is replaced, not merged. To update a single key, read first then write the full object.

Merging Metadata (Pattern)

// Read current metadata, then merge
const org = await hj.organizations.get("org_01HABCDEF777666");

await hj.organizations.update("org_01HABCDEF777666", {
  public_metadata: {
    ...org.public_metadata,
    plan: "enterprise",     // override one key
    seat_limit: 200,        // add a new key
  },
});

Reading Metadata

Public metadata (client-side)

import { useOrganization } from "@hellojohn/react";

function OrgPlanBadge() {
  const { organization } = useOrganization();
  const plan = organization.public_metadata?.plan ?? "free";

  return <span className={`badge badge-${plan}`}>{plan}</span>;
}

Private metadata (server-side only)

// Backend — requires sk_live_ key
const org = await hj.organizations.get("org_01HABCDEF777666");
const stripeId = org.private_metadata?.stripe_customer_id;

Metadata in Organization Object

{
  "id": "org_01HABCDEF777666",
  "name": "Acme Corp",
  "public_metadata": {
    "plan": "pro",
    "features": ["sso", "audit-logs"],
    "seat_limit": 50
  },
  "private_metadata": {
    "stripe_customer_id": "cus_abc123"
  }
}

Note: private_metadata is not returned in client-side SDK responses. Only backend API calls with a secret key receive it.


Common Patterns

Feature Flags

const org = await hj.organizations.get(orgId);
const hasSSO = org.public_metadata?.features?.includes("sso") ?? false;

if (!hasSSO) {
  return res.status(403).json({ error: "SSO not available on your plan" });
}

Storing Billing Provider IDs

// After creating a Stripe customer
const customer = await stripe.customers.create({ email: org.name });

await hj.organizations.update(orgId, {
  private_metadata: {
    ...org.private_metadata,
    stripe_customer_id: customer.id,
  },
});

Seat Limits

const org = await hj.organizations.get(orgId);
const seatLimit = org.public_metadata?.seat_limit ?? 5;
const currentMembers = org.member_count;

if (currentMembers >= seatLimit) {
  throw new Error("Seat limit reached. Upgrade to add more members.");
}

Size Limits

FieldLimit
public_metadata64 KB
private_metadata64 KB

Both fields must be valid JSON objects (not arrays or primitives at the top level).


On this page