apex-callable-interface
Use when building Apex classes meant to be invoked dynamically — from Flow, external packages, managed-package extensions, or loose-coupling code that cannot directly reference the concrete class. Trigger keywords: Callable, call method, dynamic Apex, action registry, plugin pattern, managed package extension point. NOT for: Invocable methods exposed to Flow (see apex-invocable-methods) or REST endpoints (see apex-rest-services).
Best use case
apex-callable-interface is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use when building Apex classes meant to be invoked dynamically — from Flow, external packages, managed-package extensions, or loose-coupling code that cannot directly reference the concrete class. Trigger keywords: Callable, call method, dynamic Apex, action registry, plugin pattern, managed package extension point. NOT for: Invocable methods exposed to Flow (see apex-invocable-methods) or REST endpoints (see apex-rest-services).
Teams using apex-callable-interface 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
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/apex-callable-interface/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How apex-callable-interface Compares
| Feature / Agent | apex-callable-interface | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Use when building Apex classes meant to be invoked dynamically — from Flow, external packages, managed-package extensions, or loose-coupling code that cannot directly reference the concrete class. Trigger keywords: Callable, call method, dynamic Apex, action registry, plugin pattern, managed package extension point. NOT for: Invocable methods exposed to Flow (see apex-invocable-methods) or REST endpoints (see apex-rest-services).
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 Callable Interface
Activate this skill when Apex must be invoked dynamically without the caller having a compile-time reference. The `System.Callable` interface provides a single-method contract (`call(String action, Map<String, Object> args)`) that lets Flow, managed package consumers, and service registries address any implementing class by type name and action string.
---
## Before Starting
Gather this context before working on anything in this domain:
- **Who is the caller?** A managed-package extension, Flow, an in-repo service registry, or ad-hoc reflection?
- **Is the call site trusted?** A trusted caller can skip input validation; an untrusted one cannot.
- **Does the action need to be async?** `Callable.call` runs synchronously in the caller's transaction.
- **What's the contract versioning story?** Changing accepted keys is a breaking change for every consumer.
---
## Core Concepts
### The `System.Callable` Interface
One method: `Object call(String action, Map<String, Object> args)`.
- `action` is a free-form string — you define the action vocabulary per class.
- `args` is a `Map<String, Object>` — you document the expected keys.
- Return is `Object` — callers cast. Document the return shape per action.
- The interface is in the `System` namespace and is available in every org.
### Dynamic Instantiation Via `Type.forName` + Cast
A caller typically looks like:
```apex
Type t = Type.forName(namespace, className);
if (t == null) throw new HandlerNotFoundException(className);
Object instance = t.newInstance();
if (!(instance instanceof Callable)) {
throw new NotCallableException(className);
}
Object result = ((Callable) instance).call(action, args);
```
The indirection is the whole point — the caller has zero compile-time coupling to the implementation.
### Extension-Point Pattern (Managed Package)
Managed packages can ship a `Callable` with public action strings. Subscribers implement the same `Callable` in their org with custom logic, and the package looks up the subscriber's class via a custom metadata record or custom setting.
### Flow Compatibility
Apex `Callable` is NOT directly invokable from Flow. Flow needs `@InvocableMethod`. `Callable` is for code-to-code dispatch — often behind an `@InvocableMethod` facade when Flow is a consumer.
---
## Common Patterns
### Plugin Action Registry
**When to use:** You have a fixed set of "hook" points where admins or subscribers should be able to inject logic.
**How it works:**
```apex
public with sharing class PluginRegistry {
public static Object invoke(String pluginApiName, String action, Map<String, Object> args) {
Plugin__mdt config = Plugin__mdt.getInstance(pluginApiName);
if (config == null) return null;
Type t = Type.forName(config.Namespace__c, config.ClassName__c);
if (t == null || !Callable.class.isAssignableFrom(t)) {
throw new PluginException('Plugin not found or not Callable: ' + pluginApiName);
}
return ((Callable) t.newInstance()).call(action, args);
}
}
```
**Why not the alternative:** Hardcoded `if (pluginName == 'X') new X()` requires redeployment for every new plugin.
### Documented Action Contract
**When to use:** Every `Callable` class where you expect multiple actions.
**How it works:**
```apex
global with sharing class OrderFulfillmentActions implements Callable {
// Actions:
// 'reserveInventory': args { 'orderId': Id } -> Id (reservation id)
// 'cancelReservation': args { 'reservationId': Id } -> Boolean
// 'quote': args { 'productIds': Set<Id>, 'qty': Map<Id, Integer> } -> Decimal
global Object call(String action, Map<String, Object> args) {
switch on action {
when 'reserveInventory' { return reserveInventory((Id) args.get('orderId')); }
when 'cancelReservation' { return cancelReservation((Id) args.get('reservationId')); }
when 'quote' { return quote(args); }
when else { throw new CalloutException('Unknown action: ' + action); }
}
}
// ...
}
```
**Why not the alternative:** Undocumented `Map<String, Object>` contracts lead to runtime casts that fail silently.
---
## Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| Flow needs to invoke Apex | `@InvocableMethod` | `Callable` is not wired to Flow directly |
| Managed package extension point | `Callable` via metadata | Loose coupling survives package updates |
| In-repo dispatch by config | `Callable` via metadata | Removes hardcoded `if/else` branches |
| REST client calling Apex | `@RestResource` | `Callable` is not a REST endpoint |
| Scheduled or async job | `Queueable` / `Schedulable` | `Callable` runs in caller's transaction |
| Type-safe helper class | Regular Apex class | `Callable` is for dynamic dispatch only |
---
## Recommended Workflow
1. Confirm the caller actually needs dynamic dispatch (most don't — direct class reference is simpler).
2. Define the action vocabulary as comments at the top of the class — name, expected keys, return type.
3. Implement `call` with a `switch on action` and throw on unknown actions.
4. Add `TypeException`-safe casts on every `args.get(...)` call.
5. Write tests: a happy-path test per action plus an "unknown action" test that asserts the expected exception.
6. If the class is a managed-package extension point, ship a reference implementation and document the contract in the package's help.
---
## Review Checklist
- [ ] All expected action strings are documented at the top of the class.
- [ ] `switch on action` with a default `when else` throw clause.
- [ ] Every `args.get('key')` is type-cast to the expected type with a clear failure mode.
- [ ] Unknown action test asserts the specific exception type.
- [ ] Class is `global` if it's a managed-package extension point; `public` otherwise.
- [ ] `Callable` consumers use `Type.forName` + `instanceof Callable` check, not raw cast.
---
## Salesforce-Specific Gotchas
1. **`Callable` is synchronous** — calls run in the caller's transaction, share governor limits, and cannot be enqueued by the interface alone.
2. **`Type.forName(null, 'X')` searches the caller's namespace** — pass the correct namespace explicitly in a managed-package context.
3. **`Callable.call` return is `Object`** — callers must cast; a typo in the action string yields a runtime exception, not a compile error.
4. **Args map is not validated** — missing keys return `null` from `.get()`, casts to primitives may `NullPointerException` or `TypeException`.
5. **No Aura/LWC direct access** — `Callable` is Apex-to-Apex; UI layers should go through `@AuraEnabled` facades.
6. **Removing an action is a breaking change** — downstream consumers have no compile-time contract, so silent regressions are common.
---
## Output Artifacts
| Artifact | Description |
|---|---|
| `scripts/check_apex_callable_interface.py` | Scans for unguarded `args.get` casts, missing `when else`, and `Callable` used where `@InvocableMethod` is correct |
| `templates/apex-callable-interface-template.md` | Work template for defining a `Callable` class with a documented action contract |
---
## Related Skills
- `apex-invocable-methods` — when Flow needs to invoke Apex (not `Callable`)
- `apex-custom-metadata-types` — storing plugin registrations
- `apex-dependency-injection` — higher-level patterns that may use `Callable` under the hoodRelated Skills
apex-managed-sharing-patterns
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.
lwc-imperative-apex
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).
dataweave-for-apex
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
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-apex-defined-types
Design and use Apex-Defined Types as Flow variables for structured non-sObject data (HTTP callout payloads, External Service responses, complex configuration). Trigger keywords: apex-defined type, flow variable, @AuraEnabled class, flow http callout response. Does NOT cover building HTTP Callout Actions themselves, External Services schema, or raw Apex invocable methods.
scheduled-apex-failure-detection-and-monitoring
Use when nightly batch / scheduled Apex jobs are failing without anyone noticing — covers why uncaught exceptions in `execute()` go to the debug log instead of email, how to query `AsyncApexJob` for `Status`, `NumberOfErrors`, and `ExtendedStatus`, when to implement `Database.RaisesPlatformEvents` so the platform publishes `BatchApexErrorEvent` on uncaught failures, how to subscribe to that event with an Apex trigger and notify operators, and how to layer a custom watcher schedule on top so silent-failure modes (job that never started, scheduled class deleted, queue stuck on `Queued`) still surface. Triggers: 'nightly batch failed at 2am with no notification', 'how do we know if a scheduled apex job is failing', 'BatchApexErrorEvent vs custom retry logic', 'Setup Apex Jobs only shows last 7 days, where else can I look', 'job is stuck in queued status nobody noticed for a week'. NOT for general Apex exception handling patterns (use apex/apex-exception-handling-and-logging), NOT for Batch Apex authoring or chunking strategy (use apex/batch-apex-design), NOT for Setup → Apex Jobs UI walkthrough as an admin task (use admin/batch-job-scheduling-and-monitoring), NOT for retry logic itself (use apex/scheduled-apex-retry-patterns once authored).
platform-events-apex
Use when publishing or subscribing to Salesforce Platform Events from Apex, comparing Platform Events with Change Data Capture, or designing event-triggered error handling and monitoring. Triggers: 'EventBus.publish', 'platform event trigger', 'CDC vs Platform Events', 'replay ID', 'high-volume event'. NOT for Flow-only publish/subscribe automation.
health-cloud-apex-extensions
Use this skill when extending Health Cloud via Apex: implementing HealthCloudGA managed-package interfaces, automating care plan lifecycle hooks, processing referrals using Industries Common Components invocable actions, or enforcing HIPAA-compliant logging governance for clinical Apex code. Trigger keywords: CarePlanProcessorCallback, HealthCloudGA namespace, ReferralRequest, ReferralResponse, care plan invocable actions, clinical Apex extension, Health Cloud Apex API. NOT for standard Apex triggers or generic Apex development unrelated to Health Cloud managed-package extension points.
fsl-apex-extensions
Use when writing Apex that calls Field Service Lightning scheduling APIs — AppointmentBookingService, ScheduleService, GradeSlotsService, or OAAS — to book, schedule, grade, or optimize service appointments programmatically. Trigger keywords: FSL Apex namespace, GetSlots, schedule service appointment via code, appointment booking API, FSL optimization API. NOT for standard Apex patterns unrelated to FSL, admin-level scheduling policy configuration, or declarative FSL scheduling.
fsc-apex-extensions
Use this skill when extending Financial Services Cloud (FSC) behavior through Apex: customizing financial rollup recalculation, disabling built-in FSC triggers to write custom trigger logic, implementing Compliant Data Sharing (CDS) participant/role integrations, or building custom FSC action handlers. NOT for standard Apex unrelated to the FSC managed package, standard Salesforce sharing rules, or configuring FSC rollups through the Admin UI — use the admin/financial-account-setup skill for declarative rollup setup.
entitlement-apex-hooks
Use this skill when writing Apex triggers or classes that interact with CaseMilestone records — completing milestones, detecting violations, or reacting to SLA state changes. Trigger keywords: CaseMilestone trigger, auto-complete milestone Apex, milestone violation polling, CompletionDate write pattern. NOT for entitlement process admin setup, milestone configuration in Setup UI, or Flow-based milestone actions.
dynamic-apex
Use when building or reviewing code that constructs SOQL/SOSL at runtime, inspects schema metadata via Schema.describe methods, accesses fields dynamically on sObjects, or performs runtime type inspection. Triggers: 'Database.query', 'Schema.getGlobalDescribe', 'Schema.describeSObjects', 'dynamic field access', 'SObjectType', 'DescribeFieldResult'. NOT for static SOQL queries or query performance tuning — use soql-fundamentals or soql-query-optimization.