Designs Salesforce custom objects, relationships (lookup vs master-detail), Custom Metadata, and sharing models for scalable org architecture. Guides field types and standard object extensions.
npx claudepluginhub jiten-singh-shahi/salesforce-claude-code --plugin salesforce-claude-codeThis skill uses the workspace's default tool permissions.
@../_reference/DATA_MODELING.md
Provides reference architecture for Salesforce integrations using jsforce, SFDX, and event-driven patterns in Node.js apps and Apex metadata projects. Use for designing sync logic, project layouts, or standards.
Guides Salesforce Flow design and review: selects flow types (Record-Triggered, Screen, etc.), validates bulk safety (no DML/Get Records in loops), ensures fault handling and automation density checks.
Sets up Dataverse tables, columns, and relationships for Power Pages sites using OData API from ER diagrams or AI analysis after user approval.
Share bugs, ideas, or general feedback.
@../_reference/DATA_MODELING.md
| Business Need | Use Standard Object | Not Custom |
|---|---|---|
| Customer companies | Account | Company__c |
| Individual contacts | Contact | Person__c |
| Sales deals | Opportunity | Deal__c |
| Support tickets | Case | Ticket__c |
| Events/meetings | Event | Meeting__c |
| Tasks/to-dos | Task | Todo__c |
| Products/pricing | Product2, PricebookEntry | Product__c |
| Orders | Order, OrderItem | PurchaseOrder__c |
Standard objects come with built-in reports, process automations, and integrations.
API Name: ProjectTask__c (PascalCase + __c)
Label: Project Task (human-readable)
Plural: Project Tasks
Relationship Name: ProjectTasks (plural for child relationship)
| Relationship | Cascade Delete | Roll-Up Summary | Required | Sharing Inherited |
|---|---|---|---|---|
| Lookup | No (configurable) | No | No | No |
| Master-Detail | Yes | Yes | Yes | Yes |
| Many-to-Many (Junction) | Both sides | From junction | Both | From primary master |
| Hierarchical | No | No | No | No |
| External Lookup | No | No | No | No |
*Master-Detail can be reparented if "Allow Reparenting" is enabled.
<!-- Master-Detail field metadata -->
<fields>
<fullName>Project__c</fullName>
<label>Project</label>
<type>MasterDetail</type>
<referenceTo>Project__c</referenceTo>
<relationshipLabel>Project Tasks</relationshipLabel>
<relationshipName>ProjectTasks</relationshipName>
<relationshipOrder>0</relationshipOrder>
<reparentableMasterDetail>false</reparentableMasterDetail>
</fields>
| Field Type | Use When | Avoid When |
|---|---|---|
| Text (255) | Short single-line text | Long descriptions |
| Long Text Area | Up to 131,072 chars | Need to filter/search on it |
| Rich Text Area | HTML-formatted content | Need to query/filter by content |
| Number | Integers, no currency | Financial values (use Currency) |
| Currency | Monetary values | Non-financial numbers |
| Date | Date without time | Need time zone info |
| DateTime | Timestamps, audit trails | Simple date records |
| Checkbox | Boolean yes/no | Optional boolean (use Picklist) |
| Picklist (single) | Controlled vocabulary | Many values (use Lookup) |
| Picklist (multi) | Multiple selections | Filtering/reporting (anti-pattern) |
| Formula | Calculated, read-only | Values needing DML update |
| Roll-Up Summary | Aggregate child data | 25 per object (default limit) |
| External ID | Upsert key from external system | - |
// Limited SOQL support — can use INCLUDES/EXCLUDES but not = or IN
List<Case> cases = [
SELECT Id FROM Case
WHERE Tag_List__c INCLUDES ('Billing', 'Technical')
];
// Cannot use in GROUP BY, ORDER BY, or most aggregates
// Consider Lookup to a Tags junction object for complex tagging
| Feature | Custom Metadata | Custom Settings | Custom Labels |
|---|---|---|---|
| Deployable | Yes | No (hierarchy)/Yes (list) | Yes |
| Per-user/profile values | No | Yes (hierarchy) | No |
| Governor limit on reads | No (cached) | Yes (SOQL equivalent) | No |
| Best for | Config deployed with code | User/profile-specific settings | Translatable strings |
// Custom Metadata — no SOQL limits, deployable
String endpoint = Service_Config__mdt.getInstance('Production').Endpoint_URL__c;
// Custom Setting — profile-specific
Boolean isEnabled = Integration_Settings__c.getInstance().Is_Enabled__c;
// Custom Label — translatable
String welcomeMsg = System.Label.Welcome_Message;
Id caseRecordTypeId = Schema.SObjectType.Case.getRecordTypeInfosByDeveloperName()
.get('Internal_Support').getRecordTypeId();
List<Case> internalCases = [
SELECT Id, Subject FROM Case
WHERE RecordTypeId = :caseRecordTypeId WITH USER_MODE
];
| OWD Setting | Other Users | Best For |
|---|---|---|
| Public Read/Write | Read + Write | Reference/config data |
| Public Read Only | Read only | Products, pricebooks |
| Private | None | Accounts, Opportunities |
| Controlled by Parent | Inherits | Master-Detail children |
Start with Private OWD for sensitive objects and open up with sharing rules.
public with sharing class ProjectSharingService {
public static void shareProjectWithUser(Id projectId, Id userId, String accessLevel) {
Project__Share shareRecord = new Project__Share(
ParentId = projectId,
UserOrGroupId = userId,
AccessLevel = accessLevel,
RowCause = Schema.Project__Share.RowCause.Manual
);
Database.SaveResult result = Database.insert(shareRecord, false);
if (!result.isSuccess() &&
result.getErrors()[0].getStatusCode() != StatusCode.FIELD_FILTER_VALIDATION_EXCEPTION) {
throw new SharingException('Failed to share: ' + result.getErrors()[0].getMessage());
}
}
public class SharingException extends Exception {}
}
Objects with >100,000 records require special attention.
Schema design:
// Good — uses indexed fields, selective
List<Order__c> orders = [
SELECT Id, Status__c FROM Order__c
WHERE AccountId = :accountId
AND CreatedDate >= :thirtyDaysAgo
LIMIT 200
];
Archiving: Move old records to BigObjects or external archive. Use batch jobs for archive-and-delete.
Account <-- AccountContactRelation --> Contact
+ Role (picklist)
+ IsPrimary (checkbox)
+ StartDate (date)
AccountContactRelation (standard) before creating custom junctions for Account-Contact| Adapter | Use When |
|---|---|
| OData 2.0/4.0 | External REST API with OData support |
| Custom Adapter | Proprietary API or database |
| Cross-Org | Another Salesforce org |
External Objects have no triggers, Flows, or Validation Rules. Use Apex callouts for write operations.
| Anti-Pattern | Fix |
|---|---|
| Polymorphic lookup abuse | Use explicit lookup fields per related object |
| Over-normalization | Flatten into fields unless multiple addresses per record |
| Too many custom fields (800 limit) | Split into related child objects |
| Circular Master-Detail | Break the circle with a Lookup on one side |
| Text instead of Lookup | Use Lookup fields for referential integrity |
| Ignoring LDV on 100K+ objects | Request custom indexes, use skinny tables |
sf-architect — For interactive, in-depth guidancesf-apex-constraints — Governor limits and Apex safety rules