LLM Mistakes in Apex & LWC: Salesforce Code Generation Rules

LLM mistakes are very common but at the same time LLMs are transforming how we write Salesforce code. Vibe Coding Tools use these LLMs under the hood to generate code suggestions for APEX and LWC.

But here’s what most developers don’t realize: LLMs are making mistakes and don’t understand Salesforce’s unique constraints—governor limits, LWC template restrictions, or Agentforce-specific patterns. They generate code that compiles in your IDE but breaks in production.

After months of prompting LLMs for Apex, LWC, and Flow code—and debugging the failures—I’ve documented the common mistakes LLMs make when generating Salesforce code.

The Solution: Use this guide as a ruleset for your LLM prompts. Add these rules to your system prompts, custom instructions, or paste them directly when asking LLMs to generate Salesforce code. This will dramatically improve the quality of generated code.


Why LLMs Struggle with Salesforce Code

The Core Problem

When you prompt an LLM to generate Apex, LWC, or Flow code, it draws from general programming patterns. But Salesforce isn’t a general platform—it’s a highly opinionated ecosystem with:

  • Governor Limits that don’t exist in other platforms
  • LWC Template Restrictions that break standard JavaScript patterns
  • Metadata Dependencies that require specific deployment ordering
  • Platform-Specific APIs that differ from common patterns

What LLMs Actually Generate

Here’s a typical scenario when prompting an LLM:

Prompt: "Generate an Apex class that fetches contacts for an account"

LLM Output:
public class ContactFetcher {
    public List<Contact> getContacts(Id accountId) {
        Account acc = [SELECT Id FROM Account WHERE Id = :accountId];
        return acc.Contacts;  // 💥 BOOM! SObject row was retrieved without querying Contacts
    }
}

The code looks clean. It compiles in your IDE. But it throws a runtime exception because the relationship wasn’t queried. The LLM doesn’t know about Salesforce’s lazy-loading behavior.

How Often LLMs Make These Mistakes

After analyzing hundreds of LLM-generated Salesforce code snippets, I categorized the common mistake patterns:

Category Failure Count Impact
SOQL/Field Errors 32% Runtime crashes
LWC Template Violations 28% Compilation failures
Permission/Security Gaps 18% Deployment failures
API/Integration Issues 12% 404s and 401s
Async Context Bugs 10% Data loss

How I Discovered These LLM Patterns

🔬 The Research Process

This wasn’t a theoretical exercise. Every pattern came from actual LLM outputs I encountered while building various usecase on Salesforce. So i have good amount of vibe coding chats stored locally and these patterns have been identified by LLM itself by going again through those chats.

📊 Data Collection Method

For each LLM mistake, I documented:

  • The exact error message
  • The LLM-generated code that caused it
  • The correct Salesforce pattern
  • Why the LLM gets it wrong

🎯 Organization Strategy

Mistakes are organized by where they’ll break your code:

Section Color Code Meaning
Compilation Errors 🔴 Code won’t compile
LWC Template Errors 🔴 Component won’t build
Runtime Errors 🔴 Crashes in production
Deployment Failures 🔴 Won’t deploy to org
API/Integration Failures 🔴 404s and 401s
Agentforce Failures 🔴 Agent actions break

🔴 COMMON LLM MISTAKES (Will Break Your Code)

These are the patterns LLMs repeatedly generate that cause compilation failures, runtime errors, or deployment crashes.


1.1 Compilation Errors

Rule 1: SOQL Query Field Coverage

Error: SObject row was retrieved via SOQL without querying field

This is the #1 mistake AI makes. It queries an object but forgets to include fields that are accessed later.

// ❌ WRONG - AI often generates this
Account acc = [SELECT Id FROM Account WHERE Id = :accountId];
String name = acc.Name;  // 💥 ERROR! Name not queried

// ✅ RIGHT - All accessed fields must be in SELECT
Account acc = [SELECT Id, Name, Industry FROM Account WHERE Id = :accountId];
String name = acc.Name;

Why AI fails: LLMs see acc.Name as valid property access (which it is syntactically), but don’t understand Salesforce’s runtime field verification.


Rule 2: Relationship Fields in SOQL

Error: System.SObjectException: SObject row was retrieved via SOQL without querying the requested field

// ❌ WRONG - Parent field not queried
Contact c = [SELECT Id, Name FROM Contact LIMIT 1];
String accountName = c.Account.Name;  // 💥 ERROR!

// ✅ RIGHT - Use dot notation in SELECT
Contact c = [SELECT Id, Name, Account.Name FROM Contact LIMIT 1];
String accountName = c.Account.Name;

AI Pattern to Watch: When AI generates code that traverses relationships like record.Parent.Field, always verify the SELECT clause includes Parent.Field.


Rule 3: Datetime Methods – No addMilliseconds()

Error: Method does not exist: addMilliseconds

AI often suggests Java/JavaScript patterns that don’t exist in Apex:

// ❌ WRONG - Method does not exist in Salesforce Apex
Datetime warning = Datetime.now().addMilliseconds(5000);  // 💥 COMPILE ERROR!

// ✅ RIGHT - Use existing methods
Datetime warning = Datetime.now().addSeconds(5);

Available Datetime methods:

  • addDays()
  • addHours()
  • addMinutes()
  • addSeconds()
  • addMonths()
  • addYears()

NOT available: addMilliseconds(), addWeeks()


Rule 4: Static vs Instance Methods

Error: Method does not exist or incorrect signature

AI confuses static and instance method patterns:

// ❌ WRONG - Calling instance method as static
IConnector connector = ConnectorFactory.createConnector(type, config);  // 💥 ERROR!

// ✅ RIGHT - Instantiate first for instance methods
ConnectorFactory factory = new ConnectorFactory();
IConnector connector = factory.createConnector(type, config);

Rule 5: Non-Existent Types

Error: Invalid type: StringBuffer

AI loves to use Java classes that don’t exist in Apex:

// ❌ WRONG - StringBuffer doesn't exist in Apex
StringBuffer buffer = new StringBuffer();  // 💥 COMPILE ERROR!

// ✅ RIGHT - Use String or List<String>
List<String> parts = new List<String>{'part1', 'part2'};
String result = String.join(parts, '');

Common Java types NOT in Apex:

  • StringBuffer / StringBuilder → Use String or List<String>
  • HashMap → Use Map<K,V>
  • ArrayList → Use List<T>
  • Optional → Use null checks

1.2 LWC Compilation Errors

This is where AI fails most frequently. LWC templates have strict rules that don’t exist in React, Vue, or Angular.

Rule 11: LWC – No Inline Expressions

Error: LWC1083: Invalid expression

<!-- ❌ WRONG - Inline expressions NOT allowed in LWC -->
<template>
    <p>{record.Name + ' - ' + record.Industry}</p>
    <p>{items.length > 0}</p>
</template>

<!-- ✅ RIGHT - Use JavaScript getters -->
<template>
    <p>{formattedName}</p>
    <p>{hasItems}</p>
</template>
// In your .js file
get formattedName() {
    return this.record ? `${this.record.Name} - ${this.record.Industry}` : '';
}

get hasItems() {
    return this.items && this.items.length > 0;
}

Why this matters: AI trained on React/Vue generates expressions like {count + 1} or {isActive ? 'Yes' : 'No'} which are perfectly valid there but cause LWC compilation failures.


Rule 12: LWC Templates – No Object Literals

Error: LWC1535/LWC1083 Compilation Error

<!-- ❌ WRONG - Object literals NOT allowed -->
<c-component headers={{"Authorization": "Bearer token"}}></c-component>

<!-- ✅ RIGHT - Define in JavaScript -->
<c-component headers={httpHeaders}></c-component>
get httpHeaders() {
    return { "Authorization": "Bearer " + this.token };
}

Rule 13: LWC Template – No Ternary Operators

Error: LWC1060: Template expression doesn't allow ConditionalExpression

<!-- ❌ WRONG - Ternary not allowed in LWC templates -->
<span>{count === 1 ? 'item' : 'items'}</span>

<!-- ✅ RIGHT - Use getter -->
<span>{itemLabel}</span>
get itemLabel() {
    return this.count === 1 ? 'item' : 'items';
}

Rule 14: LWC Template – No Binary Expressions

Error: LWC1060: Template expression doesn't allow BinaryExpression

<!-- ❌ WRONG - Arithmetic not allowed -->
<span>Row {index + 1}</span>

<!-- ✅ RIGHT - Compute in JavaScript -->
<span>Row {rowNumber}</span>
get rowNumber() {
    return this.index + 1;
}

Rule 16: LWC Decorators – Must Import from lwc

Error: LWC1102: Invalid decorator usage

AI sometimes forgets the import statement:

// ❌ WRONG - Decorators not imported
export default class MyComponent extends LightningElement {
    @api value;  // 💥 Error!
}

// ✅ RIGHT - Always import decorators
import { LightningElement, api, track, wire } from 'lwc';

export default class MyComponent extends LightningElement {
    @api value;
}

1.3 Runtime Errors

Rule 18: Null Checks Before Field Access

Error: System.NullPointerException: Attempt to de-reference a null object

This is the most common runtime error in AI-generated Apex:

// ❌ WRONG - No null check
String name = account.Name.toUpperCase();  // 💥 NullPointerException if Name is null

// ✅ RIGHT - Null-safe access
String name = String.isNotBlank(account.Name) ? account.Name.toUpperCase() : '';

Rule 20: Map.containsKey() Before Get

Error: Attempt to de-reference a null object

// ❌ WRONG - Key may not exist
String apiKey = (String) config.get('apiKey');  // May return null!

// ✅ RIGHT - Check before access
if (config.containsKey('apiKey')) {
    String apiKey = (String) config.get('apiKey');
}

Rule 21: Recursive Trigger Prevention

Error: Maximum stack depth reached

AI-generated triggers often lack recursion guards:

// ❌ WRONG - Can cause infinite loop
trigger AccountTrigger on Account (after update) {
    update accountsToUpdate;  // 💥 Triggers itself again!
}

// ✅ RIGHT - Use static flag
public class TriggerHandler {
    private static Boolean isExecuting = false;
    
    public static void handle(List<Account> accounts) {
        if (isExecuting) return;
        isExecuting = true;
        // Process...
        isExecuting = false;
    }
}

1.4 Deployment Failures

Rule 24: Permission Set – Field Permissions

Error: Insufficient access / deployment failures

AI generates fields but forgets permission sets:

<!-- ADD THIS for every new custom field -->
<fieldPermissions>
    <editable>true</editable>
    <field>Object__c.New_Field__c</field>
    <readable>true</readable>
</fieldPermissions>

Rule 25: Permission Set – Apex Class Permissions

Error: Insufficient access

<!-- ADD THIS for every new Apex class -->
<classAccesses>
    <apexClass>NewClassName</apexClass>
    <enabled>true</enabled>
</classAccesses>

Rule 30: Package.xml Metadata Order

Error: Dependency failures

AI generates package.xml without considering dependency order:

Correct Deployment Order:

  1. Custom Objects
  2. Custom Fields
  3. Apex Classes
  4. Layouts
  5. Permission Sets

1.5 API/Integration Failures

Rule 33: API Endpoints – Same-Org vs Cross-Org

Error: 404 URL No Longer Exists

AI often confuses which base URL to use:

// ❌ WRONG - Using org domain for Agent API
String endpoint = 'https://myorg.my.salesforce.com/einstein/ai-agent/v1/...';  // 💥 404!

// ✅ RIGHT - Agent API uses api.salesforce.com
String endpoint = 'https://api.salesforce.com/einstein/ai-agent/v1/...';

Rule 35: API Key Authentication Patterns

Different providers require different header formats. AI often uses the wrong one:

Provider Header Format
OpenAI Authorization: Bearer {key}
Anthropic x-api-key: {key}
Azure OpenAI api-key: {key}
Salesforce Authorization: Bearer {access_token}

1.6 Agentforce/Flow Specific Failures

Rule 37: Unable to Find Apex Action Method

Error: Unable to find Apex action method

For LWC to call Apex methods, all these must be true:

// ✅ REQUIRED checklist:
// - Method exists in Apex class
// - Method has @AuraEnabled decorator
// - Method is public static
// - Class is deployed to org

@AuraEnabled
public static String myMethod() {
    return 'Hello';
}

Rule 38: InvocableVariable Type Restrictions

Error: InvocableVariable fields do not support type

AI generates custom wrapper classes for Flow, but Flow has type restrictions:

// ❌ WRONG - Custom class not supported
@InvocableVariable
public List<AccountList> accounts;  // 💥 Invalid!

// ✅ RIGHT - Use standard types only
@InvocableVariable
public List<Account> accounts;  // sObject types work

Rule 39: InvocableMethod Return Type for Agentforce

Error: Agentforce can’t parse return value

For Agentforce to work with your Apex actions:

// ✅ REQUIRED for Agentforce
@JsonAccess(serializable='always' deserializable='always')
public class AccountData {
    @AuraEnabled public String name;
    @AuraEnabled public String industry;
}

Rule 41: Agentforce ValueChange Event

Error: Agentforce doesn’t receive user input

Custom LWC editors in Agentforce must dispatch valuechange:

// ✅ REQUIRED - Dispatch valuechange event
handleSelectionChange(event) {
    this._value = event.detail.value;
    this.dispatchEvent(new CustomEvent('valuechange', {
        detail: { value: this._value }
    }));
}

ERROR MESSAGE QUICK LOOKUP

Error Rule Quick Fix
SObject row was retrieved via SOQL without querying field 1, 2 Add field to SELECT
LWC1083: Invalid expression 11, 12 Use JS getter
LWC1060: Template expression doesn't allow 13, 14 Move to JS getter
Attempt to de-reference a null object 18, 19, 20 Add null checks
Maximum stack depth reached 21 Add recursion flag
Insufficient access 24, 25 Add to permission set
404 URL No Longer Exists 33 Check API endpoint
401 Unauthorized 36 Check credentials
Unable to find Apex action method 37 Add @AuraEnabled
Method does not exist: addMilliseconds 3 Use addSeconds()
Invalid type 7, 9 Use Apex classes only
LWC1102: Invalid decorator usage 16 Import from ‘lwc’
MALFORMED_ID 23 Use actual resolved ID

Conclusion

LLMs are powerful code generation tools, but they lack understanding of Salesforce’s unique constraints. By recognizing these common mistake patterns, you can review LLM-generated code more effectively and catch errors before they reach production.

Key Patterns LLMs Get Wrong:

  1. LWC Templates ≠ React/Vue – LLMs apply JSX patterns that don’t work in LWC
  2. SOQL Field Coverage – LLMs don’t understand Salesforce’s lazy-loading model
  3. Null Safety – LLMs skip defensive coding patterns Salesforce requires
  4. Metadata Dependencies – LLMs forget permission sets and deployment order
  5. Agentforce Specifics – LLMs miss JsonAccess, valuechange, and target requirements

How to Use This Ruleset with LLM Providers

The best way to prevent these mistakes is to give the LLM these rules upfront. Here’s how to use this ruleset with different providers:

Add to your System Prompt or paste at the start of your conversation:

When generating Salesforce Apex or LWC code, follow these rules:
- Query ALL fields in SOQL that will be accessed later, including relationship fields (e.g., Account.Name)
- No inline expressions, ternary operators, or arithmetic in LWC templates - use JavaScript getters
- Always import decorators (@api, @track, @wire) from 'lwc'
- Add null checks before accessing object properties
- Use Map.containsKey() before Map.get()
- Include @AuraEnabled for LWC-callable methods
- Use @JsonAccess annotation for Agentforce return types
- Don't use Java types like StringBuffer, HashMap, ArrayList - use Apex equivalents

IDE Tools

These tools let you configure custom rules or system prompts. Add the Salesforce ruleset to your workspace settings so every code suggestion follows these patterns.


Quick Copy: Salesforce LLM Ruleset

Copy this condensed ruleset and add it to your LLM’s instructions:

SALESFORCE CODE GENERATION RULES:

[SOQL]
- Query ALL fields that will be accessed, including relationship fields (Parent.Field)
- No filtering on LongTextArea or encrypted fields

[LWC TEMPLATES]
- NO inline expressions: {a + b}, {condition ? x : y}, {items.length}
- Use JavaScript getters for all computed values
- Import decorators: import { LightningElement, api, track, wire } from 'lwc'

[APEX]
- Use 'with sharing' by default
- Add null checks before property access
- Use Map.containsKey() before Map.get()
- No Java types: StringBuffer→String, HashMap→Map, ArrayList→List
- No addMilliseconds() - use addSeconds()
- Add recursion guards for triggers

[AURA/LWC CALLABLE]
- Methods need: @AuraEnabled, public static
- Wrap exceptions in AuraHandledException

[AGENTFORCE/FLOW]
- Use @JsonAccess(serializable='always' deserializable='always') for return types
- InvocableVariable only supports primitives and sObjects
- Dispatch 'valuechange' event from custom editors

[DEPLOYMENT]
- Add fieldPermissions and classAccesses to permission sets
- Deploy order: Objects → Fields → Classes → Layouts → Permissions

Additional Resources


Do you need help?

Are you using LLMs for Salesforce development and running into issues? Want a code review of your LLM-generated Apex or LWC? Book a dedicated 1:1 session completely free.

Book Meeting For Free


One comment

Leave a Reply