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
| Field | Access | Use |
|---|---|---|
public_metadata | Readable by org members via frontend SDK | Feature flags, UI preferences, plan info |
private_metadata | Backend 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
| Field | Limit |
|---|---|
public_metadata | 64 KB |
private_metadata | 64 KB |
Both fields must be valid JSON objects (not arrays or primitives at the top level).