{"id":"840fe6b6-95b6-4a97-94dc-762e66e53747","shortId":"HnWR4V","kind":"skill","title":"salesforce-development","tagline":"Expert patterns for Salesforce platform development including","description":"# Salesforce Development\n\nExpert patterns for Salesforce platform development including Lightning Web\nComponents (LWC), Apex triggers and classes, REST/Bulk APIs, Connected Apps,\nand Salesforce DX with scratch orgs and 2nd generation packages (2GP).\n\n## Patterns\n\n### Lightning Web Component with Wire Service\n\nUse @wire decorator for reactive data binding with Lightning Data Service\nor Apex methods. @wire fits LWC's reactive architecture and enables\nSalesforce performance optimizations.\n\n// myComponent.js\nimport { LightningElement, wire, api } from 'lwc';\nimport { getRecord, getFieldValue } from 'lightning/uiRecordApi';\nimport getRelatedRecords from '@salesforce/apex/MyController.getRelatedRecords';\nimport ACCOUNT_NAME from '@salesforce/schema/Account.Name';\nimport ACCOUNT_INDUSTRY from '@salesforce/schema/Account.Industry';\n\nconst FIELDS = [ACCOUNT_NAME, ACCOUNT_INDUSTRY];\n\nexport default class MyComponent extends LightningElement {\n  @api recordId;  // Passed from parent or record page\n\n  // Wire to Lightning Data Service (preferred for single records)\n  @wire(getRecord, { recordId: '$recordId', fields: FIELDS })\n  account;\n\n  // Wire to Apex method (for complex queries)\n  @wire(getRelatedRecords, { accountId: '$recordId' })\n  wiredRecords({ error, data }) {\n    if (data) {\n      this.relatedRecords = data;\n      this.error = undefined;\n    } else if (error) {\n      this.error = error;\n      this.relatedRecords = undefined;\n    }\n  }\n\n  get accountName() {\n    return getFieldValue(this.account.data, ACCOUNT_NAME);\n  }\n\n  get isLoading() {\n    return !this.account.data && !this.account.error;\n  }\n\n  // Reactive: changing recordId automatically re-fetches\n}\n\n// myComponent.html\n<template>\n  <lightning-card title={accountName}>\n    <template if:true={isLoading}>\n      <lightning-spinner alternative-text=\"Loading\"></lightning-spinner>\n    </template>\n\n    <template if:true={account.data}>\n      <p>Industry: {industry}</p>\n    </template>\n\n    <template if:true={error}>\n      <p class=\"slds-text-color_error\">{error.body.message}</p>\n    </template>\n  </lightning-card>\n</template>\n\n// MyController.cls\npublic with sharing class MyController {\n  @AuraEnabled(cacheable=true)\n  public static List<Contact> getRelatedRecords(Id accountId) {\n    return [\n      SELECT Id, Name, Email, Phone\n      FROM Contact\n      WHERE AccountId = :accountId\n      WITH SECURITY_ENFORCED\n      LIMIT 100\n    ];\n  }\n}\n\n### Context\n\n- building LWC components\n- fetching Salesforce data\n- reactive UI\n\n### Bulkified Apex Trigger with Handler Pattern\n\nApex triggers must be bulkified to handle 200+ records per transaction.\nUse handler pattern for separation of concerns, testability, and\nrecursion prevention.\n\n// AccountTrigger.trigger\ntrigger AccountTrigger on Account (\n  before insert, before update, before delete,\n  after insert, after update, after delete, after undelete\n) {\n  new AccountTriggerHandler().run();\n}\n\n// TriggerHandler.cls (base class)\npublic virtual class TriggerHandler {\n  // Recursion prevention\n  private static Set<String> executedHandlers = new Set<String>();\n\n  public void run() {\n    String handlerName = String.valueOf(this).split(':')[0];\n\n    // Prevent recursion\n    String contextKey = handlerName + '_' + Trigger.operationType;\n    if (executedHandlers.contains(contextKey)) {\n      return;\n    }\n    executedHandlers.add(contextKey);\n\n    switch on Trigger.operationType {\n      when BEFORE_INSERT { this.beforeInsert(); }\n      when BEFORE_UPDATE { this.beforeUpdate(); }\n      when BEFORE_DELETE { this.beforeDelete(); }\n      when AFTER_INSERT { this.afterInsert(); }\n      when AFTER_UPDATE { this.afterUpdate(); }\n      when AFTER_DELETE { this.afterDelete(); }\n      when AFTER_UNDELETE { this.afterUndelete(); }\n    }\n  }\n\n  // Override in child classes\n  protected virtual void beforeInsert() {}\n  protected virtual void beforeUpdate() {}\n  protected virtual void beforeDelete() {}\n  protected virtual void afterInsert() {}\n  protected virtual void afterUpdate() {}\n  protected virtual void afterDelete() {}\n  protected virtual void afterUndelete() {}\n}\n\n// AccountTriggerHandler.cls\npublic class AccountTriggerHandler extends TriggerHandler {\n  private List<Account> newAccounts;\n  private List<Account> oldAccounts;\n  private Map<Id, Account> newMap;\n  private Map<Id, Account> oldMap;\n\n  public AccountTriggerHandler() {\n    this.newAccounts = (List<Account>) Trigger.new;\n    this.oldAccounts = (List<Account>) Trigger.old;\n    this.newMap = (Map<Id, Account>) Trigger.newMap;\n    this.oldMap = (Map<Id, Account>) Trigger.oldMap;\n  }\n\n  protected override void afterInsert() {\n    createDefaultContacts();\n    notifySlack();\n  }\n\n  protected override void afterUpdate() {\n    handleIndustryChange();\n  }\n\n  // BULKIFIED: Query once, update once\n  private void createDefaultContacts() {\n    List<Contact> contactsToInsert = new List<Contact>();\n\n    for (Account acc : newAccounts) {\n      if (acc.Type == 'Prospect') {\n        contactsToInsert.add(new Contact(\n          AccountId = acc.Id,\n          LastName = 'Primary Contact',\n          Email = 'contact@' + acc.Website\n        ));\n      }\n    }\n\n    if (!contactsToInsert.isEmpty()) {\n      insert contactsToInsert;  // Single DML for all\n    }\n  }\n\n  private void handleIndustryChange() {\n    Set<Id> changedAccountIds = new Set<Id>();\n\n    for (Account acc : newAccounts) {\n      Account oldAcc = oldMap.get(acc.Id);\n      if (acc.Industry != oldAcc.Industry) {\n        changedAccountIds.add(acc.Id);\n      }\n    }\n\n    if (!changedAccountIds.isEmpty()) {\n      // Queue async processing for heavy work\n      System.enqueueJob(new IndustryChangeQueueable(changedAccountIds));\n    }\n  }\n\n  private void notifySlack() {\n    // Offload callouts to async\n    List<Id> accountIds = new List<Id>(newMap.keySet());\n    System.enqueueJob(new SlackNotificationQueueable(accountIds));\n  }\n}\n\n### Context\n\n- apex triggers\n- data operations\n- automation\n\n### Queueable Apex for Async Processing\n\nUse Queueable Apex for async processing with support for non-primitive\ntypes, monitoring via AsyncApexJob, and job chaining. Limit: 50 jobs\nper transaction, 1 child job when chaining.\n\n// IndustryChangeQueueable.cls\npublic class IndustryChangeQueueable implements Queueable, Database.AllowsCallouts {\n  private Set<Id> accountIds;\n  private Integer retryCount;\n\n  public IndustryChangeQueueable(Set<Id> accountIds) {\n    this(accountIds, 0);\n  }\n\n  public IndustryChangeQueueable(Set<Id> accountIds, Integer retryCount) {\n    this.accountIds = accountIds;\n    this.retryCount = retryCount;\n  }\n\n  public void execute(QueueableContext context) {\n    try {\n      // Query with fresh data\n      List<Account> accounts = [\n        SELECT Id, Name, Industry, OwnerId\n        FROM Account\n        WHERE Id IN :accountIds\n        WITH SECURITY_ENFORCED\n      ];\n\n      // Process and make callout\n      for (Account acc : accounts) {\n        syncToExternalSystem(acc);\n      }\n\n      // Update records\n      updateRelatedOpportunities(accountIds);\n\n    } catch (Exception e) {\n      handleError(e);\n    }\n  }\n\n  private void syncToExternalSystem(Account acc) {\n    HttpRequest req = new HttpRequest();\n    req.setEndpoint('callout:ExternalCRM/accounts');\n    req.setMethod('POST');\n    req.setHeader('Content-Type', 'application/json');\n    req.setBody(JSON.serialize(new Map<String, Object>{\n      'salesforceId' => acc.Id,\n      'name' => acc.Name,\n      'industry' => acc.Industry\n    }));\n\n    Http http = new Http();\n    HttpResponse res = http.send(req);\n\n    if (res.getStatusCode() != 200 && res.getStatusCode() != 201) {\n      throw new CalloutException('Sync failed: ' + res.getBody());\n    }\n  }\n\n  private void updateRelatedOpportunities(Set<Id> accIds) {\n    List<Opportunity> oppsToUpdate = [\n      SELECT Id, Industry__c, AccountId\n      FROM Opportunity\n      WHERE AccountId IN :accIds\n      WITH SECURITY_ENFORCED\n    ];\n\n    Map<Id, Account> accountMap = new Map<Id, Account>([\n      SELECT Id, Industry FROM Account WHERE Id IN :accIds\n    ]);\n\n    for (Opportunity opp : oppsToUpdate) {\n      opp.Industry__c = accountMap.get(opp.AccountId).Industry;\n    }\n\n    if (!oppsToUpdate.isEmpty()) {\n      update oppsToUpdate;\n    }\n  }\n\n  private void handleError(Exception e) {\n    // Log error\n    System.debug(LoggingLevel.ERROR, 'Queueable failed: ' + e.getMessage());\n\n    // Retry with exponential backoff (max 3 retries)\n    if (retryCount < 3) {\n      // Chain new job for retry\n      System.enqueueJob(new IndustryChangeQueueable(accountIds, retryCount + 1));\n    } else {\n      // Create error record for monitoring\n      insert new Integration_Error__c(\n        Type__c = 'Industry Sync',\n        Message__c = e.getMessage(),\n        Stack_Trace__c = e.getStackTraceString(),\n        Record_Ids__c = String.join(new List<Id>(accountIds), ',')\n      );\n    }\n  }\n}\n\n### Context\n\n- async processing\n- long-running operations\n- callouts from triggers\n\n### REST API Integration with Connected App\n\nExternal integrations use Connected Apps with OAuth 2.0. JWT Bearer flow\nfor server-to-server, Web Server flow for user-facing apps. Always use\nNamed Credentials for secure callout configuration.\n\n// Node.js - JWT Bearer Flow (server-to-server)\nimport jwt from 'jsonwebtoken';\nimport fs from 'fs';\n\nclass SalesforceClient {\n  private accessToken: string | null = null;\n  private instanceUrl: string | null = null;\n  private tokenExpiry: number = 0;\n\n  constructor(\n    private clientId: string,\n    private username: string,\n    private privateKeyPath: string,\n    private loginUrl: string = 'https://login.salesforce.com'\n  ) {}\n\n  async authenticate(): Promise<void> {\n    // Check if token is still valid (5 min buffer)\n    if (this.accessToken && Date.now() < this.tokenExpiry - 300000) {\n      return;\n    }\n\n    const privateKey = fs.readFileSync(this.privateKeyPath, 'utf8');\n\n    // Create JWT assertion\n    const claim = {\n      iss: this.clientId,\n      sub: this.username,\n      aud: this.loginUrl,\n      exp: Math.floor(Date.now() / 1000) + 300  // 5 minutes\n    };\n\n    const assertion = jwt.sign(claim, privateKey, { algorithm: 'RS256' });\n\n    // Exchange JWT for access token\n    const response = await fetch(`${this.loginUrl}/services/oauth2/token`, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n      body: new URLSearchParams({\n        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',\n        assertion\n      })\n    });\n\n    if (!response.ok) {\n      const error = await response.json();\n      throw new Error(`Auth failed: ${error.error_description}`);\n    }\n\n    const data = await response.json();\n    this.accessToken = data.access_token;\n    this.instanceUrl = data.instance_url;\n    this.tokenExpiry = Date.now() + 7200000;  // 2 hours\n  }\n\n  async query(soql: string): Promise<any> {\n    await this.authenticate();\n\n    const response = await fetch(\n      `${this.instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(soql)}`,\n      {\n        headers: {\n          'Authorization': `Bearer ${this.accessToken}`,\n          'Content-Type': 'application/json'\n        }\n      }\n    );\n\n    if (!response.ok) {\n      await this.handleError(response);\n    }\n\n    return response.json();\n  }\n\n  async createRecord(sobject: string, data: object): Promise<any> {\n    await this.authenticate();\n\n    const response = await fetch(\n      `${this.instanceUrl}/services/data/v59.0/sobjects/${sobject}`,\n      {\n        method: 'POST',\n        headers: {\n          'Authorization': `Bearer ${this.accessToken}`,\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify(data)\n      }\n    );\n\n    if (!response.ok) {\n      await this.handleError(response);\n    }\n\n    return response.json();\n  }\n\n  private async handleError(response: Response): Promise<never> {\n    const error = await response.json();\n\n    if (response.status === 401) {\n      // Token expired, clear and retry\n      this.accessToken = null;\n      throw new Error('Session expired, retry required');\n    }\n\n    throw new Error(`API Error: ${JSON.stringify(error)}`);\n  }\n}\n\n// Usage\nconst sf = new SalesforceClient(\n  process.env.SF_CLIENT_ID!,\n  process.env.SF_USERNAME!,\n  './certificates/server.key'\n);\n\nconst accounts = await sf.query(\n  \"SELECT Id, Name FROM Account WHERE CreatedDate = TODAY\"\n);\n\n### Context\n\n- external integration\n- REST API access\n- connected apps\n\n### Bulk API 2.0 for Large Data Operations\n\nUse Bulk API 2.0 for operations on 10K+ records. Asynchronous processing\nwith job-based workflow. Part of REST API with streamlined interface\ncompared to original Bulk API.\n\n// Node.js - Bulk API 2.0 insert\nclass SalesforceBulkClient extends SalesforceClient {\n\n  async bulkInsert(sobject: string, records: object[]): Promise<any> {\n    await this.authenticate();\n\n    // Step 1: Create job\n    const job = await this.createBulkJob(sobject, 'insert');\n\n    try {\n      // Step 2: Upload data (CSV format)\n      await this.uploadJobData(job.id, records);\n\n      // Step 3: Close job to start processing\n      await this.closeJob(job.id);\n\n      // Step 4: Poll for completion\n      return await this.waitForJobCompletion(job.id);\n\n    } catch (error) {\n      // Abort job on error\n      await this.abortJob(job.id);\n      throw error;\n    }\n  }\n\n  private async createBulkJob(sobject: string, operation: string): Promise<any> {\n    const response = await fetch(\n      `${this.instanceUrl}/services/data/v59.0/jobs/ingest`,\n      {\n        method: 'POST',\n        headers: {\n          'Authorization': `Bearer ${this.accessToken}`,\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify({\n          object: sobject,\n          operation,\n          contentType: 'CSV',\n          lineEnding: 'LF'\n        })\n      }\n    );\n\n    return response.json();\n  }\n\n  private async uploadJobData(jobId: string, records: object[]): Promise<void> {\n    // Convert to CSV\n    const csv = this.recordsToCSV(records);\n\n    await fetch(\n      `${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}/batches`,\n      {\n        method: 'PUT',\n        headers: {\n          'Authorization': `Bearer ${this.accessToken}`,\n          'Content-Type': 'text/csv'\n        },\n        body: csv\n      }\n    );\n  }\n\n  private async closeJob(jobId: string): Promise<void> {\n    await fetch(\n      `${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}`,\n      {\n        method: 'PATCH',\n        headers: {\n          'Authorization': `Bearer ${this.accessToken}`,\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify({ state: 'UploadComplete' })\n      }\n    );\n  }\n\n  private async waitForJobCompletion(jobId: string): Promise<any> {\n    const maxWaitTime = 10 * 60 * 1000;  // 10 minutes\n    const pollInterval = 5000;  // 5 seconds\n    const startTime = Date.now();\n\n    while (Date.now() - startTime < maxWaitTime) {\n      const response = await fetch(\n        `${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}`,\n        {\n          headers: { 'Authorization': `Bearer ${this.accessToken}` }\n        }\n      );\n\n      const job = await response.json();\n\n      if (job.state === 'JobComplete') {\n        // Get results\n        return {\n          success: job.numberRecordsProcessed - job.numberRecordsFailed,\n          failed: job.numberRecordsFailed,\n          failedResults: job.numberRecordsFailed > 0\n            ? await this.getFailedResults(jobId)\n            : []\n        };\n      }\n\n      if (job.state === 'Failed' || job.state === 'Aborted') {\n        throw new Error(`Bulk job failed: ${job.state}`);\n      }\n\n      await new Promise(r => setTimeout(r, pollInterval));\n    }\n\n    throw new Error('Bulk job timeout');\n  }\n\n  private async getFailedResults(jobId: string): Promise<any[]> {\n    const response = await fetch(\n      `${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}/failedResults`,\n      {\n        headers: { 'Authorization': `Bearer ${this.accessToken}` }\n      }\n    );\n\n    const csv = await response.text();\n    return this.parseCSV(csv);\n  }\n\n  private recordsToCSV(records: object[]): string {\n    if (records.length === 0) return '';\n\n    const headers = Object.keys(records[0]);\n    const rows = records.map(r =>\n      headers.map(h => this.escapeCSV(r[h])).join(',')\n    );\n\n    return [headers.join(','), ...rows].join('\\n');\n  }\n\n  private escapeCSV(value: any): string {\n    if (value === null || value === undefined) return '';\n    const str = String(value);\n    if (str.includes(',') || str.includes('\"') || str.includes('\\n')) {\n      return `\"${str.replace(/\"/g, '\"\"')}\"`;\n    }\n    return str;\n  }\n}\n\n### Context\n\n- large data volumes\n- data migration\n- bulk operations\n\n### Salesforce DX with Scratch Orgs\n\nSource-driven development with disposable scratch orgs for isolated\ntesting. Scratch orgs exist 7-30 days and can be created throughout\nthe day, unlike sandbox refresh limits.\n\n// project-scratch-def.json - Scratch org definition\n{\n  \"orgName\": \"MyApp Dev Org\",\n  \"edition\": \"Developer\",\n  \"features\": [\"EnableSetPasswordInApi\", \"Communities\"],\n  \"settings\": {\n    \"lightningExperienceSettings\": {\n      \"enableS1DesktopEnabled\": true\n    },\n    \"mobileSettings\": {\n      \"enableS1EncryptedStoragePref2\": false\n    },\n    \"securitySettings\": {\n      \"passwordPolicies\": {\n        \"enableSetPasswordInApi\": true\n      }\n    }\n  }\n}\n\n// sfdx-project.json - Project configuration\n{\n  \"packageDirectories\": [\n    {\n      \"path\": \"force-app\",\n      \"default\": true,\n      \"package\": \"MyPackage\",\n      \"versionName\": \"ver 1.0\",\n      \"versionNumber\": \"1.0.0.NEXT\",\n      \"dependencies\": [\n        {\n          \"package\": \"SomePackage@2.0.0\"\n        }\n      ]\n    }\n  ],\n  \"namespace\": \"myns\",\n  \"sfdcLoginUrl\": \"https://login.salesforce.com\",\n  \"sourceApiVersion\": \"59.0\"\n}\n\n# Development workflow commands\n# 1. Create scratch org\nsf org create scratch \\\n  --definition-file config/project-scratch-def.json \\\n  --alias myapp-dev \\\n  --duration-days 7 \\\n  --set-default\n\n# 2. Push source to scratch org\nsf project deploy start --target-org myapp-dev\n\n# 3. Assign permission set\nsf org assign permset --name MyApp_Admin --target-org myapp-dev\n\n# 4. Import sample data\nsf data import tree --plan data/sample-data-plan.json --target-org myapp-dev\n\n# 5. Open org\nsf org open --target-org myapp-dev\n\n# 6. Run tests\nsf apex run test \\\n  --code-coverage \\\n  --result-format human \\\n  --wait 10 \\\n  --target-org myapp-dev\n\n# 7. Pull changes back\nsf project retrieve start --target-org myapp-dev\n\n### Context\n\n- development workflow\n- CI/CD\n- testing\n\n### 2nd Generation Package (2GP) Development\n\n2GP replaces 1GP with source-driven, modular packaging. Requires Dev Hub\nwith 2GP enabled, namespace linked, and 75% code coverage for promoted\npackages.\n\n# Enable Dev Hub and 2GP in Setup:\n# Setup > Dev Hub > Enable Dev Hub\n# Setup > Dev Hub > Enable Unlocked Packages and 2GP\n\n# Link namespace (required for managed packages)\nsf package create \\\n  --name \"MyManagedPackage\" \\\n  --package-type Managed \\\n  --path force-app \\\n  --target-dev-hub DevHub\n\n# Create package version (beta)\nsf package version create \\\n  --package \"MyManagedPackage\" \\\n  --installation-key-bypass \\\n  --wait 30 \\\n  --code-coverage \\\n  --target-dev-hub DevHub\n\n# Check version status\nsf package version list --packages \"MyManagedPackage\" --target-dev-hub DevHub\n\n# Promote to released (requires 75% coverage)\nsf package version promote \\\n  --package \"MyManagedPackage@1.0.0-1\" \\\n  --target-dev-hub DevHub\n\n# Install in sandbox for testing\nsf package install \\\n  --package \"MyManagedPackage@1.0.0-1\" \\\n  --target-org MySandbox \\\n  --wait 20\n\n# CI/CD Pipeline (GitHub Actions)\n# .github/workflows/salesforce-ci.yml\nname: Salesforce CI\n\non:\n  push:\n    branches: [main, develop]\n  pull_request:\n    branches: [main]\n\njobs:\n  validate:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Install Salesforce CLI\n        run: npm install -g @salesforce/cli\n\n      - name: Authenticate Dev Hub\n        run: |\n          echo \"${{ secrets.SFDX_AUTH_URL }}\" > auth.txt\n          sf org login sfdx-url --sfdx-url-file auth.txt --alias DevHub --set-default-dev-hub\n\n      - name: Create Scratch Org\n        run: |\n          sf org create scratch \\\n            --definition-file config/project-scratch-def.json \\\n            --alias ci-scratch \\\n            --duration-days 1 \\\n            --set-default\n\n      - name: Deploy Source\n        run: sf project deploy start --target-org ci-scratch\n\n      - name: Run Tests\n        run: |\n          sf apex run test \\\n            --code-coverage \\\n            --result-format human \\\n            --wait 20 \\\n            --target-org ci-scratch\n\n      - name: Delete Scratch Org\n        if: always()\n        run: sf org delete scratch --target-org ci-scratch --no-prompt\n\n### Context\n\n- packaging\n- ISV development\n- AppExchange\n\n## Sharp Edges\n\n### Governor Limits Apply Per Transaction, Not Per Record\n\nSeverity: CRITICAL\n\n### @wire Results Are Cached and May Be Stale\n\nSeverity: HIGH\n\n### LWC Properties Are Case-Sensitive\n\nSeverity: MEDIUM\n\n### Null Pointer Exceptions in Apex Collections\n\nSeverity: HIGH\n\n### Trigger Recursion Causes Infinite Loops\n\nSeverity: CRITICAL\n\n### Cannot Make Callouts from Synchronous Triggers\n\nSeverity: HIGH\n\n### Cannot Mix Setup and Non-Setup DML\n\nSeverity: HIGH\n\n### Dynamic SOQL Is Vulnerable to Injection\n\nSeverity: CRITICAL\n\n### Scratch Orgs Expire and Lose All Data\n\nSeverity: MEDIUM\n\n### API Version Mismatches Cause Silent Failures\n\nSeverity: MEDIUM\n\n## Validation Checks\n\n### SOQL Query Inside Loop\n\nSeverity: ERROR\n\nSOQL in loops causes governor limit exceptions with bulk data\n\nMessage: SOQL query inside loop. Query once outside the loop and use a Map.\n\n### DML Operation Inside Loop\n\nSeverity: ERROR\n\nDML in loops hits 150 statement limit\n\nMessage: DML operation inside loop. Collect records and perform single DML outside loop.\n\n### HTTP Callout in Trigger\n\nSeverity: ERROR\n\nSynchronous triggers cannot make callouts\n\nMessage: Callout in trigger. Use @future(callout=true) or Queueable with Database.AllowsCallouts.\n\n### Potential SOQL Injection\n\nSeverity: ERROR\n\nDynamic SOQL with string concatenation is vulnerable\n\nMessage: Dynamic SOQL with concatenation. Use bind variables or String.escapeSingleQuotes().\n\n### Missing WITH SECURITY_ENFORCED\n\nSeverity: WARNING\n\nSOQL should enforce FLS/CRUD permissions\n\nMessage: SOQL without security enforcement. Add WITH SECURITY_ENFORCED.\n\n### Hardcoded Salesforce ID\n\nSeverity: WARNING\n\nRecord IDs differ between orgs\n\nMessage: Hardcoded Salesforce ID. Query by DeveloperName or ExternalId instead.\n\n### Hardcoded Credentials\n\nSeverity: ERROR\n\nCredentials must use Named Credentials or Custom Metadata\n\nMessage: Hardcoded credentials. Use Named Credentials or Custom Metadata.\n\n### Direct DOM Manipulation in LWC\n\nSeverity: WARNING\n\nLWC uses shadow DOM, direct manipulation breaks encapsulation\n\nMessage: Direct DOM access in LWC. Use this.template.querySelector() or data binding.\n\n### Reactive Property Without @track\n\nSeverity: INFO\n\nComplex object properties need @track for reactivity\n\nMessage: Object assignment may need @track for reactivity (post-Spring '20 objects are auto-tracked).\n\n### Wire Without Refresh After DML\n\nSeverity: WARNING\n\nCached wire data becomes stale after updates\n\nMessage: DML after @wire without refreshApex. Data may be stale.\n\n## Collaboration\n\n### Delegation Triggers\n\n- user needs external API integration -> backend (REST API design, external system sync)\n- user needs complex UI beyond LWC -> frontend (Custom portal with React/Next.js)\n- user needs HubSpot integration -> hubspot-integration (Salesforce-HubSpot sync patterns)\n- user needs data warehouse sync -> data-engineer (ETL from Salesforce to warehouse)\n- user needs payment processing -> stripe-integration (Beyond Salesforce Billing)\n- user needs advanced auth -> auth-specialist (SSO, SAML, custom portals)\n\n## When to Use\n- User mentions or implies: salesforce\n- User mentions or implies: sfdc\n- User mentions or implies: apex\n- User mentions or implies: lwc\n- User mentions or implies: lightning web components\n- User mentions or implies: sfdx\n- User mentions or implies: scratch org\n- User mentions or implies: visualforce\n- User mentions or implies: soql\n- User mentions or implies: governor limits\n- User mentions or implies: connected app\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.","tags":["salesforce","development","antigravity","awesome","skills","sickn33","agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows"],"capabilities":["skill","source-sickn33","skill-salesforce-development","topic-agent-skills","topic-agentic-skills","topic-ai-agent-skills","topic-ai-agents","topic-ai-coding","topic-ai-workflows","topic-antigravity","topic-antigravity-skills","topic-claude-code","topic-claude-code-skills","topic-codex-cli","topic-codex-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/salesforce-development","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add sickn33/antigravity-awesome-skills","source_repo":"https://github.com/sickn33/antigravity-awesome-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 34583 github stars · SKILL.md body (24,800 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-22T18:52:10.048Z","embedding":null,"createdAt":"2026-04-18T21:43:49.349Z","updatedAt":"2026-04-22T18:52:10.048Z","lastSeenAt":"2026-04-22T18:52:10.048Z","tsv":"'-1':1848,1865 '-30':1531 '/batches':1303 '/certificates/server.key':1123 '/failedresults':1437 '/g':1500 '/services/data/v59.0/jobs/ingest':1261,1301,1325,1371,1435 '/services/data/v59.0/query':1024 '/services/data/v59.0/sobjects':1057 '/services/oauth2/token':960 '0':317,589,887,1394,1456,1462 '1':565,778,1198,1598,1958 '1.0':1582 '1.0.0':1847,1864 '1.0.0.next':1584 '10':1349,1352,1697 '100':234 '1000':939,1351 '10k':1158 '150':2154 '1gp':1730 '2':1010,1209,1621 '2.0':831,1146,1154,1182 '2.0.0':1588 '20':1871,1992,2326 '200':257,686 '201':688 '2gp':42,1726,1728,1741,1756,1772 '2nd':39,1723 '3':763,767,1219,1637 '30':1812 '300':940 '300000':918 '4':1229,1654 '401':1091 '5':911,941,1357,1670 '50':561 '5000':1356 '59.0':1594 '6':1682 '60':1350 '7':1530,1617,1704 '7200000':1009 '75':1746,1839 'abort':1239,1402 'acc':458,491,632,635,649 'acc.id':467,496,501,671 'acc.industry':498,675 'acc.name':673 'acc.type':461 'acc.website':473 'access':953,1141,2294 'accesstoken':875 'accid':699,712,732 'account':92,97,103,105,136,169,276,408,413,426,431,457,490,493,611,618,631,633,648,718,723,728,1125,1132 'account.data':196 'accountid':146,218,228,229,466,522,529,579,586,588,593,597,622,639,706,710,776,807 'accountmap':719 'accountmap.get':739 'accountnam':165,188 'accounttrigg':274 'accounttrigger.trigger':272 'accounttriggerhandl':292,396,416 'accounttriggerhandler.cls':393 'action':1875 'actions/checkout':1899 'add':2231 'admin':1647 'advanc':2419 'afterdelet':388 'afterinsert':380,436 'afterundelet':392 'afterupd':384,442 'algorithm':948 'alia':1610,1931,1951 'alway':848,2004 'apex':24,62,139,245,250,531,537,543,1686,1981,2058,2445 'api':29,79,113,819,1109,1140,1145,1153,1170,1178,1181,2104,2362,2366 'app':31,823,828,847,1143,1575,1791,2490 'appexchang':2023 'appli':2028 'application/json':663,1035,1068,1271,1336 'application/x-www-form-urlencoded':967 'architectur':69 'ask':2524 'assert':927,944,983 'assign':1638,1643,2317 'async':505,520,539,545,809,902,1012,1043,1080,1188,1249,1284,1317,1342,1424 'asyncapexjob':556 'asynchron':1160 'aud':934 'auraen':210 'auth':993,1917,2420,2422 'auth-specialist':2421 'auth.txt':1919,1930 'authent':903,1911 'author':1029,1062,1265,1307,1330,1374,1439 'auto':2330 'auto-track':2329 'autom':535 'automat':179 'await':957,988,999,1017,1021,1038,1050,1054,1074,1087,1126,1195,1203,1214,1225,1234,1243,1258,1298,1322,1368,1379,1395,1410,1432,1444 'back':1707 'backend':2364 'backoff':761 'base':295,1165 'bearer':833,858,982,1030,1063,1266,1308,1331,1375,1440 'becom':2342 'beforedelet':376 'beforeinsert':368 'beforeupd':372 'beta':1800 'beyond':2375,2414 'bill':2416 'bind':56,2211,2301 'bodi':968,1069,1272,1314,1337 'boundari':2532 'branch':1882,1887 'break':2289 'buffer':913 'build':236 'bulk':1144,1152,1177,1180,1406,1420,1509,2128 'bulkifi':244,254,444 'bulkinsert':1189 'bypass':1810 'c':705,738,789,791,795,799,803 'cach':2039,2339 'cacheabl':211 'callout':518,629,655,815,854,2071,2171,2180,2182,2187 'calloutexcept':691 'cannot':2069,2077,2178 'card':186 'case':2050 'case-sensit':2049 'catch':640,1237 'caus':2064,2107,2123 'chain':559,569,768 'chang':177,1706 'changedaccountid':486,513 'changedaccountids.add':500 'changedaccountids.isempty':503 'check':905,1821,2113 'child':363,566 'ci':1879,1953,1974,1997,2014 'ci-scratch':1952,1973,1996,2013 'ci/cd':1721,1872 'claim':929,946 'clarif':2526 'class':27,109,208,296,299,364,395,572,872,1184 'clear':1094,2499 'cli':1904 'client':1119 'clientid':890 'close':1220 'closejob':1318 'code':1690,1747,1814,1985 'code-coverag':1689,1813,1984 'collabor':2356 'collect':2059,2162 'command':1597 'communiti':1556 'compar':1174 'complet':1232 'complex':142,2308,2373 'compon':22,46,238,2457 'concaten':2202,2209 'concern':267 'config/project-scratch-def.json':1609,1950 'configur':855,1570 'connect':30,822,827,1142,2489 'const':101,920,928,943,955,986,997,1019,1052,1085,1114,1124,1201,1256,1294,1347,1354,1359,1366,1377,1430,1442,1458,1463,1489 'constructor':888 'contact':226,465,470,472 'contactstoinsert':453,477 'contactstoinsert.add':463 'contactstoinsert.isempty':475 'content':661,965,1033,1066,1269,1311,1334 'content-typ':660,964,1032,1065,1268,1310,1333 'contenttyp':1277 'context':235,530,604,808,1136,1503,1718,2019 'contextkey':321,326,329 'convert':1291 'coverag':1691,1748,1815,1840,1986 'creat':780,925,1199,1536,1599,1604,1781,1797,1804,1939,1945 'createbulkjob':1250 'createdd':1134 'createdefaultcontact':437,451 'createrecord':1044 'credenti':851,2256,2259,2263,2269,2272 'criteria':2535 'critic':2035,2068,2094 'csv':1212,1278,1293,1295,1315,1443,1448 'custom':2265,2274,2378,2426 'data':55,59,124,150,152,154,241,533,609,998,1047,1071,1149,1211,1505,1507,1657,1659,2101,2129,2300,2341,2352,2396,2400 'data-engin':2399 'data.access':1002 'data.instance':1005 'data/sample-data-plan.json':1663 'database.allowscallouts':576,2192 'date.now':916,938,1008,1361,1363 'day':1532,1539,1616,1957 'decor':52 'default':108,1576,1620,1935,1961 'definit':1547,1607,1948 'definition-fil':1606,1947 'deleg':2357 'delet':282,288,343,355,2000,2008 'depend':1585 'deploy':1629,1963,1968 'describ':2503 'descript':996 'design':2367 'dev':1550,1613,1636,1653,1669,1681,1703,1717,1738,1753,1760,1763,1766,1794,1818,1832,1851,1912,1936 'develop':3,9,12,18,1519,1553,1595,1719,1727,1884,2022 'developernam':2251 'devhub':1796,1820,1834,1853,1932 'differ':2242 'direct':2276,2287,2292 'dispos':1521 'dml':479,2084,2144,2150,2158,2167,2336,2347 'dom':2277,2286,2293 'driven':1518,1734 'durat':1615,1956 'duration-day':1614,1955 'dx':34,1512 'dynam':2087,2198,2206 'e':642,644,750 'e.getmessage':757,796 'e.getstacktracestring':800 'echo':1915 'edg':2025 'edit':1552 'els':157,779 'email':223,471 'enabl':71,1742,1752,1762,1768 'enables1desktopenabled':1559 'enables1encryptedstoragepref2':1562 'enablesetpasswordinapi':1555,1566 'encapsul':2290 'encodeuricompon':1026 'enforc':232,625,715,2218,2223,2230,2234 'engin':2401 'environ':2515 'environment-specif':2514 'error':149,159,161,202,752,781,788,987,992,1086,1101,1108,1110,1112,1238,1242,1247,1405,1419,2119,2149,2175,2197,2258 'error.body.message':203 'error.error':995 'escapecsv':1479 'etl':2402 'except':641,749,2056,2126 'exchang':950 'execut':602 'executedhandl':306 'executedhandlers.add':328 'executedhandlers.contains':325 'exist':1529 'exp':936 'expert':4,13,2520 'expir':1093,1103,2097 'exponenti':760 'export':107 'extend':111,397,1186 'extern':824,1137,2361,2368 'externalcrm/accounts':656 'externalid':2253 'face':846 'fail':693,756,994,1390,1400,1408 'failedresult':1392 'failur':2109 'fals':1563 'featur':1554 'fetch':182,239,958,1022,1055,1259,1299,1323,1369,1433 'field':102,134,135 'file':1608,1929,1949 'fit':65 'flow':834,842,859 'fls/crud':2224 'forc':1574,1790 'force-app':1573,1789 'format':1213,1694,1989 'fresh':608 'frontend':2377 'fs':869,871 'fs.readfilesync':922 'futur':2186 'g':1908 'generat':40,1724 'get':164,171,1384 'getfailedresult':1425 'getfieldvalu':84,167 'getrecord':83,131 'getrelatedrecord':88,145,216 'github':1874 'github/workflows/salesforce-ci.yml':1876 'governor':2026,2124,2483 'grant':971,978 'grant-typ':977 'h':1468,1471 'handl':256 'handleerror':643,748,1081 'handleindustrychang':443,484 'handler':248,262 'handlernam':313,322 'hardcod':2235,2246,2255,2268 'header':963,1028,1061,1264,1306,1329,1373,1438,1459 'headers.join':1474 'headers.map':1467 'heavi':508 'high':2045,2061,2076,2086 'hit':2153 'hour':1011 'http':676,677,679,2170 'http.send':682 'httprequest':650,653 'httprespons':680 'hub':1739,1754,1761,1764,1767,1795,1819,1833,1852,1913,1937 'hubspot':2384,2387,2391 'hubspot-integr':2386 'human':1695,1990 'id':217,221,407,412,425,430,613,620,703,717,722,725,730,802,1120,1129,2237,2241,2248 'ietf':974 'implement':574 'impli':2434,2439,2444,2449,2454,2461,2466,2472,2477,2482,2488 'import':76,82,87,91,96,864,868,1655,1660 'includ':10,19 'industri':98,106,197,198,615,674,704,726,741,792 'industrychangequeu':512,573,584,591,775 'industrychangequeueable.cls':570 'infinit':2065 'info':2307 'inject':2092,2195 'input':2529 'insert':278,284,335,347,476,785,1183,1206 'insid':2116,2133,2146,2160 'instal':1808,1854,1861,1902,1907 'installation-key-bypass':1807 'instanceurl':880 'instead':2254 'integ':581,594 'integr':787,820,825,1138,2363,2385,2388,2413 'interfac':1173 'isload':172,192 'isol':1525 'iss':930 'isv':2021 'job':558,562,567,770,1164,1200,1202,1221,1240,1378,1407,1421,1889 'job-bas':1163 'job.id':1216,1227,1236,1245 'job.numberrecordsfailed':1389,1391,1393 'job.numberrecordsprocessed':1388 'job.state':1382,1399,1401,1409 'jobcomplet':1383 'jobid':1286,1302,1319,1326,1344,1372,1397,1426,1436 'join':1472,1476 'json.serialize':665 'json.stringify':1070,1111,1273,1338 'jsonwebtoken':867 'jwt':832,857,865,926,951,981 'jwt-bearer':980 'jwt.sign':945 'key':1809 'larg':1148,1504 'lastnam':468 'latest':1896 'lf':1280 'lightn':20,44,58,123,185,2455 'lightning-card':184 'lightning/uirecordapi':86 'lightningel':77,112 'lightningexperienceset':1558 'limit':233,560,1543,2027,2125,2156,2484,2491 'lineend':1279 'link':1744,1773 'list':215,400,403,418,421,452,455,521,524,610,700,806,1827 'log':751 'logginglevel.error':754 'login':1922 'login.salesforce.com':901,1592 'loginurl':899 'long':812 'long-run':811 'loop':2066,2117,2122,2134,2139,2147,2152,2161,2169 'lose':2099 'lwc':23,66,81,237,2046,2280,2283,2296,2376,2450 'main':1883,1888 'make':628,2070,2179 'manag':1777,1787 'manipul':2278,2288 'map':406,411,424,429,667,716,721,2143 'match':2500 'math.floor':937 'max':762 'maxwaittim':1348,1365 'may':2041,2318,2353 'medium':2053,2103,2111 'mention':2432,2437,2442,2447,2452,2459,2464,2470,2475,2480,2486 'messag':794,2130,2157,2181,2205,2226,2245,2267,2291,2315,2346 'metadata':2266,2275 'method':63,140,961,1059,1262,1304,1327 'migrat':1508 'min':912 'minut':942,1353 'mismatch':2106 'miss':2215,2537 'mix':2078 'mobileset':1561 'modular':1735 'monitor':554,784 'must':252,2260 'myapp':1549,1612,1635,1646,1652,1668,1680,1702,1716 'myapp-dev':1611,1634,1651,1667,1679,1701,1715 'mycompon':110 'mycomponent.html':183 'mycomponent.js':75 'mycontrol':209 'mycontroller.cls':204 'mymanagedpackag':1783,1806,1829,1846,1863 'myn':1590 'mypackag':1579 'mysandbox':1869 'n':1477,1497 'name':93,104,170,222,614,672,850,1130,1645,1782,1877,1901,1910,1938,1962,1976,1999,2262,2271 'namespac':1589,1743,1774 'need':2311,2319,2360,2372,2383,2395,2408,2418 'new':291,307,454,464,487,511,523,527,652,666,678,690,720,769,774,786,805,969,991,1100,1107,1116,1404,1411,1418 'newaccount':401,459,492 'newmap':409 'newmap.keyset':525 'no-prompt':2016 'node.js':856,1179 'non':551,2082 'non-primit':550 'non-setup':2081 'notifyslack':438,516 'npm':1906 'null':877,878,882,883,1098,1485,2054 'number':886 'oauth':830,976 'object':669,1048,1193,1274,1289,1452,2309,2316,2327 'object.keys':1460 'offload':517 'oldacc':494 'oldacc.industry':499 'oldaccount':404 'oldmap':414 'oldmap.get':495 'open':1671,1675 'oper':534,814,1150,1156,1253,1276,1510,2145,2159 'opp':735 'opp.accountid':740 'opp.industry':737 'opportun':708,734 'oppstoupd':701,736,745 'oppstoupdate.isempty':743 'optim':74 'org':37,1515,1523,1528,1546,1551,1601,1603,1626,1633,1642,1650,1666,1672,1674,1678,1700,1714,1868,1921,1941,1944,1972,1995,2002,2007,2012,2096,2244,2468 'orgnam':1548 'origin':1176 'output':2509 'outsid':2137,2168 'overrid':361,434,440 'ownerid':616 'packag':41,1578,1586,1725,1736,1751,1770,1778,1780,1785,1798,1802,1805,1825,1828,1842,1845,1860,1862,2020 'package-typ':1784 'packagedirectori':1571 'page':120 'param':975 'parent':117 'part':1167 'pass':115 'passwordpolici':1565 'patch':1328 'path':1572,1788 'pattern':5,14,43,249,263,2393 'payment':2409 'per':259,563,2029,2032 'perform':73,2165 'permiss':1639,2225,2530 'permset':1644 'phone':224 'pipelin':1873 'plan':1662 'platform':8,17 'pointer':2055 'poll':1230 'pollinterv':1355,1416 'portal':2379,2427 'post':658,962,1060,1263,2324 'post-spr':2323 'potenti':2193 'prefer':126 'prevent':271,302,318 'primari':469 'primit':552 'privat':303,399,402,405,410,449,482,514,577,580,645,695,746,874,879,884,889,892,895,898,1079,1248,1283,1316,1341,1423,1449,1478 'privatekey':921,947 'privatekeypath':896 'process':506,540,546,626,810,1161,1224,2410 'process.env.sf':1118,1121 'project':1569,1628,1709,1967 'project-scratch-def.json':1544 'promis':904,1016,1049,1084,1194,1255,1290,1321,1346,1412,1428 'promot':1750,1835,1844 'prompt':2018 'properti':2047,2303,2310 'prospect':462 'protect':365,369,373,377,381,385,389,433,439 'public':205,213,297,309,394,415,571,583,590,600 'pull':1705,1885 'push':1622,1881 'put':1305 'q':1025 'queri':143,445,606,1013,2115,2132,2135,2249 'queue':504 'queueabl':536,542,575,755,2190 'queueablecontext':603 'r':1413,1415,1466,1470 're':181 're-fetch':180 'react/next.js':2381 'reactiv':54,68,176,242,2302,2314,2322 'record':119,129,258,637,782,801,1159,1192,1217,1288,1297,1451,1461,2033,2163,2240 'recordid':114,132,133,147,178 'records.length':1455 'records.map':1465 'recordstocsv':1450 'recurs':270,301,319,2063 'refresh':1542,2334 'refreshapex':2351 'releas':1837 'replac':1729 'req':651,683 'req.setbody':664 'req.setendpoint':654 'req.setheader':659 'req.setmethod':657 'request':1886 'requir':1105,1737,1775,1838,2528 'res':681 'res.getbody':694 'res.getstatuscode':685,687 'respons':956,1020,1040,1053,1076,1082,1083,1257,1367,1431 'response.json':989,1000,1042,1078,1088,1282,1380 'response.ok':985,1037,1073 'response.status':1090 'response.text':1445 'rest':818,1139,1169,2365 'rest/bulk':28 'result':1385,1693,1988,2037 'result-format':1692,1987 'retri':758,764,772,1096,1104 'retriev':1710 'retrycount':582,595,599,766,777 'return':166,173,219,327,919,1041,1077,1233,1281,1386,1446,1457,1473,1488,1498,1501 'review':2521 'row':1464,1475 'rs256':949 'run':293,311,813,1683,1687,1892,1905,1914,1942,1965,1977,1979,1982,2005 'runs-on':1891 'safeti':2531 'salesforc':2,7,11,16,33,72,240,1511,1878,1903,2236,2247,2390,2404,2415,2435 'salesforce-develop':1 'salesforce-hubspot':2389 'salesforce/apex/mycontroller.getrelatedrecords':90 'salesforce/cli':1909 'salesforce/schema/account.industry':100 'salesforce/schema/account.name':95 'salesforcebulkcli':1185 'salesforcecli':873,1117,1187 'salesforceid':670 'saml':2425 'sampl':1656 'sandbox':1541,1856 'scope':2502 'scratch':36,1514,1522,1527,1545,1600,1605,1625,1940,1946,1954,1975,1998,2001,2009,2015,2095,2467 'second':1358 'secrets.sfdx':1916 'secur':231,624,714,853,2217,2229,2233 'securityset':1564 'select':220,612,702,724,1128 'sensit':2051 'separ':265 'server':837,839,841,861,863 'server-to-serv':836,860 'servic':49,60,125 'session':1102 'set':305,308,485,488,578,585,592,698,1557,1619,1640,1934,1960 'set-default':1618,1959 'set-default-dev-hub':1933 'settimeout':1414 'setup':1758,1759,1765,2079,2083 'sever':2034,2044,2052,2060,2067,2075,2085,2093,2102,2110,2118,2148,2174,2196,2219,2238,2257,2281,2306,2337 'sf':1115,1602,1627,1641,1658,1673,1685,1708,1779,1801,1824,1841,1859,1920,1943,1966,1980,2006 'sf.query':1127 'sfdc':2440 'sfdcloginurl':1591 'sfdx':1924,1927,2462 'sfdx-project.json':1568 'sfdx-url':1923 'sfdx-url-fil':1926 'shadow':2285 'share':207 'sharp':2024 'silent':2108 'singl':128,478,2166 'skill':2494 'skill-salesforce-development' 'slacknotificationqueu':528 'sobject':1045,1058,1190,1205,1251,1275 'somepackag':1587 'soql':1014,1027,2088,2114,2120,2131,2194,2199,2207,2221,2227,2478 'sourc':1517,1623,1733,1964 'source-driven':1516,1732 'source-sickn33' 'sourceapivers':1593 'specialist':2423 'specif':2516 'split':316 'spring':2325 'sso':2424 'stack':797 'stale':2043,2343,2355 'start':1223,1630,1711,1969 'starttim':1360,1364 'state':1339 'statement':2155 'static':214,304 'status':1823 'step':1197,1208,1218,1228,1897 'still':909 'stop':2522 'str':1490,1502 'str.includes':1494,1495,1496 'str.replace':1499 'streamlin':1172 'string':312,320,668,876,881,891,894,897,900,1015,1046,1191,1252,1254,1287,1320,1345,1427,1453,1482,1491,2201 'string.escapesinglequotes':2214 'string.join':804 'string.valueof':314 'stripe':2412 'stripe-integr':2411 'sub':932 'substitut':2512 'success':1387,2534 'support':548 'switch':330 'sync':692,793,2370,2392,2398 'synchron':2073,2176 'synctoexternalsystem':634,647 'system':2369 'system.debug':753 'system.enqueuejob':510,526,773 'target':1632,1649,1665,1677,1699,1713,1793,1817,1831,1850,1867,1971,1994,2011 'target-dev-hub':1792,1816,1830,1849 'target-org':1631,1648,1664,1676,1698,1712,1866,1970,1993,2010 'task':2498 'templat':189,193,199 'test':1526,1684,1688,1722,1858,1978,1983,2518 'testabl':268 'text/csv':1313 'this.abortjob':1244 'this.accesstoken':915,1001,1031,1064,1097,1267,1309,1332,1376,1441 'this.account.data':168,174 'this.account.error':175 'this.accountids':596 'this.afterdelete':356 'this.afterinsert':348 'this.afterundelete':360 'this.afterupdate':352 'this.authenticate':1018,1051,1196 'this.beforedelete':344 'this.beforeinsert':336 'this.beforeupdate':340 'this.clientid':931 'this.closejob':1226 'this.createbulkjob':1204 'this.error':155,160 'this.escapecsv':1469 'this.getfailedresults':1396 'this.handleerror':1039,1075 'this.instanceurl':1004,1023,1056,1260,1300,1324,1370,1434 'this.loginurl':935,959 'this.newaccounts':417 'this.newmap':423 'this.oldaccounts':420 'this.oldmap':428 'this.parsecsv':1447 'this.privatekeypath':923 'this.recordstocsv':1296 'this.relatedrecords':153,162 'this.retrycount':598 'this.template.queryselector':2298 'this.tokenexpiry':917,1007 'this.uploadjobdata':1215 'this.username':933 'this.waitforjobcompletion':1235 'throughout':1537 'throw':689,990,1099,1106,1246,1403,1417 'timeout':1422 'titl':187 'today':1135 'token':907,954,1003,1092 'tokenexpiri':885 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agent-skills' 'topic-ai-agents' 'topic-ai-coding' 'topic-ai-workflows' 'topic-antigravity' 'topic-antigravity-skills' 'topic-claude-code' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-codex-skills' 'trace':798 'track':2305,2312,2320,2331 'transact':260,564,2030 'treat':2507 'tree':1661 'tri':605,1207 'trigger':25,246,251,273,532,817,2062,2074,2173,2177,2184,2358 'trigger.new':419 'trigger.newmap':427 'trigger.old':422 'trigger.oldmap':432 'trigger.operationtype':323,332 'triggerhandl':300,398 'triggerhandler.cls':294 'true':191,195,201,212,1560,1567,1577,2188 'type':553,662,790,966,972,979,1034,1067,1270,1312,1335,1786 'ubuntu':1895 'ubuntu-latest':1894 'ui':243,2374 'undefin':156,163,1487 'undelet':290,359 'unlik':1540 'unlock':1769 'updat':280,286,339,351,447,636,744,2345 'updaterelatedopportun':638,697 'upload':1210 'uploadcomplet':1340 'uploadjobdata':1285 'url':1006,1918,1925,1928 'urlsearchparam':970 'urn':973 'usag':1113 'use':50,261,541,826,849,1151,1898,2141,2185,2210,2261,2270,2284,2297,2430,2492 'user':845,2359,2371,2382,2394,2407,2417,2431,2436,2441,2446,2451,2458,2463,2469,2474,2479,2485 'user-fac':844 'usernam':893,1122 'utf8':924 'v4':1900 'valid':910,1890,2112,2517 'valu':1480,1484,1486,1492 'variabl':2212 'ver':1581 'version':1799,1803,1822,1826,1843,2105 'versionnam':1580 'versionnumb':1583 'via':555 'virtual':298,366,370,374,378,382,386,390 'visualforc':2473 'void':310,367,371,375,379,383,387,391,435,441,450,483,515,601,646,696,747 'volum':1506 'vulner':2090,2204 'wait':1696,1811,1870,1991 'waitforjobcomplet':1343 'warehous':2397,2406 'warn':2220,2239,2282,2338 'web':21,45,840,2456 'wire':48,51,64,78,121,130,137,144,2036,2332,2340,2349 'wiredrecord':148 'without':2228,2304,2333,2350 'work':509 'workflow':1166,1596,1720","prices":[{"id":"d3aff212-703a-45b4-a1e8-afc1aad19d35","listingId":"840fe6b6-95b6-4a97-94dc-762e66e53747","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T21:43:49.349Z"}],"sources":[{"listingId":"840fe6b6-95b6-4a97-94dc-762e66e53747","source":"github","sourceId":"sickn33/antigravity-awesome-skills/salesforce-development","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/salesforce-development","isPrimary":false,"firstSeenAt":"2026-04-18T21:43:49.349Z","lastSeenAt":"2026-04-22T18:52:10.048Z"}],"details":{"listingId":"840fe6b6-95b6-4a97-94dc-762e66e53747","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"salesforce-development","github":{"repo":"sickn33/antigravity-awesome-skills","stars":34583,"topics":["agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity","antigravity-skills","claude-code","claude-code-skills","codex-cli","codex-skills","cursor","cursor-skills","developer-tools","gemini-cli","gemini-skills","kiro","mcp","skill-library"],"license":"mit","html_url":"https://github.com/sickn33/antigravity-awesome-skills","pushed_at":"2026-04-22T06:40:00Z","description":"Installable GitHub library of 1,400+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.","skill_md_sha":"2fb65b7b6cc1d42ed1603ff9575da15210712670","skill_md_path":"skills/salesforce-development/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/salesforce-development"},"layout":"multi","source":"github","category":"antigravity-awesome-skills","frontmatter":{"name":"salesforce-development","description":"Expert patterns for Salesforce platform development including"},"skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/salesforce-development"},"updatedAt":"2026-04-22T18:52:10.048Z"}}