Claude Code Plugins

Community-maintained marketplace

Feedback

sf-integration

@Jaganpro/sf-skills
12
0

>

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name sf-integration
description Creates comprehensive Salesforce integrations with 120-point scoring. Use when setting up Named Credentials, External Services, REST/SOAP callouts, Platform Events, Change Data Capture, or connecting Salesforce to external systems.
license MIT
metadata [object Object]

sf-integration: Salesforce Integration Patterns Expert

Expert integration architect specializing in secure callout patterns, event-driven architecture, and external service registration for Salesforce.

Core Responsibilities

  1. Named Credential Generation: Create Named Credentials with OAuth 2.0, JWT Bearer, Certificate, or Custom authentication
  2. External Credential Generation: Create modern External Credentials (API 61+) with Named Principals
  3. External Service Registration: Generate ExternalServiceRegistration metadata from OpenAPI/Swagger specs
  4. REST Callout Patterns: Synchronous and asynchronous HTTP callout implementations
  5. SOAP Callout Patterns: WSDL2Apex guidance and WebServiceCallout patterns
  6. Platform Events: Event definitions, publishers, and subscriber triggers
  7. Change Data Capture: CDC enablement and subscriber patterns
  8. Validation & Scoring: Score integrations against 6 categories (0-120 points)

Key Insights

Insight Details Action
Named Credential Architecture Legacy (pre-API 61) vs External Credentials (API 61+) Check org API version first
Callouts in Triggers Synchronous callouts NOT allowed in triggers Always use async (Queueable, @future)
Governor Limits 100 callouts per transaction, 120s timeout max Batch callouts, use async patterns
External Services Auto-generates Apex from OpenAPI specs Requires Named Credential for auth

⚠️ CRITICAL: Named Credential Architecture (API 61+)

Legacy Named Credentials vs External Credentials

Feature Legacy Named Credential External Credential (API 61+)
API Version Pre-API 61 API 61+ (Winter '24+)
Principal Concept Single principal per credential Named Principal + Per-User Principal
OAuth Support Basic OAuth 2.0 Full OAuth 2.0 + PKCE, JWT
Permissions Profile-based Permission Set + Named Principal
Recommendation Legacy orgs only Use for all new development

Decision Matrix

┌─────────────────────────────────────────────────────────────────────────────┐
│  WHEN TO USE WHICH CREDENTIAL TYPE                                          │
├─────────────────────────────────────────────────────────────────────────────┤
│  Use LEGACY Named Credential if:                                            │
│  • Org API version < 61                                                     │
│  • Migrating existing integrations (maintain compatibility)                 │
│  • Simple API key / Basic Auth (quick setup)                               │
│                                                                             │
│  Use EXTERNAL Credential (API 61+) if:                                      │
│  • New development (recommended)                                            │
│  • OAuth 2.0 with PKCE required                                            │
│  • Per-user authentication needed                                           │
│  • Fine-grained permission control required                                 │
│  • JWT Bearer flow for server-to-server                                    │
└─────────────────────────────────────────────────────────────────────────────┘

Workflow (5-Phase Pattern)

Phase 1: Requirements Gathering

Use AskUserQuestion to gather:

  1. Integration Type:

    • Outbound REST (Salesforce → External API)
    • Outbound SOAP (Salesforce → External SOAP Service)
    • Inbound REST (External → Salesforce REST API)
    • Event-driven (Platform Events, CDC)
  2. Authentication Method:

    • OAuth 2.0 Client Credentials
    • OAuth 2.0 JWT Bearer
    • OAuth 2.0 Authorization Code
    • Certificate-based (Mutual TLS)
    • API Key / Basic Auth
  3. External System Details:

    • Base endpoint URL
    • API version
    • Rate limits
    • Required headers
  4. Sync vs Async Requirements:

    • Real-time response needed? → Sync
    • Fire-and-forget? → Async (@future, Queueable)
    • Triggered from DML? → MUST be async

Phase 2: Template Selection

Integration Need Template Location
OAuth 2.0 Client Credentials oauth-client-credentials.namedCredential-meta.xml templates/named-credentials/
OAuth 2.0 JWT Bearer oauth-jwt-bearer.namedCredential-meta.xml templates/named-credentials/
Certificate Auth certificate-auth.namedCredential-meta.xml templates/named-credentials/
API Key / Basic Auth custom-auth.namedCredential-meta.xml templates/named-credentials/
External Credential (OAuth) oauth-external-credential.externalCredential-meta.xml templates/external-credentials/
External Service (OpenAPI) openapi-registration.externalServiceRegistration-meta.xml templates/external-services/
REST Callout (Sync) rest-sync-callout.cls templates/callouts/
REST Callout (Async) rest-queueable-callout.cls templates/callouts/
Retry Handler callout-retry-handler.cls templates/callouts/
SOAP Callout soap-callout-service.cls templates/soap/
Platform Event platform-event-definition.object-meta.xml templates/platform-events/
Event Publisher event-publisher.cls templates/platform-events/
Event Subscriber event-subscriber-trigger.trigger templates/platform-events/
CDC Subscriber cdc-subscriber-trigger.trigger templates/cdc/

Phase 3: Generation & Validation

File Locations:

force-app/main/default/
├── namedCredentials/
│   └── {{CredentialName}}.namedCredential-meta.xml
├── externalCredentials/
│   └── {{CredentialName}}.externalCredential-meta.xml
├── externalServiceRegistrations/
│   └── {{ServiceName}}.externalServiceRegistration-meta.xml
├── classes/
│   ├── {{ServiceName}}Callout.cls
│   ├── {{ServiceName}}Callout.cls-meta.xml
│   └── ...
├── objects/
│   └── {{EventName}}__e/
│       └── {{EventName}}__e.object-meta.xml
└── triggers/
    ├── {{EventName}}Subscriber.trigger
    └── {{EventName}}Subscriber.trigger-meta.xml

Validate using scoring system (see Scoring System section)

Phase 4: Deployment

Deployment Order (CRITICAL):

1. Deploy Named Credentials / External Credentials FIRST
2. Deploy External Service Registrations (depends on Named Credentials)
3. Deploy Apex classes (callout services, handlers)
4. Deploy Platform Events / CDC configuration
5. Deploy Triggers (depends on events being deployed)

Use sf-deploy skill:

Skill(skill="sf-deploy")
Request: "Deploy Named Credential {{Name}} with dry-run first"

CLI Commands:

# Deploy Named Credential
sf project deploy start --metadata NamedCredential:{{Name}} --target-org {{alias}}

# Deploy External Service
sf project deploy start --metadata ExternalServiceRegistration:{{Name}} --target-org {{alias}}

# Deploy all integration components
sf project deploy start --source-dir force-app/main/default/namedCredentials,force-app/main/default/externalServiceRegistrations --target-org {{alias}}

Phase 5: Testing & Verification

  1. Test Named Credential in Setup → Named Credentials → Test Connection
  2. Test External Service by invoking generated Apex methods
  3. Test Callout using Anonymous Apex or test class
  4. Test Events by publishing and verifying subscriber execution

Named Credentials

Auth Type Use Case Template Key Config
OAuth 2.0 Client Credentials Server-to-server, no user context oauth-client-credentials.namedCredential-meta.xml scope, tokenEndpoint
OAuth 2.0 JWT Bearer CI/CD, backend services oauth-jwt-bearer.namedCredential-meta.xml Certificate + Connected App
Certificate (Mutual TLS) High-security integrations certificate-auth.namedCredential-meta.xml Client cert required
Custom (API Key/Basic) Simple APIs custom-auth.namedCredential-meta.xml username/password

Templates in templates/named-credentials/. ⚠️ NEVER hardcode credentials - always use Named Credentials!


External Credentials (API 61+)

OAuth External Credential

Use Case: Modern OAuth 2.0 with per-user or named principal authentication

Template: templates/external-credentials/oauth-external-credential.externalCredential-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<ExternalCredential xmlns="http://soap.sforce.com/2006/04/metadata">
    <label>{{CredentialLabel}}</label>
    <authenticationProtocol>Oauth</authenticationProtocol>
    <externalCredentialParameters>
        <parameterName>clientId</parameterName>
        <parameterType>AuthProviderClientId</parameterType>
        <parameterValue>{{ClientId}}</parameterValue>
    </externalCredentialParameters>
    <externalCredentialParameters>
        <parameterName>clientSecret</parameterName>
        <parameterType>AuthProviderClientSecret</parameterType>
        <parameterValue>{{ClientSecret}}</parameterValue>
    </externalCredentialParameters>
    <principals>
        <principalName>{{PrincipalName}}</principalName>
        <principalType>NamedPrincipal</principalType>
        <sequenceNumber>1</sequenceNumber>
    </principals>
</ExternalCredential>

External Services (OpenAPI/Swagger)

Generating from OpenAPI Spec

Process:

  1. Obtain OpenAPI 2.0 (Swagger) or 3.0 spec from external API
  2. Create Named Credential for authentication
  3. Register External Service in Salesforce
  4. Salesforce auto-generates Apex classes

Template: templates/external-services/openapi-registration.externalServiceRegistration-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<ExternalServiceRegistration xmlns="http://soap.sforce.com/2006/04/metadata">
    <label>{{ServiceLabel}}</label>
    <namedCredential>{{NamedCredentialName}}</namedCredential>
    <schema>{{OpenAPISchemaContent}}</schema>
    <schemaType>OpenApi3</schemaType>
    <serviceBinding>{{ServiceBindingName}}</serviceBinding>
    <status>Complete</status>
</ExternalServiceRegistration>

CLI Alternative:

# Register External Service from URL
sf api request rest /services/data/v62.0/externalServiceRegistrations \
  --method POST \
  --body '{"label":"{{Label}}","namedCredential":"{{NC}}","schemaUrl":"{{URL}}"}'

Using Auto-Generated Apex

External Services generate Apex classes like:

  • ExternalService.{{ServiceName}}
  • ExternalService.{{ServiceName}}_{{OperationName}}

Example Usage:

// Auto-generated class usage
ExternalService.Stripe stripe = new ExternalService.Stripe();
ExternalService.Stripe_createCustomer_Request req = new ExternalService.Stripe_createCustomer_Request();
req.email = 'customer@example.com';
ExternalService.Stripe_createCustomer_Response resp = stripe.createCustomer(req);

REST Callout Patterns

Synchronous REST Callout

Use Case: Need immediate response, NOT triggered from DML

Template: templates/callouts/rest-sync-callout.cls

public with sharing class {{ServiceName}}Callout {

    private static final String NAMED_CREDENTIAL = 'callout:{{NamedCredentialName}}';

    public static HttpResponse makeRequest(String method, String endpoint, String body) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint(NAMED_CREDENTIAL + endpoint);
        req.setMethod(method);
        req.setHeader('Content-Type', 'application/json');
        req.setTimeout(120000); // 120 seconds max

        if (String.isNotBlank(body)) {
            req.setBody(body);
        }

        Http http = new Http();
        return http.send(req);
    }

    public static Map<String, Object> get(String endpoint) {
        HttpResponse res = makeRequest('GET', endpoint, null);
        return handleResponse(res);
    }

    public static Map<String, Object> post(String endpoint, Map<String, Object> payload) {
        HttpResponse res = makeRequest('POST', endpoint, JSON.serialize(payload));
        return handleResponse(res);
    }

    private static Map<String, Object> handleResponse(HttpResponse res) {
        Integer statusCode = res.getStatusCode();

        if (statusCode >= 200 && statusCode < 300) {
            return (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
        } else if (statusCode >= 400 && statusCode < 500) {
            throw new CalloutException('Client Error: ' + statusCode + ' - ' + res.getBody());
        } else if (statusCode >= 500) {
            throw new CalloutException('Server Error: ' + statusCode + ' - ' + res.getBody());
        }

        return null;
    }
}

Asynchronous REST Callout (Queueable)

Use Case: Callouts triggered from DML (triggers, Process Builder)

Template: templates/callouts/rest-queueable-callout.cls

public with sharing class {{ServiceName}}QueueableCallout implements Queueable, Database.AllowsCallouts {

    private List<Id> recordIds;
    private String operation;

    public {{ServiceName}}QueueableCallout(List<Id> recordIds, String operation) {
        this.recordIds = recordIds;
        this.operation = operation;
    }

    public void execute(QueueableContext context) {
        if (recordIds == null || recordIds.isEmpty()) {
            return;
        }

        try {
            // Query records
            List<{{ObjectName}}> records = [
                SELECT Id, Name, {{FieldsToSend}}
                FROM {{ObjectName}}
                WHERE Id IN :recordIds
                WITH USER_MODE
            ];

            // Make callout for each record (consider batching)
            for ({{ObjectName}} record : records) {
                makeCallout(record);
            }

        } catch (CalloutException e) {
            // Log callout errors
            System.debug(LoggingLevel.ERROR, 'Callout failed: ' + e.getMessage());
            // Consider: Create error log record, retry logic, notification
        } catch (Exception e) {
            System.debug(LoggingLevel.ERROR, 'Error: ' + e.getMessage());
        }
    }

    private void makeCallout({{ObjectName}} record) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:{{NamedCredentialName}}/{{Endpoint}}');
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/json');
        req.setTimeout(120000);

        Map<String, Object> payload = new Map<String, Object>{
            'id' => record.Id,
            'name' => record.Name
            // Add more fields
        };
        req.setBody(JSON.serialize(payload));

        Http http = new Http();
        HttpResponse res = http.send(req);

        if (res.getStatusCode() >= 200 && res.getStatusCode() < 300) {
            // Success - update record status if needed
        } else {
            // Handle error
            throw new CalloutException('API Error: ' + res.getStatusCode());
        }
    }
}

Retry Handler with Exponential Backoff

Use Case: Handle transient failures with intelligent retry

Template: templates/callouts/callout-retry-handler.cls

public with sharing class CalloutRetryHandler {

    private static final Integer MAX_RETRIES = 3;
    private static final Integer BASE_DELAY_MS = 1000; // 1 second

    public static HttpResponse executeWithRetry(HttpRequest request) {
        Integer retryCount = 0;
        HttpResponse response;

        while (retryCount < MAX_RETRIES) {
            try {
                Http http = new Http();
                response = http.send(request);

                // Success or client error (4xx) - don't retry
                if (response.getStatusCode() < 500) {
                    return response;
                }

                // Server error (5xx) - retry with backoff
                retryCount++;
                if (retryCount < MAX_RETRIES) {
                    // Exponential backoff: 1s, 2s, 4s
                    Integer delayMs = BASE_DELAY_MS * (Integer) Math.pow(2, retryCount - 1);
                    // Note: Apex doesn't have sleep(), so we schedule retry via Queueable
                    throw new RetryableException('Server error, retry ' + retryCount);
                }

            } catch (CalloutException e) {
                retryCount++;
                if (retryCount >= MAX_RETRIES) {
                    throw e;
                }
            }
        }

        return response;
    }

    public class RetryableException extends Exception {}
}

SOAP Callout Patterns

WSDL2Apex Process

Step 1: Generate Apex from WSDL

  1. Setup → Apex Classes → Generate from WSDL
  2. Upload WSDL file
  3. Salesforce generates Apex classes

Step 2: Configure Remote Site Setting or Named Credential

Step 3: Use generated classes in Apex

Template: templates/soap/soap-callout-service.cls

public with sharing class {{ServiceName}}SoapService {

    public static {{ResponseType}} callService({{RequestType}} request) {
        try {
            // Generated stub class
            {{WsdlGeneratedClass}}.{{PortType}} stub = new {{WsdlGeneratedClass}}.{{PortType}}();

            // Set endpoint (use Named Credential if possible)
            stub.endpoint_x = 'callout:{{NamedCredentialName}}';

            // Set timeout
            stub.timeout_x = 120000;

            // Make the call
            return stub.{{OperationName}}(request);

        } catch (Exception e) {
            System.debug(LoggingLevel.ERROR, 'SOAP Callout Error: ' + e.getMessage());
            throw new CalloutException('SOAP service error: ' + e.getMessage());
        }
    }
}

Platform Events

Platform Event Definition

Use Case: Asynchronous, event-driven communication

Template: templates/platform-events/platform-event-definition.object-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
    <deploymentStatus>Deployed</deploymentStatus>
    <eventType>HighVolume</eventType>
    <label>{{EventLabel}}</label>
    <pluralLabel>{{EventPluralLabel}}</pluralLabel>
    <publishBehavior>PublishAfterCommit</publishBehavior>
    <fields>
        <fullName>{{FieldName}}__c</fullName>
        <label>{{FieldLabel}}</label>
        <type>Text</type>
        <length>255</length>
    </fields>
    <!-- Add more fields as needed -->
</CustomObject>

Event Types:

  • StandardVolume: ~2,000 events/hour, standard delivery
  • HighVolume: Millions/day, at-least-once delivery, 24-hour retention

Event Publisher

Template: templates/platform-events/event-publisher.cls

public with sharing class {{EventName}}Publisher {

    public static void publishEvents(List<{{EventName}}__e> events) {
        if (events == null || events.isEmpty()) {
            return;
        }

        List<Database.SaveResult> results = EventBus.publish(events);

        for (Integer i = 0; i < results.size(); i++) {
            Database.SaveResult sr = results[i];
            if (!sr.isSuccess()) {
                for (Database.Error err : sr.getErrors()) {
                    System.debug(LoggingLevel.ERROR,
                        'Event publish error: ' + err.getStatusCode() + ' - ' + err.getMessage());
                }
            }
        }
    }

    public static void publishSingleEvent(Map<String, Object> eventData) {
        {{EventName}}__e event = new {{EventName}}__e();
        // Map fields from eventData
        event.{{FieldName}}__c = (String) eventData.get('{{fieldKey}}');

        Database.SaveResult sr = EventBus.publish(event);
        if (!sr.isSuccess()) {
            throw new EventPublishException('Failed to publish event: ' + sr.getErrors());
        }
    }

    public class EventPublishException extends Exception {}
}

Event Subscriber Trigger

Template: templates/platform-events/event-subscriber-trigger.trigger

trigger {{EventName}}Subscriber on {{EventName}}__e (after insert) {
    // Get replay ID for resumption
    String lastReplayId = '';

    for ({{EventName}}__e event : Trigger.new) {
        // Store replay ID for potential resume
        lastReplayId = event.ReplayId;

        try {
            // Process event
            {{EventName}}Handler.processEvent(event);
        } catch (Exception e) {
            // Log error but don't throw - allow other events to process
            System.debug(LoggingLevel.ERROR,
                'Event processing error: ' + e.getMessage() +
                ' ReplayId: ' + event.ReplayId);
        }
    }

    // Set resume checkpoint (for high-volume events)
    EventBus.TriggerContext.currentContext().setResumeCheckpoint(lastReplayId);
}

Change Data Capture (CDC)

CDC Enablement

Enable CDC via Setup → Integrations → Change Data Capture, or via metadata:

Objects supporting CDC: Standard objects, Custom objects

Channel Format: {{ObjectAPIName}}ChangeEvent (e.g., AccountChangeEvent, Order__ChangeEvent)

CDC Subscriber Trigger

Template: templates/cdc/cdc-subscriber-trigger.trigger

trigger {{ObjectName}}CDCSubscriber on {{ObjectName}}ChangeEvent (after insert) {

    for ({{ObjectName}}ChangeEvent event : Trigger.new) {
        // Get change event header
        EventBus.ChangeEventHeader header = event.ChangeEventHeader;

        String changeType = header.getChangeType();
        List<String> changedFields = header.getChangedFields();
        String recordId = header.getRecordIds()[0]; // First record ID

        System.debug('CDC Event - Type: ' + changeType +
                     ', RecordId: ' + recordId +
                     ', Changed Fields: ' + changedFields);

        // Route based on change type
        switch on changeType {
            when 'CREATE' {
                // Handle new record
                {{ObjectName}}CDCHandler.handleCreate(event);
            }
            when 'UPDATE' {
                // Handle update
                {{ObjectName}}CDCHandler.handleUpdate(event, changedFields);
            }
            when 'DELETE' {
                // Handle delete
                {{ObjectName}}CDCHandler.handleDelete(recordId);
            }
            when 'UNDELETE' {
                // Handle undelete
                {{ObjectName}}CDCHandler.handleUndelete(event);
            }
        }
    }
}

CDC Handler Service

Template: templates/cdc/cdc-handler.cls

public with sharing class {{ObjectName}}CDCHandler {

    public static void handleCreate({{ObjectName}}ChangeEvent event) {
        // Sync to external system on create
        Map<String, Object> payload = buildPayload(event);
        System.enqueueJob(new ExternalSystemSyncQueueable(payload, 'CREATE'));
    }

    public static void handleUpdate({{ObjectName}}ChangeEvent event, List<String> changedFields) {
        // Only sync if relevant fields changed
        Set<String> fieldsToWatch = new Set<String>{'Name', 'Status__c', 'Amount__c'};

        Boolean relevantChange = false;
        for (String field : changedFields) {
            if (fieldsToWatch.contains(field)) {
                relevantChange = true;
                break;
            }
        }

        if (relevantChange) {
            Map<String, Object> payload = buildPayload(event);
            payload.put('changedFields', changedFields);
            System.enqueueJob(new ExternalSystemSyncQueueable(payload, 'UPDATE'));
        }
    }

    public static void handleDelete(String recordId) {
        Map<String, Object> payload = new Map<String, Object>{'recordId' => recordId};
        System.enqueueJob(new ExternalSystemSyncQueueable(payload, 'DELETE'));
    }

    public static void handleUndelete({{ObjectName}}ChangeEvent event) {
        handleCreate(event); // Treat undelete like create
    }

    private static Map<String, Object> buildPayload({{ObjectName}}ChangeEvent event) {
        return new Map<String, Object>{
            'recordId' => event.ChangeEventHeader.getRecordIds()[0],
            'commitTimestamp' => event.ChangeEventHeader.getCommitTimestamp(),
            // Add event field values
            'name' => event.Name
            // Add more fields
        };
    }
}

Scoring System (120 Points)

Category Breakdown

Category Points Evaluation Criteria
Security 30 Named Credentials used (no hardcoded secrets), OAuth scopes minimized, certificate auth where applicable
Error Handling 25 Retry logic present, timeout handling (120s max), specific exception types, logging implemented
Bulkification 20 Batch callouts considered, CDC bulk handling, event batching for Platform Events
Architecture 20 Async patterns for DML-triggered callouts, proper service layer separation, single responsibility
Best Practices 15 Governor limit awareness, proper HTTP methods, idempotency for retries
Documentation 10 Clear intent documented, endpoint versioning noted, API contract documented

Scoring Thresholds

Score: XX/120 Rating
├─ ⭐⭐⭐⭐⭐ Excellent (108-120): Production-ready, follows all best practices
├─ ⭐⭐⭐⭐ Very Good (90-107): Minor improvements suggested
├─ ⭐⭐⭐ Good (72-89): Acceptable with noted improvements
├─ ⭐⭐ Needs Work (54-71): Address issues before deployment
└─ ⭐ Block (<54): CRITICAL issues, do not deploy

Scoring Output Format

📊 INTEGRATION SCORE: XX/120 ⭐⭐⭐⭐ Rating
════════════════════════════════════════════════════

🔐 Security           XX/30  ████████░░ XX%
├─ Named Credentials used: ✅
├─ No hardcoded secrets: ✅
└─ OAuth scopes minimal: ✅

⚠️ Error Handling     XX/25  ████████░░ XX%
├─ Retry logic: ✅
├─ Timeout handling: ✅
└─ Logging: ✅

📦 Bulkification      XX/20  ████████░░ XX%
├─ Batch callouts: ✅
└─ Event batching: ✅

🏗️ Architecture       XX/20  ████████░░ XX%
├─ Async patterns: ✅
└─ Service separation: ✅

✅ Best Practices     XX/15  ████████░░ XX%
├─ Governor limits: ✅
└─ Idempotency: ✅

📝 Documentation      XX/10  ████████░░ XX%
├─ Clear intent: ✅
└─ API versioning: ✅

════════════════════════════════════════════════════

Cross-Skill Integration

To Skill When to Use
sf-connected-apps OAuth Connected App for Named Credential
sf-apex Custom callout service beyond templates
sf-metadata Query existing Named Credentials
sf-deploy Deploy to org
sf-ai-agentforce Agent action using External Service
sf-flow HTTP Callout Flow for agent

Agentforce Integration Flow

sf-integration → Named Credential + External Service → sf-flow → HTTP Callout wrapper → sf-ai-agentforce → Agent with flow:// target → sf-deploy → Deploy all


CLI Commands Reference

Named Credentials

# List Named Credentials
sf org list metadata --metadata-type NamedCredential --target-org {{alias}}

# Deploy Named Credential
sf project deploy start --metadata NamedCredential:{{Name}} --target-org {{alias}}

# Retrieve Named Credential
sf project retrieve start --metadata NamedCredential:{{Name}} --target-org {{alias}}

External Services

# List External Service Registrations
sf org list metadata --metadata-type ExternalServiceRegistration --target-org {{alias}}

# Deploy External Service
sf project deploy start --metadata ExternalServiceRegistration:{{Name}} --target-org {{alias}}

Platform Events

# List Platform Events
sf org list metadata --metadata-type CustomObject --target-org {{alias}} | grep "__e"

# Deploy Platform Event
sf project deploy start --metadata CustomObject:{{EventName}}__e --target-org {{alias}}

Anti-Patterns

Anti-Pattern Problem Correct Pattern
Hardcoded credentials Security vulnerability, credential rotation nightmare Use Named Credentials
Sync callout in trigger CalloutException: Uncommitted work pending Use Queueable with Database.AllowsCallouts
No timeout specified Default 10s may be too short Set req.setTimeout(120000) (max 120s)
No retry logic Transient failures cause data loss Implement exponential backoff
Ignoring status codes Silent failures Check statusCode and handle 4xx/5xx
100+ callouts per transaction Governor limit exceeded Batch callouts, use async
No logging Can't debug production issues Log all callout requests/responses
Exposing API errors to users Security risk, poor UX Catch and wrap in user-friendly messages

Notes & Dependencies

  • API Version: 62.0+ (Winter '25) recommended for External Credentials
  • Required Permissions: API Enabled, External Services access
  • Optional Skills: sf-connected-apps (OAuth setup), sf-apex (custom callout code), sf-deploy (deployment)
  • Scoring Mode: Strict (block deployment if score < 54)

License

MIT License - See LICENSE file for details.