Portal Community

tenantOverrideable Flag

The flag is set at the group level in Atlas_FormCategories. Setting it to 0 blocks all tenant customisation for the entire group:

-- Allow tenants to customise forms in this group
UPDATE [dbo].[Atlas_FormCategories]
SET [TenantOverrideable] = 1
WHERE [FormCategoryID] = 130;   -- GuardRails

-- Lock down a sensitive group — no tenant overrides permitted
UPDATE [dbo].[Atlas_FormCategories]
SET [TenantOverrideable] = 0
WHERE [FormCategoryID] = 140;   -- NodePolicies (example)

What Can Be Overridden

Override TypeMechanismExample
Control label / placeholderTenant SchemaJson overrides the control's label fieldRename "VAT Number" to "Tax ID" for US tenants
Visibility rulesTenant SchemaJson adds or modifies visibilityRuleHide the "Company Registration" field for sole traders
Validation rulesTenant SchemaJson tightens or relaxes validationMake phone number required for UK tenants
Player overrideSet metadata.playerOverride to a different registered playerUse a gov-accessible player for public-sector tenants
Section additionsAdd new sections/controls not in the base formAdd a "GST Details" section for Australian tenants
Default valuesSet defaultValue on controls via the tenant rowDefault currency to "AUD" for Australian tenants

Override Row in Atlas_Forms

-- Tenant 9001 customises GuardRail_Edit (FormID 13001)
-- TenantID is non-NULL; FormID matches the base form
IF NOT EXISTS (
    SELECT 1 FROM [dbo].[Atlas_Forms]
    WHERE [FormID] = 13001 AND [TenantID] = 9001
)
BEGIN
    INSERT INTO [dbo].[Atlas_Forms]
        ([FormID], [FormCode], [FormCategoryID], [FormTypeID],
         [PrimaryUsage], [NodeUsage], [TenantID], [IsActive], [SchemaJson])
    VALUES
        (13001, 'GuardRail_Edit', 130, 2,
         'guardrails', 'guardrail-edit', 9001, 1,
         N'{ "metadata": { "formId": 13001, "title": "Edit Policy (Acme)" },
             "controls": [
               { "id": "policy-name", "type": "text", "label": "Policy Name (Acme)" }
             ] }');
END

Load-Time Merge

The form loader always queries both the base row and any tenant row, then deep-merges them. The tenant row wins on any field it declares:

// Simplified form loading logic
async function loadForm(formId: number, tenantId: number | null): Promise<FormSchema> {
  const base = await db.query(
    'SELECT SchemaJson FROM Atlas_Forms WHERE FormID = @formId AND TenantID IS NULL',
    { formId }
  );

  if (!tenantId) return parseSchema(JSON.parse(base.SchemaJson));

  const override = await db.query(
    'SELECT SchemaJson FROM Atlas_Forms WHERE FormID = @formId AND TenantID = @tenantId',
    { formId, tenantId }
  );

  if (!override) return parseSchema(JSON.parse(base.SchemaJson));

  // Deep merge: tenant wins on conflicting keys
  const merged = deepMerge(
    JSON.parse(base.SchemaJson),
    JSON.parse(override.SchemaJson)
  );
  return parseSchema(merged);
}

What Cannot Be Overridden

FieldReason
FormIDPrimary key — immutable
FormCategoryIDGroup membership is fixed; a tenant cannot move a form to another group
FormTypeIDType cannot change — a list form cannot become a property form per tenant
FormCodeStable identifier used by the routing and dispatch systems
PrimaryUsageTab routing is group-wide — tenant cannot redirect to a different tab
Full Tenant Override Detail in Guide 15 This page covers tenant overrides from the Form Group perspective. For the complete guide to the override schema, JSONPath targeting, additions, and deployment workflow, see Guide 15: Tenant Overrides.