apex-custom-settings-hierarchy

Use when reading or writing Hierarchy Custom Settings from Apex to resolve per-user/per-profile/org configuration. Covers `getInstance()` resolution order, DML cost, cache semantics, and when to prefer Custom Metadata Types instead. NOT for List Custom Settings, Custom Metadata Types deployment packaging, or the deprecated Setup UI for editing.

Best use case

apex-custom-settings-hierarchy is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when reading or writing Hierarchy Custom Settings from Apex to resolve per-user/per-profile/org configuration. Covers `getInstance()` resolution order, DML cost, cache semantics, and when to prefer Custom Metadata Types instead. NOT for List Custom Settings, Custom Metadata Types deployment packaging, or the deprecated Setup UI for editing.

Teams using apex-custom-settings-hierarchy should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/apex-custom-settings-hierarchy/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/apex/apex-custom-settings-hierarchy/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/apex-custom-settings-hierarchy/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How apex-custom-settings-hierarchy Compares

Feature / Agentapex-custom-settings-hierarchyStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when reading or writing Hierarchy Custom Settings from Apex to resolve per-user/per-profile/org configuration. Covers `getInstance()` resolution order, DML cost, cache semantics, and when to prefer Custom Metadata Types instead. NOT for List Custom Settings, Custom Metadata Types deployment packaging, or the deprecated Setup UI for editing.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Apex Custom Settings Hierarchy

Activates when Apex reads or writes Hierarchy Custom Settings — the cached, per-user/per-profile/per-org configuration store. Produces correct resolution, null-safe defaults, and guidance on when to pick Custom Metadata Types instead.

---

## Before Starting

- Is this configuration **mutable at runtime**? If admins never change it, prefer Custom Metadata Types (deploy-time only, packageable, no DML).
- Is this configuration **per-user/profile/org**? Only Hierarchy Custom Settings offer this automatically. List Custom Settings and CMDTs do not.
- Does the caller run as **System.RunAs or as a real user**? `getInstance()` with no argument returns the setting for the *running* user — tests often surprise.
- Does the setting need to be **visible to all profiles**? Without the `Privileged = true` flag (API field `Privileged`) or a CRUD grant, unprivileged users cannot read it.

---

## Core Concepts

### The Three Accessors Resolve Differently

Hierarchy Custom Settings have three distinct Apex accessors:

- `MySetting__c.getOrgDefaults()` — always returns the org-level record (or `null` if none exists).
- `MySetting__c.getInstance(userOrProfileId)` — returns the merged record for a specific User or Profile, falling back through Profile → Org.
- `MySetting__c.getInstance()` — equivalent to `getInstance(UserInfo.getUserId())`.

When the running user has no User-level override, Salesforce falls back to their Profile-level override, and if none exists, to the org default. **An empty record is returned, not `null`**, when no tier is configured — field values will be `null` but the record itself exists. This surprises practitioners who null-check the record.

### It's Cached, But Not Free

Reads are cached within a transaction, so repeated `getInstance()` calls in the same execution are cheap. But the cache is per-transaction — a long-running batch that calls `getInstance()` in every `execute()` re-reads. DML on the setting counts against governor DML limits; mass-upserting one record per user is the wrong shape.

### Custom Metadata Is Often The Better Fit

Hierarchy Custom Settings predate Custom Metadata Types (CMDTs). CMDTs are deployable (packageable, source-tracked, sandbox-migration-friendly) while Custom Settings values are **data, not metadata** — they don't move with deploys. If your "config" is actually static code-adjacent decisions (feature toggle per environment, integration endpoint URL, retry caps), CMDT is the right home. Reserve Custom Settings for values admins must change in production Setup UI.

---

## Common Patterns

### Pattern 1: Null-Safe Read With Org Default Fallback

**When to use:** Any feature-flag or threshold lookup in Apex.

**How it works:**

```apex
public with sharing class ApiRetryConfig {
    public static Integer maxRetries() {
        RetrySettings__c s = RetrySettings__c.getInstance();
        // s is never null for Hierarchy Settings, but field values can be null.
        return (s != null && s.MaxRetries__c != null)
            ? s.MaxRetries__c.intValue()
            : 3;
    }
}
```

**Why not the alternative:** `getOrgDefaults()` misses per-user overrides. `getInstance()` with a nested null-check is the universal safe shape.

### Pattern 2: Bulk-Safe Upsert Of Per-User Overrides

**When to use:** A one-time job seeds per-user settings during onboarding.

**How it works:**

```apex
List<PerUserFlag__c> rows = new List<PerUserFlag__c>();
for (Id userId : userIds) {
    rows.add(new PerUserFlag__c(SetupOwnerId = userId, Enabled__c = true));
}
upsert rows SetupOwnerId;
```

**Why not the alternative:** `insert` row-by-row burns DML statements. Batch upsert by the `SetupOwnerId` external-ID-like key inserts new and updates existing in one DML.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| Static config that changes at deploy only | Custom Metadata Type | Packageable, deploys with source, no DML |
| Admin-editable per-user or per-profile config | Hierarchy Custom Setting | Only CS offers hierarchy fallback |
| Feature flag changed during an incident | Hierarchy Custom Setting at Org tier | Admins can flip in Setup without deploy |
| Lookup table (e.g., country → currency) | Custom Metadata Type with custom fields | Relationships and SOQL; better than List CS |
| Per-transaction cache of computed values | Platform Cache (`Cache.Org` / `Cache.Session`) | Custom Settings are not a computation cache |

---

## Recommended Workflow

1. Classify the data: runtime-mutable admin config, deploy-time code config, or derived/cached compute? Only the first belongs in Hierarchy Custom Settings.
2. Confirm the object's `Privileged` flag matches the security posture — set it if end users should not write.
3. Read through `getInstance()` for per-user fallback; document why if you pick `getOrgDefaults()` explicitly.
4. Null-check the **field** (not the record), with a sane code default for missing fields.
5. For writes, batch by `SetupOwnerId` and use `upsert ... SetupOwnerId`; never loop-inserting.
6. Write a test with `System.runAs(user)` to confirm the hierarchy resolves as expected.
7. Document in the setting's Description field who edits this in production and when.

---

## Review Checklist

- [ ] The object is actually mutable at runtime; otherwise migrate to CMDT.
- [ ] `Privileged` is set correctly for the security posture.
- [ ] Apex uses `getInstance()` with a null-safe field read AND a code default.
- [ ] Tests cover the hierarchy: org default, profile override, user override.
- [ ] Writes are batched with `upsert ... SetupOwnerId`; no DML in loops.
- [ ] The README / setting description lists the intended editors and change cadence.

---

## Salesforce-Specific Gotchas

See `references/gotchas.md` for the full list.

1. **`getInstance()` never returns `null`** for Hierarchy Settings — it returns an empty record with `null` fields. Null-check fields, not the record.
2. **`getOrgDefaults()` CAN return `null`** if no org-default row exists. Different semantics than `getInstance()`.
3. **CS values do not deploy with metadata** — seeding production after a sandbox deploy is manual or requires a data loader.
4. **`Privileged` checkbox on the object** controls whether non-admins can write. Without it, a user trigger calling `insert newCSRecord` can fail for low-privilege users.
5. **`SetupOwnerId` is polymorphic** — it accepts User or Profile IDs. The wrong type inserts as the wrong hierarchy tier silently.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| `references/examples.md` | Realistic read/write patterns and a List-CS to CMDT migration |
| `references/gotchas.md` | Hierarchy resolution surprises and packaging warnings |
| `references/llm-anti-patterns.md` | Common LLM mistakes: null-checking the record, `getOrgDefaults` drift |
| `references/well-architected.md` | OpEx / Reliability framing and CMDT vs CS tradeoffs |
| `scripts/check_apex_custom_settings_hierarchy.py` | Stdlib lint for anti-pattern usage |

---

## Related Skills

- **apex-custom-metadata-types** — when to migrate config from CS to CMDT
- **apex-platform-cache** — for runtime caches, not configuration
- **apex-user-and-permission-checks** — resolving the running user before `getInstance(userId)`

Related Skills

customer-data-request-workflow

8
from PranavNagrecha/AwesomeSalesforceSkills

Implement GDPR/CCPA data subject rights (access, deletion, rectification) using Salesforce Privacy Center and/or custom workflow. NOT for general backup or org-level data retention policy.

apex-managed-sharing-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Grant row-level access programmatically via __Share records when declarative sharing rules cannot express the policy. NOT for OWD, role hierarchy, or criteria-based sharing rule design.

omnistudio-custom-lwc-elements

8
from PranavNagrecha/AwesomeSalesforceSkills

Creating and integrating custom Lightning Web Components within OmniScripts: LWC override patterns, pubsub event handling, custom validation, OmniStudio data passing conventions. Use when a standard OmniScript element cannot meet a UX requirement. NOT for standalone LWC development (use lwc/* skills). NOT for Integration Procedures (use integration-procedures). NOT for embedding an OmniScript inside an LWC (use omnistudio-lwc-integration).

lwc-imperative-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Call Apex methods imperatively from LWC — on button click, lifecycle hooks, or conditional logic. Covers import syntax, cacheable vs non-cacheable, async/await patterns, error handling, loading states, and Promise.all. NOT for wire service (use wire-service-patterns) and NOT for testing Apex mocks (use lwc-testing).

lwc-custom-lookup

8
from PranavNagrecha/AwesomeSalesforceSkills

Custom lookup component in LWC — typeahead/autocomplete that searches records via Apex SOSL/SOQL, shows pills, supports keyboard navigation, and manages open/close state. Use when lightning-input-field or lightning-record-picker won't work (cross-org search, computed filters, custom result rendering). NOT for in-form lookups inside lightning-record-edit-form (use lightning-input-field) or lookup filters (use admin lookup filter config).

lwc-custom-event-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

When and how to design CustomEvent traffic out of an LWC — bubbles / composed / cancelable flag choices, detail payload shape, naming rules, and propagation control. Trigger keywords: 'event not reaching parent', 'composed shadow DOM', 'CustomEvent detail mutation', 'stopPropagation vs stopImmediatePropagation'. NOT for parent-to-child communication (use `@api` — see `lwc/component-communication`), NOT for sibling fan-out (use Lightning Message Service — see `lwc/lightning-message-service`), NOT for wire-service data plumbing.

lwc-custom-datatable-types

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when you need to extend `lightning-datatable` with custom cell renderings: status pills, progress bars, image thumbnails, action cells, editable pickliststo, rich-text, or any column that `lightning-datatable` does not ship out of the box. Triggers: 'custom cell type lightning datatable', 'progress bar column', 'image column', 'inline edit picklist in datatable', 'rich text column'. NOT for basic datatable usage (see `lwc-data-table`) and NOT for tree-grid or large-dataset virtualization (see `virtualized-lists`).

experience-cloud-search-customization

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when configuring or extending search on an Experience Cloud site — covering Search Manager scope configuration, LWR vs Aura search component selection, federated search setup, guest user search access, and custom search result components. NOT for SOSL/SOQL query development. NOT for internal Salesforce global search or Einstein Search for agents.

custom-property-editor-for-flow

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when building or reviewing an LWC Custom Property Editor for Flow screen or action configuration, including the `configurationEditor` metadata hook, builder-side APIs, validation, and value-change events. Triggers: 'custom property editor', 'Flow configuration editor', 'builderContext', 'inputVariables', 'configurationEditor'. NOT for ordinary runtime screen-component behavior when no Flow Builder design-time customization is involved.

dataweave-for-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when transforming structured data inside Apex — CSV → JSON, XML → SObject list, JSON → flattened CSV, or schema-mapping a third-party payload to a Salesforce model — and the existing options (`JSON.deserialize`, `Dom.Document`, hand-written loops) are getting unwieldy. Triggers: 'apex transform csv json xml without external library', 'system.dataweave script', 'salesforce native dataweave apex execute', 'transform xml to sobject apex no mulesoft', 'json reshape salesforce apex script'. NOT for MuleSoft Anypoint DataWeave running off-platform (use mulesoft-anypoint-architecture), NOT for Apex JSON serialization basics (use apex-json-serialization), NOT for Bulk API CSV ingest (use bulk-api-2-patterns).

flow-invocable-from-apex

8
from PranavNagrecha/AwesomeSalesforceSkills

Author @InvocableMethod Apex classes that Flow can call as Actions. Design the input / output variable contract, bulk semantics (one list in, one list out), null handling, and error surfacing. Also covers the inverse direction: calling a flow from Apex via Flow.Interview. NOT for general Apex authoring (use apex-service-selector-domain). NOT for REST-exposed Apex (use apex-rest-resource-patterns).

flow-custom-property-editors

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing Flow custom property editor patterns for screen components or actions, including when Flow Builder needs guided design-time configuration, generic type mapping, or builder-context-aware validation. Triggers: 'Flow custom property editor', 'configurationEditor', 'builderContext', 'inputVariables', 'Flow screen component setup'. NOT for general LWC runtime behavior when Flow Builder customization is not involved.