Objects & Metadata - Guide
Framework: KernDX Package Type: Managed Package
Target Audience:
- Developers - Understanding custom objects, metadata types, and configuration patterns for framework implementation
- Architects - Designing solutions leveraging framework objects, custom settings, and metadata-driven configuration
- Business Analysts - Understanding data model, configuration options, and cross-framework relationships
What problem does this solve?
Every part of KernDX is steered by data you can see and change: configuration records that turn behaviour on, log records that capture what happened, and a handful of tracking objects that record API calls and background jobs. When you need to wire something up or look something up, you need to know which record holds it.
This guide is the map of all that data. It tells you which object or metadata type stores what, which field controls which behaviour, and how the objects connect across the framework.
Use it when you need to look up a field, wire up a configuration record, or understand how one framework's data feeds another. Developers use it as a field reference; architects use it to see the whole data model; analysts use it to find the records they configure.
Mental model
Think of these records as the framework's control panel and flight recorder. The custom metadata and custom settings are the dials and switches you turn to change how the framework behaves; the custom objects are the recorders that capture what happened (every log line, every API call, every background job). You change behaviour by editing a record, not by editing code, and you read back what the framework did by querying a record.
Use this when
- You need to configure the framework: register a trigger action, add a validation rule, set up an API endpoint, or define a feature flag. All of these are records, not code.
- You want to look up an exact field name, type, or default before you write a query or build a record.
- You are mapping how the frameworks connect, for example how a failed API call becomes a retry record, or how every framework feeds the same log object.
- You want an audit trail you can query: which API calls ran, what was logged, which background jobs executed.
Don't use this when
- A plain Salesforce validation rule or formula already does the check you need. The custom validation objects here earn their place when you need cross-object checks, field-to-field comparisons, or a rule you can switch off under a condition; for a single-field rule on one object, the built-in feature is simpler.
- You only need a one-off scheduled job and never plan to change it declaratively. Salesforce's own scheduling is enough;
ScheduledJob__cearns its place when you want admins to manage schedules as records. - You are looking for the behaviour itself rather than where it is stored. The logic lives in the Apex classes documented in the per-framework guides; this guide is the data-model reference those guides point back to.
Quick Start
Two tasks come up more than any others: adding behaviour to an object's trigger, and scheduling a recurring job. Both are done by creating configuration records, not by editing code. Here is how each one works.
Configure a trigger action via custom metadata:
You want to run some Apex logic when records on an object are saved. Create a TriggerSetting__mdt record for your object, then create a TriggerAction__mdt child record that points to your handler class. The framework finds and runs your handler automatically when the trigger fires, so you never edit the trigger file itself.
TriggerSetting__mdt:
SObjectType__c = Foobar__c (EntityDefinition)
TriggerAction__mdt:
ApexClassName__c = 'TRG_SetFoobarDefaults'
TriggerSetting__c = [above record]
Event__c = 'Before Insert'
Order__c = 1Schedule a recurring job via ScheduledJob__c:
You want a class to run on a schedule, for example a nightly clean-up. Create a ScheduledJob__c record with a cron expression (the time pattern) and the class name, then set IsActive__c = true. The framework schedules the job for you.
ScheduledJob__c job = new ScheduledJob__c();
job.SchedulerName__c = 'Nightly Log Purge';
job.ClassName__c = 'SCHED_PurgeRecords';
job.CronExpression__c = '0 0 2 * * ?';
job.Parameters__c = new DTO_NameValues(new Map<String, String>{'objectName' => 'LogEntry__c', 'minimumNumberOfDays' => '90'}).serialize();
job.IsActive__c = true;
insert job;For deeper coverage, continue reading the sections below.
Table of Contents
Expand
- What problem does this solve?
- Mental model
- Use this when
- Don't use this when
- Quick Start
- Quick Navigation
- What are the building blocks?
- How is the data model laid out?
- What does each framework store?
- How do the frameworks connect?
- How do these objects work together?
- How do I test this?
- What mistakes should I avoid?
- What are the recommended practices?
- Related Documentation
- Appendix A: Mermaid ERD
Quick Navigation
| I am a... | I need to... | Go to... |
|---|---|---|
| Architect | Understand the data model | How is the data model laid out? |
| Architect | See cross-framework relationships | How do the frameworks connect? |
| Developer | Find object field references | What does each framework store? |
| Developer | See integration patterns | How do these objects work together? |
| Analyst | Configure trigger settings | Trigger Action Framework |
| Analyst | Configure validation rules | Validation Framework |
| Analyst | Configure API settings | Web Service Framework |
| Analyst | Configure feature flags | Feature Management Framework |
What are the building blocks?
These records hold configuration, logs, and operational data only. They do not contain business logic: that lives in the Apex classes documented in the other guides.
Package Data Model: 10 custom objects, 15 custom metadata types, 57 pre-built metadata records, and 1 platform event. These objects underpin the Logging, Trigger, Web Service, Feature Flag, Async, and Validation frameworks.
For current framework statistics, see Metrics.
The package groups its data into four kinds of building block. Each kind plays a different role:
- Custom Objects store data that needs to persist and be queried: logs, configuration, and operational records.
- Custom Settings hold runtime configuration and feature toggles, which you can set per user, per profile, or org-wide.
- Platform Events carry messages between parts of the system so work can happen in the background instead of slowing the user down.
- Custom Metadata Types hold declarative configuration: you change framework behaviour by editing a record, not by editing code.
Together these objects power six integrated frameworks: Logging, Trigger Actions, Web Services, Feature Management, Asynchronous Operations, and Data Management. If you are new to the underlying Salesforce concepts, the official Salesforce documentation explains Custom Metadata Types, Custom Objects, and Platform Events.
How is the data model laid out?
Custom Objects
| Object | Purpose | Key Features |
|---|---|---|
LogEntry__c | Persistent log storage | Severity levels, stack traces, record associations, correlation IDs |
ScheduledJob__c | Scheduled job management | Cron expressions, execution history, active/inactive control |
LoginFrequency__c | User login analytics | Tracks monthly login counts per user |
ApiCall__c | API orchestration & tracking | Request/response logging, retry logic, performance metrics |
ApiIssue__c | Failed API retry management | Error details, retry status, parameter hashing |
AsyncChainExecution__c | Async chain orchestration tracking | Step progress, duration, correlation IDs, context data |
Custom Settings
| Custom Setting | Type | Purpose |
|---|---|---|
ApiRuntimeSwitch__c | Hierarchy | A master off-switch you can flip in an incident without a deployment (kill-switch): disable all APIs per user, profile, or org |
LogSetting__c | Hierarchy | Log level thresholds and performance logging toggles (per user / profile / org) |
ScheduleSetting__c | List | Runtime state for scheduled jobs (last run time) |
Platform Events
| Event | Purpose | Consumed By |
|---|---|---|
LogEntryEvent__e | Asynchronous log publishing | TRG_LogEntryEvent trigger handler |
Custom Metadata Types
All 15 custom metadata types and how they relate
| Metadata Type | Purpose | Parent/Child Relationships |
|---|---|---|
AsynchronousJobSetting__mdt | Asynchronous job configuration | - |
ClassTypeResolver__mdt | Class name resolution | - |
FeatureFlag__mdt | Feature flag definitions | Parent of FeatureFlagStrategy__mdt |
FeatureFlagStrategy__mdt | Feature flag evaluation rules | Child of FeatureFlag__mdt |
FieldSetGroup__mdt | Field set grouping | - |
MaskingRule__mdt | Data masking rule definitions (what to mask and how) | Parent of MaskingTarget__mdt |
MaskingTarget__mdt | Rule-to-field wirings (where to apply each rule) | Child of MaskingRule__mdt |
TriggerAction__mdt | Trigger action definitions | Child of TriggerSetting__mdt via TriggerSetting__c |
TriggerSetting__mdt | Per-object trigger configuration | Parent of TriggerAction__mdt |
PostTriggerAction__mdt | Actions run once at end of the trigger transaction | Optional child of TriggerSetting__mdt via TriggerSetting__c |
ApiCredential__mdt | API credentials | Parent of ApiSetting__mdt |
ApiMock__mdt | API mock response configuration | - |
ApiSetting__mdt | API endpoint configuration | Child of ApiCredential__mdt |
ValidationRuleGroup__mdt | Validation rule grouping per object | Child of TriggerSetting__mdt, parent of ValidationRule__mdt |
ValidationRule__mdt | Individual validation rule configuration | Child of ValidationRuleGroup__mdt |
What does each framework store?
Logging Framework
What it does: Saves your log messages as real, searchable records instead of debug logs that expire, and does it in the background so logging never slows down the user's transaction. Each entry carries a category, a severity level, and an optional link to the record it relates to.
The pieces: You write logs with LOG_Builder. Each one is published as the LogEntryEvent__e platform event and then saved to the LogEntry__c object. LogSetting__c controls how much gets logged. Anything sensitive in a log is automatically redacted by the shared data masking framework (see MaskingRule__mdt and MaskingTarget__mdt).
See also: Logging - Guide, Fast Start - Logging
Architecture Diagram
+-----------------------------------------------------------------+
| LOG_Builder (Apex Code) UTIL_PerformanceTimer |
| |
| Logging Methods: Context Methods: Performance: |
| .info() .startCorrelation() .start() |
| .warn() .setCorrelationId() .stop() |
| .error() .setGlobalContext() .stopSilent() |
| .debug() .clearGlobalContext() |
| .serializeContext() Buffering: |
| .hydrateContext() .suspendSaving() |
| .resumeSaving() |
+-----------------+-----------------------------------------------+
| publishes
v
+-----------------------------------------------------------------+
| LogEntryEvent__e (Platform Event) |
| - Message, LogLevel, ClassMethod, RecordId |
| - CorrelationId, TransactionId, ParentTransactionId |
| - ContextData (JSON), DurationMs |
+-----------------+-----------------------------------------------+
| consumed by
v
+-----------------+
| TRG_LogEntryEvent | (Trigger Handler)
+--------+--------+
| creates
v
+-----------------------------+
| LogEntry__c |
| - Message, ClassMethod |
| - LogLevel, StackTrace |
| - RecordId, UserId |
| - CorrelationId | Configuration:
| - TransactionId | +-----------------------------+
| - ParentTransactionId | | MaskingRule__mdt |
| - ContextData | | - Mode (Regex/JsonKey/Exact)|
| - DurationMs | | - Pattern, Replacement |
+-----------------------------+ | - FailureAction, IsActive |
+-----------------------------+
+-----------------------------+
| MaskingTarget__mdt |
| - Rule, SObjectType, Field |
| - CallerClass, IsActive |
+-----------------------------+
+-----------------------------+
| LogSetting__c |
| - IsEnabled |
| - LogLevelThreshold |
| - EnablePerformanceLogging |
| - PerformanceThresholdMs |
| - MaxContextDataSize |
+-----------------------------+Objects
LogEntry__c (Custom Object)
Description: Stores all log messages generated from Apex code.
Fields:
| Field | Type | Description |
|---|---|---|
ContextData__c | Long Text Area(32768) | Structured context data as JSON (key-value pairs for debugging) |
CorrelationId__c | Text(36) | Correlation ID linking related log entries across transactions |
DurationMs__c | Number(18,0) | Duration in milliseconds (for performance-timed operations) |
ExceptionType__c | Text(255) | Exception type associated with the log |
ExecutionEvent__c | Picklist | Salesforce execution context (Quiddity): SYNCHRONOUS, BATCH_APEX, FUTURE, QUEUEABLE, etc. |
Limits__c | Long Text Area(1024) | JSON snapshot of Salesforce governor limits at the time the event was logged |
LineNumber__c | Number(8,0) | Source code line number where the exception occurred |
LogLevel__c | Picklist | Logging level: DEBUG, INFO, WARN, ERROR |
Message__c | Long Text Area(32768) | The actual log message |
ParentTransactionId__c | Text(36) | Parent transaction ID for hierarchical transaction tracking |
RecordId__c | Text(18) | Optional record ID this log is associated with |
RecordLink__c | Formula(Text) | Hyperlink to the associated record |
ClassMethod__c | Text(255) | Source of log message (ClassName.MethodName) |
ShortMessage__c | Text(255) | Brief summary of the log message, safe for SOQL WHERE and GROUP BY clauses |
StackTrace__c | Long Text Area(32768) | Stack trace information if applicable |
TransactionId__c | Text(36) | Unique transaction ID for grouping logs within a single execution |
UserId__c | Text(18) | The user ID who triggered this log |
UserLink__c | Formula(Text) | Hyperlink to the user who produced this log |
Fingerprint__c | Text(216) | Flood-control grouping key (External ID, unique); blank on ungrouped entries |
OccurrenceCount__c | Number(12,0) | Occurrences collapsed into a daily rollup row; populated on rollup rows only |
Relationships:
UserId__cstores the User ID (text field, not a lookup)
Usage:
// Log an error with record association
LOG_Builder.build().error(ex).at('MyClass.myMethod').forRecord(recordId).emit();
// Log informational message with record association
LOG_Builder.build().info('Processing completed successfully').at('MyClass.myMethod').forRecord(recordId).emit();
// Use correlation for distributed tracing
LOG_Builder.startCorrelation();
LOG_Builder.setGlobalContext('accountId', accountId);
LOG_Builder.build().info('Processing started').emitAt('MyClass.myMethod');LogEntryEvent__e (Platform Event)
Description: Platform event for asynchronous log message publishing.
Fields:
| Field | Type | Description |
|---|---|---|
ContextData__c | Long Text Area(5000) | Structured context data as JSON |
CorrelationId__c | Text(36) | Correlation ID linking related log entries |
DurationMs__c | Number(18,0) | Duration in milliseconds for timed operations |
ExceptionType__c | Text(255) | Exception type |
ExecutionEvent__c | Text(40) | Salesforce execution context (Quiddity): SYNCHRONOUS, BATCH_APEX, FUTURE, QUEUEABLE, etc. |
Limits__c | Long Text Area(1024) | JSON snapshot of Salesforce governor limits at the time the event was logged |
LineNumber__c | Number(8,0) | Source code line number where the exception occurred |
LogLevel__c | Text(20) | Logging level (DEBUG, INFO, WARN, ERROR) |
Message__c | Long Text Area(5000) | Log message |
ParentTransactionId__c | Text(36) | Parent transaction ID for hierarchical tracking |
RecordId__c | Text(18) | Optional record ID |
ClassMethod__c | Text(255) | Source (ClassName.MethodName) |
ShortMessage__c | Text(255) | Brief summary of the log message, safe for SOQL WHERE and GROUP BY clauses |
StackTrace__c | Long Text Area(5000) | Stack trace |
TransactionId__c | Text(36) | Unique transaction ID |
UserId__c | Text(18) | The user ID who triggered this event |
Usage:
- Published automatically by
LOG_Builder - Consumed by the
TRG_LogEntryEventtrigger, which creates theLogEntry__crecords - Lets logging run in the background, so it does not slow down the user's transaction
- Carries a correlation ID (one tracking ID that follows a single user action across triggers, queries, callouts, and jobs) plus a transaction ID, so you can trace one action through every step
Sensitive data in log entries is redacted by the data masking framework (MaskingRule__mdt plus MaskingTarget__mdt) before the platform event is published. Out of the box, the active MaskSecretKeys and MaskPaymentCard rules are applied to every text field on LogEntryEvent__e, so a raw secret or card number never gets saved through the log-entry pipeline. You get this protection without any setup.
LogSetting__c (Hierarchy Custom Setting)
Description: Controls logging thresholds and performance logging configuration. As a hierarchy custom setting, values can be set at org-default, profile, or user level. Code-level defaults are provided by LOG_Engine.getLogSetting().
Fields:
| Field | Type | Description |
|---|---|---|
IsEnabled__c | Checkbox | Whether logging is enabled |
LogLevelThreshold__c | Text | Minimum log level threshold: DEBUG, INFO, WARN, ERROR |
EnablePerformanceLogging__c | Checkbox | Enable webservice performance timer logging |
EnableQueryPerformanceLogging__c | Checkbox | Enable query performance logging |
EnableTriggerPerformanceLogging__c | Checkbox | Enable trigger action performance logging |
EnableValidationPerformanceLogging__c | Checkbox | Enable validation context performance logging |
MaxContextDataSize__c | Number(6,0) | Maximum size of context data to log (bytes) |
PerformanceThresholdMs__c | Number(10,0) | Webservice performance threshold (ms, Default: 10000 = 10 seconds) |
QueryPerformanceThresholdMs__c | Number(10,0) | Query performance threshold (ms, Default: 1000 = 1 second) |
TriggerPerformanceThresholdMs__c | Number(10,0) | Trigger action performance threshold (ms) |
ValidationPerformanceThresholdMs__c | Number(10,0) | Validation context performance threshold (ms) |
Usage:
- Org Default: Global settings applied to all users (e.g., enable performance logging globally)
- Profile: Override settings for a specific profile
- User: Override settings for a specific user
Performance Logging Configuration:
- Webservice Performance: Uses
EnablePerformanceLogging__candPerformanceThresholdMs__c(10s default - appropriate for network calls) - Query Performance: Uses
EnableQueryPerformanceLogging__candQueryPerformanceThresholdMs__c(1s default - appropriate for database queries) - Trigger Performance: Uses
EnableTriggerPerformanceLogging__candTriggerPerformanceThresholdMs__c - Validation Performance: Uses
EnableValidationPerformanceLogging__candValidationPerformanceThresholdMs__c
Example Configuration:
LogSetting__c (Org Default):
- IsEnabled__c: true
- LogLevelThreshold__c: INFO
- EnablePerformanceLogging__c: true
- PerformanceThresholdMs__c: 10000 (log webservice calls > 10s)
- EnableQueryPerformanceLogging__c: true
- QueryPerformanceThresholdMs__c: 1000 (log queries > 1s)
Defaults are provided code-level by LOG_Engine.getLogSetting() when no custom setting record exists.Trigger Action Framework
What it does: Lets you add behaviour to an object's trigger by creating a configuration record instead of editing trigger code. Each piece of logic lives in its own small Apex class, so triggers stay easy to read, reorder, and switch off without a deployment.
The pieces: A TriggerSetting__mdt record turns the framework on for one object; one TriggerAction__mdt record per behaviour points to a handler class and sets its run order. At runtime TRG_Dispatcher reads that configuration and runs each handler (handlers extend TRG_Base and implement an IF_Trigger interface).
See also: Triggers - Guide, Fast Start - Trigger Actions
Architecture Diagram
+---------------------+
| Physical Trigger |
| TRG_Account |
| - All contexts |
+----------+----------+
| calls
v
+---------------------+
| TRG_Dispatcher |
| .run() |
+----------+----------+
| queries configuration
v
+---------------------+ 1:1 +---------------------+
| TriggerSetting__mdt |<-----------| Account SObject |
| - SObjectType | +---------------------+
| - BypassExecution |
| - RequiredPerm |
+----------+----------+
| 1:N (per context)
v
+---------------------+
| TriggerAction__mdt |
| - ApexClassName |
| - Order |
| - Event__c |--+
| - TriggerSetting__c | | (picklist + lookup)
| - BypassExecution | |
| - RequiredPerm | |
+----------+----------+ |
| |
| instantiates |
v |
+---------------------+ |
| TRG_AccountValidator| |
| extends | |
| TRG_Base | |
| implements | |
| IF_Trigger | |
| .BeforeInsert |<-+
+---------------------+Objects
TriggerSetting__mdt (Custom Metadata)
Description: Contains common configuration for trigger actions per SObject, including bypass settings and permissions.
Fields:
| Field | Type | Description |
|---|---|---|
SObjectType__c | MetadataRelationship(EntityDefinition) (Required) | The SObject this trigger setting applies to |
BypassExecution__c | Checkbox | Bypasses ALL trigger actions on this object (Default: false) |
BypassFeatureFlag__c | MetadataRelationship(FeatureFlag__mdt) | Feature Flag that bypasses triggers when enabled |
RequiredFeatureFlag__c | MetadataRelationship(FeatureFlag__mdt) | Feature Flag required for triggers to execute |
EnablePerformanceLogging__c | Checkbox | Master switch for performance logging on this object |
PerformanceThresholdMs__c | Number(10,0) | Object-level performance threshold (ms) |
ApplyMasking__c | Checkbox | Run the data masking pass on this object before insert/update (Default: true; uncheck to opt out) |
ObjectApiNameOverride__c | Text(255) | Optional override of SObjectType__c at dispatch, used to register a CDC entity (e.g. Foobar__ChangeEvent) |
Relationships:
- Parent of
TriggerAction__mdtviaTriggerSetting__c(child relationship:TriggerActions__r) - Parent of
ValidationRuleGroup__mdtviaTriggerSetting__c(child relationship:ValidationRuleGroups__r)
Pre-built records (6 shipped): framework settings for objects the package itself triggers on (LogEntryEvent__e, ApiCall__c, ApiIssue__c, AsyncChainExecution__c, etc.). You add your own TriggerSetting__mdt records for your objects.
Usage:
Create one TriggerSetting__mdt record per SObject:
- Label: Account Trigger Settings
- SObjectType__c: Account (EntityDefinition)
- BypassExecution__c: falseTriggerAction__mdt (Custom Metadata)
Description: Defines individual trigger actions and their execution order within trigger contexts.
Fields:
All TriggerAction__mdt fields
| Field | Type | Description |
|---|---|---|
ApexClassName__c | Text(100) (Required) | Fully qualified action class name |
Description__c | Long Text Area | Action description |
Event__c | Picklist (Required) | Trigger event: Before Insert, After Insert, Before Update, After Update, Before Delete, After Delete, After Undelete |
TriggerSetting__c | MetadataRelationship(TriggerSetting__mdt) (Required) | Link to the parent TriggerSetting for the target SObject |
Order__c | Number(4,0) (Required) | Execution order (lower = earlier) |
BypassExecution__c | Checkbox | Bypass this specific action (Default: false) |
BypassFeatureFlag__c | MetadataRelationship(FeatureFlag__mdt) | Feature Flag that bypasses this action when enabled |
RequiredFeatureFlag__c | MetadataRelationship(FeatureFlag__mdt) | Feature Flag required for this action to execute |
AllowRecursion__c | Checkbox | Allow recursive execution |
AllowNonSelfInitiated__c | Checkbox | Allow execution from other triggers |
EntryCriteriaFormula__c | Long Text Area | Formula for conditional execution |
EntryCriteriaContextClassName__c | Text(100) | Context class for formula evaluation |
ForcePerformanceLogging__c | Checkbox | Always log performance for this action |
SuppressPerformanceLogging__c | Checkbox | Never log performance for this action |
PerformanceThresholdMs__c | Number(8,0) | Log if duration exceeds threshold (ms) |
Relationships:
- MetadataRelationship to
TriggerSetting__mdtviaTriggerSetting__c(child relationship:TriggerActions__r)
Pre-built records (6 shipped): framework actions on framework-owned objects (e.g., TRG_LogEntryEvent_AfterInsert, retry orchestration on ApiIssue__c). You add your own TriggerAction__mdt records to wire up your handler classes.
Usage:
Example Configuration:
- Label: Account Validator
- ApexClassName__c: TRG_AccountValidator
- Event__c: Before Insert
- TriggerSetting__c: Account (TriggerSetting__mdt)
- Order__c: 10
- BypassExecution__c: falsePostTriggerAction__mdt (Custom Metadata)
Description: Defines declarative actions that run once at the end of the trigger transaction, after every per-record trigger action has completed. Use post-trigger actions for transaction-level work such as a single rollup or one outbound notification per save. A post-action cannot perform DML: attempting it always fails the transaction.
Fields:
| Field | Type | Description |
|---|---|---|
ApexClassName__c | Text(100) | Class implementing IF_Trigger.PostAction (DML is not permitted inside a post-action) |
TriggerSetting__c | MetadataRelationship(TriggerSetting__mdt) | Optional. Set to scope the action to one SObject; leave blank to fire on every outermost dispatch |
FailureAction__c | Picklist | On error: Log and Continue (the default, where the save proceeds) or Block DML (the save stops and the error is shown to the user) |
Order__c | Number(3,0) | Execution order (lower runs first; leave gaps so new actions can be inserted) |
Description__c | Text | Action description |
EntryCriteriaContextClassName__c | Text(100) | Optional class implementing IF_Trigger.PostActionEntryCriteria; shouldRun(context) returning false skips it |
BypassExecution__c | Checkbox | Bypass this specific post-action |
BypassFeatureFlag__c | MetadataRelationship(FeatureFlag__mdt) | Feature Flag that bypasses this action when enabled |
RequiredFeatureFlag__c | MetadataRelationship(FeatureFlag__mdt) | Feature Flag required for this action to execute |
ForcePerformanceLogging__c | Checkbox | Always log performance for this action |
PerformanceThresholdMs__c | Number(8,0) | Log if duration exceeds threshold (ms) |
Relationships:
- Optional MetadataRelationship to
TriggerSetting__mdtviaTriggerSetting__c. UnlikeTriggerAction__mdt, this link is not required: leave it blank to run the action on every outermost trigger dispatch.
See also: Triggers - Guide for the IF_Trigger.PostAction contract and entry-criteria details.
Validation Framework
What it does: Stops bad data from being saved by checking it against rules you write as formulas, no Apex required. It handles cases standard Salesforce validation rules struggle with, such as checking related records, comparing fields across an object, or turning a rule off under a condition.
The pieces: You group rules per object with ValidationRuleGroup__mdt and write each individual check as a ValidationRule__mdt record. At runtime TRG_ExecuteValidationRules runs the rules (backed by UTIL_ValidationRule).
See also: Validation - Guide, Fast Start - Custom Validations
Objects
ValidationRuleGroup__mdt (Custom Metadata)
Description: Groups validation rules for a specific object and trigger context. Linked to TriggerSetting__mdt via TriggerSetting__c.
Fields:
| Field | Type | Description |
|---|---|---|
TriggerSetting__c | MetadataRelationship(TriggerSetting__mdt) (Required) | Links to the object's TriggerSetting |
TriggerTiming__c | Text (Required) | Semicolon-separated: Before, After |
TriggerOperations__c | Text (Required) | Semicolon-separated: Insert, Update, Delete, Undelete |
Description__c | Long Text Area (Required) | Business purpose of this group |
ContextClassName__c | Text(100) | Default context class for rules in this group |
ExecutionStrategy__c | Picklist | Accumulate (default) or Fail Fast |
BypassExecution__c | Checkbox | Bypass all rules in this group (Default: false) |
BypassFeatureFlag__c | MetadataRelationship(FeatureFlag__mdt) | Feature Flag that bypasses all rules when enabled |
RequiredFeatureFlag__c | MetadataRelationship(FeatureFlag__mdt) | Feature Flag required for rules to execute |
Relationships:
- MetadataRelationship to
TriggerSetting__mdtviaTriggerSetting__c - Parent of
ValidationRule__mdtviaValidationRuleGroup__c
Pre-built records: none; this type is for your own use. You create ValidationRuleGroup__mdt records to configure validation rules.
Usage:
Example Configuration:
- Label: Account Field Validations
- TriggerSetting__c: Account (TriggerSetting__mdt)
- TriggerTiming__c: Before
- TriggerOperations__c: Insert;Update
- ExecutionStrategy__c: AccumulateValidationRule__mdt (Custom Metadata)
Description: Individual validation rule configuration. Formula returns true when validation fails.
Fields:
| Field | Type | Description |
|---|---|---|
ValidationRuleGroup__c | MetadataRelationship(ValidationRuleGroup__mdt) (Required) | Parent group |
RuleFormula__c | Long Text Area (Required) | Formula that returns true when validation fails |
ErrorMessage__c | Long Text Area (Required) | Message shown when validation fails |
ErrorDisplayField__c | Text(255) | API name of field to attach error to |
Severity__c | Picklist (Required) | Error (blocks save) or Warning (logs only) |
Order__c | Number(4,0) | Execution order (lower = first) |
Description__c | Long Text Area (Required) | Business purpose of this rule |
ContextClassName__c | Text(100) | Override context class for this rule |
ShadowMode__c | Checkbox | Run the rule watch-only (shadow mode): it logs what it would do but does not block the save (Default: false) |
BypassExecution__c | Checkbox | Bypass this rule (Default: false) |
BypassFeatureFlag__c | MetadataRelationship(FeatureFlag__mdt) | Feature Flag that bypasses this rule when enabled |
RequiredFeatureFlag__c | MetadataRelationship(FeatureFlag__mdt) | Feature Flag required for this rule to execute |
Relationships:
- MetadataRelationship to
ValidationRuleGroup__mdtviaValidationRuleGroup__c
Pre-built records: none; this type is for your own use. You create ValidationRule__mdt records to configure rule formulas.
Usage:
Example Configuration:
- Label: Require Industry for Enterprise Accounts
- ValidationRuleGroup__c: Account Field Validations (ValidationRuleGroup__mdt)
- RuleFormula__c: newRecord.Type = 'Enterprise' && ISBLANK(newRecord.Industry)
- ErrorMessage__c: Industry is required for Enterprise accounts
- ErrorDisplayField__c: Industry
- Severity__c: Error
- Order__c: 10Web Service Framework
What it does: Handles calls to and from external systems over HTTP/REST, and takes care of the work that surrounds every integration: logging each call, retrying failures, redacting sensitive data, and saving requests before commit. You write the part that is specific to your integration; the framework runs the rest.
The pieces: Outbound calls extend API_Outbound and inbound ones extend API_Inbound. Each call is recorded in ApiCall__c, and failures that need retrying land in ApiIssue__c. Configuration lives in ApiSetting__mdt (endpoints and behaviour), ApiCredential__mdt (authentication), and ApiMock__mdt (mock responses for testing). Request and response payloads are redacted by the shared data masking framework (see MaskingRule__mdt and MaskingTarget__mdt).
See also: Web Services - Guide, Fast Start - Outbound APIs, Fast Start - Inbound APIs
Architecture Diagram
+---------------------+
| API Class |
| API_GetCustomer |
| extends |
| API_Outbound |
+----------+----------+
| reads configuration
v
+---------------------+ 1:1 +---------------------+
| ApiSetting__mdt |<-----------| ApiCredential__mdt |
| - ClassName | | - NamedCredential |
| - EndpointPath | +---------------------+
| - MaxRetryCount |
| - RetryBackoffSecs |
| - IdempotencyEnabled|
| - MockingEnabled |
| - LogIssues |
+----------+----------+
| executes API call
v
+---------------------+
| HTTP Request/ |
| Response |
+----------+----------+
| logs to
v
+---------------------+ +---------------------+
| ApiCall__c | | MaskingRule__mdt |
| - Request |<--redacted by--| - Mode |
| - Response | data masking | - Pattern |
| - Status | framework | - Replacement |
| - Retries | | - FailureAction |
| - CalloutDurationMs | +---------------------+
| - ErrorMessages | ^ wired by
+----------+----------+ +---------------------+
| MaskingTarget__mdt |
| - Rule, SObjectType |
| - Field, CallerClass|
+---------------------+
| 1:N
v
+---------------------+
| ApiIssue__c |
| - ErrorMessages |
| - RequestParameters |
| - Status |
+---------------------+Objects
ApiCall__c (Custom Object)
Description: Records every webservice call, inbound and outbound, with full logging and retry management in one place.
Fields:
All ApiCall__c fields
| Field | Type | Description |
|---|---|---|
ServiceName__c | Text(100) | Fully qualified name of the API service handler class |
Direction__c | Picklist | Inbound, Outbound |
Status__c | Picklist | Queued, Processing, Completed, Failed, Retry, Retrying, Batched, Aborted |
HttpMethod__c | Picklist | HTTP method: GET, POST, PUT, PATCH, DELETE |
URL__c | URL | Full endpoint URL for the request |
Request__c | Long Text Area(131072) | Request payload |
RequestParameters__c | Long Text Area(131072) | Name-value pairs of request parameters |
Response__c | Long Text Area(131072) | Response payload |
StatusCode__c | Text(50) | HTTP status code |
ErrorMessages__c | Long Text Area(32768) | Errors encountered during processing |
IdempotencyKey__c | Text(255) | Idempotency key for duplicate detection: if the same request arrives twice, the first result is returned again rather than re-run (External ID) |
IsIdempotencyHit__c | Checkbox | True if the response was returned from a previous call with the same idempotency key, not re-run (Default: false) |
IsMockedResponse__c | Checkbox | True if response is mocked (Default: false) |
DeadLettered__c | Formula(Checkbox) | True once retries are exhausted or the call is manually set aside (dead-lettered) for inspection rather than retried again |
ManualDeadLetter__c | Checkbox | Admin override to permanently dead-letter a failed call (Default: false) |
Retries__c | Number(2,0) | Retry count (Default: 0) |
MaxRetries__c | Number | Maximum retry attempts for this API call |
NextRetry__c | Date Time | Time of next retry |
TraceId__c | Text(36) | W3C Trace Context trace ID for distributed tracing |
ParentSpanId__c | Text(16) | W3C Trace Context parent span ID (16 hex characters) |
CalloutDurationMs__c | Number(6,0) | HTTP callout time (milliseconds) |
HandlerDurationMs__c | Number(6,0) | Handler processing time (milliseconds) |
CommitDurationMs__c | Number(6,0) | DML commit time (milliseconds) |
TotalDurationMs__c | Number(6,0) | Total processing time (milliseconds) |
TriggeringRecordId__c | Text(80) | ID of record that triggered the call |
TriggeringRecordUrl__c | Formula(Text) | Hyperlink to triggering record |
LoggerContext__c | Long Text Area(32768) | Serialised logger context for async correlation |
Relationships:
- Parent of
ApiIssue__c(1:N)
Usage:
- Created automatically by the API framework classes, so you do not insert these records yourself
- Gives you an audit trail of every API interaction
- Drives the retry logic for failed calls
- Records performance metrics
- Auto-correlation:
LoggerContext__cis filled in automatically when queue items are created throughTST_Factory.newOutboundApiCall(). When the background job later runs, the API dispatcher restores that logger context, so every log written during the async transaction is tied back to the original correlation ID (the tracking ID for the whole action).
ApiIssue__c (Custom Object)
Description: Stores failed API service details for retry management.
Fields:
| Field | Type | Description |
|---|---|---|
ApiCall__c | Lookup(ApiCall__c) | Parent call queue record |
ServiceName__c | Text(100) | Service class API name |
Direction__c | Picklist | Inbound or Outbound |
Status__c | Picklist | Open, Resolved (automatic retry batch job or manual retry via quick action) |
ErrorMessages__c | Long Text Area(131072) | Error messages encountered |
TriggeringRecordId__c | Text(18) | ID of triggering record |
OriginatingRecordId__c | Text(18) | Originating record ID (when different from triggering record) |
IdempotencyKey__c | Text(255) | Idempotency key from the original failed API call, preserved for retry |
RequestMethod__c | Text(10) | HTTP method of the original inbound request (GET, POST, etc.) |
RequestPath__c | Text(255) | URL path of the original inbound request for failure replay |
RequestBody__c | Long Text Area(131072) | Body of the original inbound request for failure replay |
RequestParameters__c | Long Text Area(131072) | Request parameters |
RequestParametersHash__c | Text(50) | Hash of request parameters (deduplication) |
ResolvedDate__c | DateTime | Date and time when the failure was resolved |
Relationships:
- Lookup to
ApiCall__c
Usage:
- Created when API calls fail
- Used by retry jobs to re-attempt failed calls
- Prevents duplicate retry attempts via hash
ApiSetting__mdt (Custom Metadata)
Description: Registers inbound and outbound web service handlers and their configuration.
Fields:
| Field | Type | Description |
|---|---|---|
ClassName__c | Text(100) (Unique) | Fully qualified API Handler class name |
EndpointPath__c | Text(255) | Relative path (appended to Named Credential URL) |
Direction__c | Picklist | Inbound or Outbound |
IsActive__c | Checkbox | Whether this API route is active |
Priority__c | Number(3,0) | Routing priority (lower = higher priority, Default: 100) |
MaxRetryCount__c | Number(2,0) | Maximum number of retries |
RetryBackoffSeconds__c | Number(3,0) | Backoff period between retries (seconds) |
LogIssues__c | Checkbox | Whether to log API failures (Default: false) |
RetryIssues__c | Checkbox | Whether to retry failures (Default: false) |
ResolveIssues__c | Checkbox | Whether to resolve failures (Default: false) |
IdempotencyEnabled__c | Checkbox | Enable idempotency key duplicate detection (Default: false) |
MockingEnabled__c | Checkbox | Enable mock responses from ApiMock__mdt records (Default: false) |
ApiCredential__c | Metadata Relationship | Link to credential configuration |
CircuitBreakerEnabled__c | Checkbox | Turn on the circuit breaker: after repeated failures the framework stops calling a failing system for a cool-off, then resumes |
CircuitBreakerFailureThreshold__c | Number(3,0) | Consecutive failures before opening circuit (Default: 10) |
CircuitBreakerSuccessThreshold__c | Number(2,0) | Consecutive successes in half-open state to close circuit (Default: 2) |
CircuitBreakerTimeout__c | Number(5,0) | Seconds before attempting to close the circuit (Default: 60) |
BypassFeatureFlag__c | Metadata Relationship | Feature Flag that bypasses this API when enabled |
RequiredFeatureFlag__c | Metadata Relationship | Feature Flag required for this API to execute |
Relationships:
- Metadata relationship to
ApiCredential__mdt
Pre-built records (4 shipped): framework-demo API settings (ApiMock endpoints, self-callout examples) used by the bundled sample services. You add your own ApiSetting__mdt for each of your handler classes.
Usage:
Example Configuration:
- Label: Customer API - Get Data
- ClassName__c: API_GetCustomerData
- EndpointPath__c: /customers/{customerId}
- MaxRetryCount__c: 3
- RetryBackoffSeconds__c: 60
- ApiCredential__c: Customer_API_CredentialsApiCredential__mdt (Custom Metadata)
Description: Links outbound API handlers to their Salesforce Named Credential for endpoint resolution and authentication.
Fields:
| Field | Type | Description |
|---|---|---|
NamedCredential__c | Text(255) | Named Credential reference for endpoint and authentication |
Relationships:
- Parent of
ApiSetting__mdt(1:N)
Pre-built records (3 shipped): framework-demo credentials matching the bundled sample API services. You add your own ApiCredential__mdt referencing your Named Credentials.
Usage:
Example Configuration:
- Label: Customer API Credentials
- NamedCredential__c: callout:CustomerAPIRedaction of API request and response payloads is handled by the shared data masking framework (see MaskingRule__mdt and MaskingTarget__mdt in the Data Masking section of this guide, or the reference pages directly). Out of the box, two rules are applied to every text field of ApiCall__c and ApiIssue__c: MaskSecretKeys (JSON key redaction for password, token, apiKey, and similar) and MaskPaymentCard (13–19 digit sequences that pass the Luhn checksum, across all major card brands). So raw secrets and card numbers never land in the logged request or response.
ApiMock__mdt (Custom Metadata)
Description: Configures mock response scenarios for API services. Supports dynamic response interpolation, request body pattern matching, and fault simulation for both inbound and outbound APIs.
Fields:
| Field | Type | Description |
|---|---|---|
ApiSetting__c | Metadata Relationship (Required) | Link to the API Setting this mock belongs to |
IsActive__c | Checkbox | Whether mock is active |
ResponseBody__c | Long Text Area | Mock response body (JSON or XML) |
MatchRequestBodyPattern__c | Text(255) | Pattern to match against request body for scenario selection |
IsRegex__c | Checkbox | Treat match pattern as regex instead of simple contains |
StatusCode__c | Number(3,0) | HTTP status code to return |
Priority__c | Number | Match priority for this mock |
DelayMs__c | Number(6,0) | Simulated latency in milliseconds |
FailureRate__c | Number(5,2) | Percentage of requests that return a simulated failure (0-100) |
ResponseHeaders__c | Long Text Area(131072) | JSON object of response headers |
Relationships:
- Metadata relationship to
ApiSetting__mdt
Usage:
Example Mock Configuration:
- Label: Customer API Success Response
- ApiSetting__c: API_GetCustomerData (ApiSetting__mdt)
- IsActive__c: true
- ResponseBody__c: {"customerId": "C-12345", "name": "Test Customer"}
- StatusCode__c: 200MaskingRule__mdt (Custom Metadata)
Description: Reusable redaction rule describing what to find. Rules are object-agnostic and paired with MaskingTarget__mdt records that describe where to apply them.
Fields:
All MaskingRule__mdt fields
| Field | Type | Description |
|---|---|---|
Mode__c | Picklist | Regex, JsonKey (label "JSON by Key"), ExactMatch, or CreditCard (the pattern match plus a Luhn checksum) |
Pattern__c | Long Text Area | Regex pattern, JSON key regex, or literal string (depending on Mode__c) |
Replacement__c | Text(255) | Replacement text (e.g., [REDACTED]) |
CaseSensitive__c | Checkbox | Toggle case-insensitive matching (Default: false) |
FailureAction__c | Picklist | LogAndContinue, WriteFailureMarker, or BlockDml when a pattern throws |
MinInputLength__c | Number | Optional minimum input length: the rule is skipped for shorter values, so short fields cost nothing to check |
ApplicableFieldTypes__c | Text | Optional semicolon-delimited System.DisplayType.name() list (e.g., STRING;TEXTAREA;ENCRYPTEDSTRING) restricting the rule to specific field types; blank applies to every text-shaped field |
IsActive__c | Checkbox | Enable/disable rule globally |
Order__c | Number(4,0) | Rule application order |
Replaces__c | Text(40) | On a shipped replacement rule: the developer name of the older rule it replaces. While both still carry their shipped values, the newer rule does the work wherever both are wired. Managed by the package |
ReplacesFingerprint__c | Text(64) | Fingerprint of the replaced rule's original shipped values. If you customise the older rule, it keeps running. Managed by the package |
ShippedFingerprint__c | Text(64) | Fingerprint of this rule's own shipped values. If you customise the replacement rule, the takeover turns off and both rules run. Managed by the package |
Relationships:
- Parent of
MaskingTarget__mdt(1:N)
Pre-built records (18 shipped): 3 are active out of the box: MaskSecretKeys (JSON key redaction scoped to STRING;TEXTAREA), MaskPaymentCard (13–19 digit Luhn-checked card numbers with MinInputLength=13, scoped to STRING;TEXTAREA;ENCRYPTEDSTRING), and MaskCreditCard (the original issuer-prefix card rule that MaskPaymentCard replaces; still shipped for compatibility with configurations that reference it). The other 15 are inactive templates for SSN, IBAN, SWIFT/BIC, MBI, health keywords, email, US phone, JWT, AWS access key, URL basic auth, authorization header, private IPv4, postal address, free text, and international phone. To use one, set IsActive__c=true and add a MaskingTarget__mdt when your org's data profile calls for it.
MaskingTarget__mdt (Custom Metadata)
Description: Wires a MaskingRule__mdt to a specific SObject + field (or wildcards across every text field), optionally scoped to a caller class.
Fields:
| Field | Type | Description |
|---|---|---|
Rule__c | MetadataRelationship(MaskingRule__mdt) (Required) | The rule to apply |
SObjectType__c | MetadataRelationship(EntityDefinition) (Required) | Target SObject (e.g., ApiCall__c, LogEntryEvent__e) |
Field__c | MetadataRelationship(FieldDefinition) | Specific field (picklist filtered by SObjectType__c), or blank for a wildcard across every text-shaped field on the SObject |
CallerClass__c | Text(100) | Optional scope: only fire when the caller class name matches |
IsActive__c | Checkbox | Enable/disable this wiring without touching the rule |
Relationships:
- Child of
MaskingRule__mdtviaRule__c
Pre-built records (12 shipped): the secrets, payment-card, and legacy credit-card rules are each wildcarded onto ApiCall__c, ApiIssue__c, AsyncChainExecution__c, and LogEntryEvent__e. So out of the box, every persisted API, async-chain, and log row passes through MaskSecretKeys and MaskPaymentCard (on those objects the legacy credit-card targets defer to the payment-card rule).
Interaction with ApplicableFieldTypes__c: Rule-level filters take precedence over per-target wiring. If a target wires a rule to a specific Field__c whose DisplayType is excluded by the rule's ApplicableFieldTypes__c, the rule will not fire on that field. To flag the misconfiguration, the framework writes a one-time warn-level LogEntry__c with ClassMethod__c='UTIL_FrameworkMasker.filterTargetsByFieldType'.
Feature Management Framework
What it does: Lets you turn features on or off at runtime without a deployment, and target who sees them, by user, by profile, or org-wide. So you can roll a feature out gradually, or switch it off instantly if something goes wrong.
The pieces: Each feature is a FeatureFlag__mdt record, with one or more FeatureFlagStrategy__mdt records that decide who it applies to. Your code checks a flag with UTIL_FeatureFlag. ApiRuntimeSwitch__c is a related master off-switch for APIs.
See also: Fast Start - Feature Flags
Architecture Diagram
+---------------------+
| UTIL_FeatureFlag |
| .isEnabled() |
+----------+----------+
| queries
v
+---------------------+ 1:N +---------------------+
| FeatureFlag__mdt |<-----------| FeatureFlagStrategy |
| - IsActive | | __mdt |
| - Description | | - Type |
+---------------------+ | - Target |
| - Order |
+---------------------+
+---------------------+
| ApiRuntimeSwitch__c | (Hierarchy Custom Setting)
| - DisableAllApis |
+---------------------+Objects
FeatureFlag__mdt (Custom Metadata)
Description: Master record for a feature flag configuration.
Fields:
| Field | Type | Description |
|---|---|---|
IsActive__c | Checkbox | Master on/off switch (Default: false) |
Description__c | Text Area | Description of the feature |
IsEnabledByDefault__c | Checkbox | Default value for the flag if no strategies are defined |
ResultOnNoMatch__c | Picklist | Fallback result when strategies exist but none return true |
Relationships:
- Parent of
FeatureFlagStrategy__mdt(1:N)
Pre-built records (10 shipped): framework flags including UserModeQueries_Enabled and UserModeDml_Enabled (the master off-switches for the framework's safe-by-default security behaviour, both default true, so the safe behaviour is on automatically), plus DisableAllAPIs, MockAllInboundAPIs, and similar framework-scoped toggles. You add your own feature flags alongside these.
Usage:
// Check if feature is enabled for current user
Boolean isEnabled = UTIL_FeatureFlag.isEnabled('Advanced_Reporting');
if(isEnabled)
{
// Execute advanced reporting logic
}FeatureFlagStrategy__mdt (Custom Metadata)
Description: Defines evaluation rules for parent feature flags.
Fields:
| Field | Type | Description |
|---|---|---|
FeatureFlag__c | Metadata Relationship (Required) | Parent feature flag |
Type__c | Picklist (Required) | Evaluation strategy type |
Target__c | Text | Identifier, name, or path of the item to evaluate |
ExpectedValue__c | Text | Optional expected value for the strategy |
CustomHandler__c | Text | Optional custom Apex class implementing the strategy interface |
IsActive__c | Checkbox | Whether this strategy is active |
Order__c | Number(2,0) | Execution priority (lower = first) |
Relationships:
- Metadata relationship to
FeatureFlag__mdt(parent)
Pre-built records (2 shipped): strategies attached to framework flags. You add your own strategies, either onto framework flags or onto your own flags.
Usage:
Example Strategy Configuration:
1. Enable for specific user:
- Type__c: User
- Target__c: 005000000000001AAA
2. Enable for System Administrators:
- Type__c: Profile
- Target__c: 00e00000000001AAA
3. Enable globally:
- Type__c: GlobalApiRuntimeSwitch__c (Hierarchy Custom Setting)
Description: Hierarchical custom setting providing a runtime API kill switch at the org, profile, or user level.
Fields:
| Field | Type | Description |
|---|---|---|
DisableAllApis__c | Checkbox | Disables all APIs, both inbound and outbound (Default: false) |
For granular per-service control, use ApiSetting__mdt.IsActive__c. For feature-flag-driven disable/mock, use UTIL_FeatureFlag with the DisableAllAPIs or MockAllInboundAPIs feature flags.
Usage:
// Check if all APIs are disabled via feature flag (preferred)
if(UTIL_FeatureFlag.isEnabled('DisableAllAPIs'))
{
return; // Skip API logic
}
// Or use the hierarchy custom setting directly for per-user/profile control
ApiRuntimeSwitch__c settings = ApiRuntimeSwitch__c.getInstance();
if(settings.DisableAllApis__c)
{
return; // Skip API logic
}Asynchronous Operations Framework
What it does: Runs work in the background, batch jobs, scheduled jobs, and long-running processing, and picks the right execution approach for you based on the data volume, so you do not have to choose between Salesforce's separate async tools by hand. You configure jobs with records instead of code.
The pieces: You launch background work with UTIL_AsynchronousJobLauncher, tuned by AsynchronousJobSetting__mdt. Recurring jobs are defined as ScheduledJob__c records, and ScheduleSetting__c keeps their runtime state (such as the last successful run time).
See also: Async Processing - Guide, Fast Start - Async Processing
Architecture Diagram
+---------------------+
| UTIL_Asynchronous |
| JobLauncher |
| .launch() |
+----------+----------+
| reads configuration
v
+---------------------+
| AsynchronousJob |
| Setting__mdt |
| - BatchSize |
+---------------------+
+---------------------+
| ScheduledJob__c |
| - ClassName |
| - CronExpression |
| - SchedulerName |
| - IsActive |
| - ScheduledJobId |
+---------------------+
+---------------------+
| ScheduleSetting__c | (List Custom Setting)
| - LastSuccessful |
| RunTime |
+---------------------+
+---------------------+ +------------------------+
| UTIL_AsyncChain |------>| AsyncChainExecution__c |
| .newChain().then() | | - Status |
| .execute() | | - CompletedSteps |
+---------------------+ | - TotalSteps |
| - StepLog (JSON) |
| - CorrelationId |
+------------------------+Objects
AsynchronousJobSetting__mdt (Custom Metadata)
Description: Declarative configuration for asynchronous job classes. The DeveloperName of each record should match the Apex class name it configures.
Fields:
| Field | Type | Description |
|---|---|---|
BatchSize__c | Number(4,0) | Number of records to process per execution (1-2000) |
Usage:
Example Configuration:
- DeveloperName: PROC_LoginFrequencyAggregator
- Label: PROC_LoginFrequencyAggregator
- BatchSize__c: 200ScheduledJob__c (Custom Object)
Description: Manages scheduled job configuration and execution history.
Fields:
| Field | Type | Description |
|---|---|---|
IsActive__c | Checkbox | Whether scheduled job is active |
SchedulerName__c | Text | User-friendly name for this scheduled job |
ClassName__c | Text(255) (Required) | Schedulable class name |
CronExpression__c | Text(255) (Required) | Cron expression for scheduling |
Description__c | Text Area | Description of scheduled job |
Parameters__c | Long Text Area(131072) | JSON-serialised DTO_NameValues containing name/value pairs passed to the scheduled class |
ScheduledJobId__c | Text(18) | Salesforce CronTrigger ID |
Usage:
// Create scheduled job
ScheduledJob__c job = new ScheduledJob__c(
SchedulerName__c = 'Nightly Data Cleanup',
ClassName__c = 'SCHED_NightlyCleanup',
CronExpression__c = '0 0 2 * * ?', // 2 AM daily
IsActive__c = true
);
insert job;ScheduleSetting__c (List Custom Setting)
Description: Stores runtime state for scheduled jobs, such as the last successful execution time.
Fields:
| Field | Type | Description |
|---|---|---|
LastSuccessfulRunTime__c | Datetime | Last successful run time of the schedule |
Usage:
- Stores runtime state for scheduled jobs (e.g., last successful execution time)
- Used by scheduled jobs for incremental processing based on changes since last run
AsyncChainExecution__c (Custom Object)
Description: Tracks async chain executions including state, step progress, shared context, and duration. Each record represents one chain execution lifecycle managed by UTIL_AsyncChain.
Fields:
| Field | Type | Description |
|---|---|---|
ChainName__c | Text(255), Required | Chain name from newChain() |
Status__c | Picklist | Running, Completed, Failed, Aborted, or Stalled (the picklist also defines Delayed, which the framework does not currently write) |
FailureCategory__c | Picklist | Why the chain left the happy path: Step Exception, Retries Exhausted, Kill Switch, Stalled, or Operator Abort (empty on healthy chains) |
TotalSteps__c | Number(5,0) | Total steps defined in the chain |
CompletedSteps__c | Number(5,0) | Steps that succeeded (failed steps are not counted) |
CurrentStepName__c | Text(255) | Name of the currently executing or last executed step |
StartedAt__c | DateTime | When chain execution began |
CompletedAt__c | DateTime | When chain execution finished |
DurationMs__c | Number(10,0) | Wall-clock duration in milliseconds from start to terminal state |
ErrorMessage__c | Long Text Area(32768) | Error details if failed; non-fatal failure summaries if completed with issues |
StepLog__c | Long Text Area(131072) | JSON array of step definitions enriched with runtime outcomes |
ContextData__c | Long Text Area(131072) | Serialised shared chain context (JSON) |
CorrelationId__c | Text(36), External ID, Unique | UUID for distributed log tracing across async boundaries |
Field history tracking is enabled on Status__c, CompletedSteps__c, CurrentStepName__c, and CompletedAt__c.
Usage:
// Query chain status
kern__AsyncChainExecution__c execution = [
SELECT kern__ChainName__c, kern__Status__c,
kern__CompletedSteps__c, kern__TotalSteps__c
FROM kern__AsyncChainExecution__c
WHERE kern__CorrelationId__c = :correlationId
];
// Use the fluent API (preferred)
Map<String, Object> status = kern.UTIL_AsyncChain.getStatus(executionId);Data Management & Configuration
What it does: A few supporting objects that don't belong to one framework. They help the framework find your Apex classes (ClassTypeResolver__mdt), group field sets for use in UI components (FieldSetGroup__mdt), and track login activity for reporting (LoginFrequency__c).
Objects
ClassTypeResolver__mdt (Custom Metadata)
Used with UTIL_TypeResolver.
Description: Tells the framework how to find the Apex classes in your namespace at runtime. You need this when classes the framework calls (trigger handlers, web services, DTOs, and the like) are not declared global, because the framework can't see them otherwise. You can register more than one resolver; they run in turn (ordered by Order__c, lowest first), and the first one that finds the class wins.
Fields:
| Field | Type | Manageability | Description |
|---|---|---|---|
ClassName__c | Text(100) (Unique) | Subscriber Controlled | Fully qualified class name implementing UTIL_TypeResolver.INT_ClassTypeResolver |
Order__c | Number(2,0) | Subscriber Controlled | Execution priority: resolvers run in ascending order (lowest first) |
How it works: The framework loads every ClassTypeResolver__mdt record in Order__c order, creates each resolver class, and links them in a chain (via setNext()). The built-in PackageClassResolver runs first (it looks in your namespace before the package's), then hands off to your custom resolvers in turn. The first one to return a class wins.
Example. You don't have to write this by hand. The Kern app's Health Check generates this class and a matching test for you (Class Type Resolver row, then Setup); see Health Check in the Utilities Guide. The class is declared global so the package can construct it across namespaces with Type.newInstance(); the resolveType override stays public.
// 1. Create a resolver class (this is what the Health Check generator produces):
@SuppressWarnings('PMD.AvoidGlobalModifier')
global with sharing class ACME_ClassTypeResolver extends kern.UTIL_TypeResolver.BaseClassResolver
{
public override Type resolveType(String className)
{
return getTypeForClassName(className) ?? (Type)nextResolver?.resolveType(className);
}
private static Type getTypeForClassName(String className)
{
Type classType;
if(String.isNotBlank(className))
{
String namespace = kern.UTIL_System.getNamespacePrefix(kern.UTIL_System.getClassNamespace(className), '.');
classType = Type.forName(namespace, className);
classType = classType == null && String.isNotBlank(namespace) ? Type.forName('', className) : classType;
}
return classType;
}
}
// 2. Register via ClassTypeResolver__mdt record:
// DeveloperName: AcmeResolver
// ClassName__c: ACME_ClassTypeResolver
// Order__c: 1FieldSetGroup__mdt (Custom Metadata)
Description: Groups field sets together for dynamic field set management in UIs.
Fields:
| Field | Type | Description |
|---|---|---|
FieldSetApiNames__c | Text Area (Required) | Comma-separated list of field set API names |
DefaultActiveSections__c | Text Area | Comma-separated list of section names expanded by default |
Usage:
Example Configuration:
- Label: Account Detail Sections
- FieldSetApiNames__c: Account_Basic_Info,Account_Address_Info,Account_Financial_Info
- DefaultActiveSections__c: Account_Basic_InfoLoginFrequency__c (Custom Object)
Description: Tracks user login frequency for analytics.
Fields:
| Field | Type | Description |
|---|---|---|
User__c | Lookup(User) (Required) | The user whose login activity is tracked |
LoginYear__c | Number(4,0) | Calendar year |
LoginMonth__c | Number(2,0) | Calendar month (1-12) |
TotalLoginCount__c | Number(10,0) | Total number of login events for the month |
UniqueLoginCount__c | Number(10,0) | Number of distinct days the user logged in |
CompositeKey__c | Text (Unique) | Composite key combining User ID, year, and month |
ReportingDate__c | Formula(Date) | First day of login month/year for report filtering |
Relationships:
- Lookup to
Userobject
Usage:
- Populated by
SCHED_ProcessLoginHistoryscheduled job - Enables monthly login frequency reporting and user engagement analytics
How do the frameworks connect?
Framework Integration Points
+------------------------------------------------------------------+
| KernDX Framework Ecosystem |
+------------------------------------------------------------------+
LOGGING FRAMEWORK
+- LogEntryEvent__e -> LogEntry__c
+- Used by: ALL frameworks for error/info logging
+- Configuration: LogSetting__c
+- Redaction: MaskingRule__mdt + MaskingTarget__mdt (data masking framework)
TRIGGER ACTION FRAMEWORK
+- TriggerSetting__mdt <- TriggerAction__mdt
+- Runtime Control: ApiRuntimeSwitch__c, UTIL_FeatureFlag
+- Used by: Web Service orchestration
+- Logging: LOG_Builder -> LogEntry__c
VALIDATION FRAMEWORK
+- TriggerSetting__mdt <- ValidationRuleGroup__mdt <- ValidationRule__mdt
+- Executed by: TRG_ExecuteValidationRules (trigger action)
+- Logging: LOG_Builder -> LogEntry__c (shadow mode + warnings)
WEB SERVICE FRAMEWORK
+- ApiCredential__mdt <- ApiSetting__mdt
+- ApiCall__c <- ApiIssue__c
+- Configuration: ApiMock__mdt
+- Redaction: MaskingRule__mdt + MaskingTarget__mdt (data masking framework)
+- Logging: LOG_Builder -> LogEntry__c
+- Feature Control: ApiRuntimeSwitch__c, UTIL_FeatureFlag
FEATURE MANAGEMENT
+- FeatureFlag__mdt <- FeatureFlagStrategy__mdt
+- ApiRuntimeSwitch__c (API kill switch)
+- Used by: All frameworks for feature gating
ASYNC OPERATIONS
+- AsynchronousJobSetting__mdt
+- ScheduledJob__c
+- ScheduleSetting__c
+- Used by: Web Service retries, data processing
+- Logging: LOG_Builder -> LogEntry__c
DATA MANAGEMENT
+- ClassTypeResolver__mdt (All frameworks)
+- FieldSetGroup__mdt (UI components)
+- LoginFrequency__c (Analytics)Key Integration Patterns
A few patterns repeat across the whole framework. Recognising them helps you see how one object's data feeds another:
- Logging is shared by everything. Every framework writes its logs the same way:
LOG_Builder->LogEntryEvent__e->LogEntry__c - Feature gating controls what runs.
FeatureFlag__mdtandApiRuntimeSwitch__cdecide whether APIs run at runtime - Configuration is metadata, not code. Trigger Actions and Web Services are set up with Custom Metadata Types, so you change behaviour without deploying code
- Work moves in the background. Platform Events let processing run asynchronously and scale
- Two objects hold the audit trail.
ApiCall__crecords API activity andLogEntry__crecords log activity, so you have a kept record of what happened
How do these objects work together?
The three examples below show how these objects work together end to end. Each starts with the Apex you write, then the configuration records that wire it up, then what happens automatically at runtime.
Pattern 1: Metadata-Driven Trigger with Logging
You want a trigger to validate records and log anything it rejects. Write the handler, register it as a trigger action, and the logs flow on their own:
// 1. Create Trigger Action class
public class TRG_AccountValidator extends TRG_Base
implements IF_Trigger.BeforeInsert
{
public void beforeInsert(List<Account> newAccounts)
{
for(Account account : newAccounts)
{
if(account.AnnualRevenue < 0)
{
account.AnnualRevenue.addError('Revenue cannot be negative');
// Log validation failure
LOG_Builder.build()
.warn('Negative revenue blocked for: ' + account.Name)
.at('TRG_AccountValidator.beforeInsert')
.forRecord(account.Id)
.emit();
}
}
}
}
// 2. Configure in TriggerAction__mdt:
// - ApexClassName__c: TRG_AccountValidator
// - Event__c: Before Insert
// - TriggerSetting__c: Account
// - Order__c: 10
// 3. Logs automatically flow:
// LOG_Builder -> LogEntryEvent__e -> LogEntry__cPattern 2: API Integration with Retry Logic
You want to call an external service and have failures retried automatically. Write the API class, point it at a credential, set the retry options on the setting record, and the framework handles success, failure, and retry:
// 1. Create API class
public with sharing class API_GetCustomerData extends API_Outbound
{
public override void configure()
{
super.configure();
requestPayload = new DTO_Request();
responsePayload = new DTO_Response();
defaultMockBody = '{"customerId": "C-12345", "name": "Acme Corp"}';
}
// Implementation...
}
// 2. Configure in ApiSetting__mdt:
// - ClassName__c: API_GetCustomerData
// - MaxRetryCount__c: 3
// - RetryBackoffSeconds__c: 60
// - LogIssues__c: true
// 3. Configure credentials in ApiCredential__mdt:
// - NamedCredential__c: callout:CustomerAPI
// 4. Automatic flow:
// API call -> ApiCall__c (success)
// OR
// API call -> ApiCall__c + ApiIssue__c (failure)
// -> Retry job -> ApiCall__c (resolved)Pattern 3: Feature Flag with Multiple Strategies
You want to release a feature to some users before everyone. Check the flag in code, then control who sees it with one or more strategy records, no code change needed to widen the rollout:
// 1. Check feature in code
Boolean isAdvancedReportingEnabled = UTIL_FeatureFlag.isEnabled('Advanced_Reporting');
if(isAdvancedReportingEnabled)
{
// Execute advanced reporting logic
}
// 2. Configure in FeatureFlag__mdt:
// - Label: Advanced Reporting
// - IsActive__c: true
// 3. Configure strategies in FeatureFlagStrategy__mdt:
// Strategy 1: Enable for System Admins
// - Type__c: Profile
// - Target__c: 00e000000000001AAA
//
// Strategy 2: Enable for specific power users
// - Type__c: User
// - Target__c: 005000000000001AAAHow do I test this?
You don't test these objects and metadata types on their own. You test the behaviour they configure, by running the framework end to end. With kern.TST_Builder, build (and insert) a record, then check that the configured effect happened. When a test needs a feature flag switched on, seed it with the global kern.TST_Factory.newFeatureFlag(...) helper. Tests written this way stay isolated and can run in parallel.
Testing trigger action metadata:
@IsTest
private static void shouldExecuteTriggerAction()
{
// Build an in-memory record and invoke the trigger-action handler directly.
Foobar__c record = (Foobar__c)kern.TST_Builder.of(Foobar__c.SObjectType)
.withoutInsertion()
.build();
new TRG_SetFoobarDefaults().beforeInsert(new List<Foobar__c>{ record });
Assert.isNotNull(record.Name, 'Default should be set');
}Testing scheduled job configuration:
To test a job configured through ScheduledJob__c, check that the scheduler class runs correctly with the attributes you expect. Use TST_Factory to build the attribute sets in memory, then call the scheduler's execute() method directly.
Testing feature flags:
@IsTest
private static void shouldRespectFeatureFlag()
{
Boolean isEnabled = UTIL_FeatureFlag.isEnabled('MyFeatureFlag');
Assert.isFalse(isEnabled, 'Feature should be disabled when no metadata record exists');
}One thing to know: custom metadata records (TriggerSetting__mdt, TriggerAction__mdt, FeatureFlag__mdt, and the rest) cannot be inserted with DML inside a test. So instead of asserting on the metadata directly, run the configured behaviour through kern.TST_Builder end to end (build or insert a record, then assert the effect). For feature-flag state, seed an active flag with the global kern.TST_Factory.newFeatureFlag(...) helper. To test the off path, just leave the flag unseeded: an unseeded flag is treated as off.
What mistakes should I avoid?
These are common mistakes (anti-patterns) when working with the framework's objects and metadata, with what to do instead:
| Anti-Pattern | Why It's Wrong | Instead |
|---|---|---|
Hardcoding DeveloperName strings to reference metadata records | Breaks silently when records are renamed; no compile-time safety | Use class name constants (e.g., ClassName.class.getName()) or @TestVisible constants |
| Using Custom Settings where Custom Metadata Types would suffice | Custom Settings require DML in tests and cannot be deployed across orgs | Use Custom Metadata Types (__mdt) for static configuration; reserve Custom Settings for hierarchy or user-specific values |
Leaving Description__c fields empty on metadata records | Makes configuration opaque to administrators and future developers | Always populate Description__c with a brief explanation of the record's purpose |
| Creating org-specific test data that assumes specific metadata exists | Tests break in different orgs or after metadata changes | Exercise the framework through kern.TST_Builder and assert the effect; seed flag state with the global kern.TST_Factory.newFeatureFlag(...); keep tests self-contained and never rely on pre-existing org data |
| Querying metadata with inline SOQL | Violates framework patterns and bypasses selector caching | Use SEL_* selectors or QRY_Builder for all metadata queries |
What are the recommended practices?
- Use custom metadata over custom settings for configuration that should be deployable. Custom metadata records deploy with change sets and packages; custom settings require post-deployment data loading.
- Document every metadata record using the
Description__cfield. Future administrators need to understand why each trigger action, feature flag, or API setting exists. - Use meaningful DeveloperNames for metadata records. Names like
TRG_SetFoobarDefaults_BeforeInsertare self-documenting; names likeAction1are not. - Keep trigger action execution order explicit by assigning
Order__cvalues with gaps (10, 20, 30) to allow future insertions without renumbering. - Prefer
TriggerSetting__mdt.BypassExecution__cover code-level bypass for temporary deactivation. Metadata bypasses are auditable and reversible without deployment. - Use
ApiCredential__mdtfor external service configuration rather than hardcoding endpoints or credentials. Named/external credentials are preferred when available;ApiCredential__mdtprovides a fallback. - Review
LogEntry__cretention regularly. ConfigureSCHED_PurgeRecordsviaScheduledJob__cto prevent storage growth from log accumulation. - Test with
@IsTest(SeeAllData=false)to ensure tests do not depend on org-specific metadata records. Drive metadata-configured behaviour throughkern.TST_Builderand assert the outcome; seed feature-flag state with the globalkern.TST_Factory.newFeatureFlag(...)helper.
Related Documentation
- Triggers - Guide -
TriggerSetting__mdtandTriggerAction__mdtconfiguration patterns - Validation - Guide -
ValidationRuleGroup__mdtandValidationRule__mdtconfiguration patterns - Web Services - Guide -
ApiSetting__mdt,ApiCredential__mdt,ApiMock__mdt, andApiCall__cusage - Async Processing - Guide -
ScheduledJob__c,AsynchronousJobSetting__mdt, andAsyncChainExecution__cconfiguration - Logging - Guide -
LogEntry__c,LogEntryEvent__e, and log filter metadata - Security - Guide -
ApiRuntimeSwitch__cfor runtime API kill switches - Utilities - Guide -
UTIL_FeatureFlagforFeatureFlag__mdtconsumption
Appendix A: Mermaid ERD
End of Document