Automated date field stamping for Salesforce - Track when picklist values are entering and exiting with precision
DateBuddy is a powerful Salesforce application that automatically stamps date fields when picklist values change. It provides comprehensive tracking of both when values are "Entering" (changed TO a target value) and "Exiting" (changed FROM a target value), enabling precise audit trails and business process monitoring.
The application uses Custom Metadata Types for configuration, making it highly flexible and admin-friendly without requiring code changes.
- 🎯 Automatic Date Stamping: Automatically populate date fields when picklist values change
- 🔄 Bidirectional Tracking: Track both "Entering" and "Exiting" states for complete audit trails
- ⚙️ Metadata-Driven Configuration: Easy setup through Custom Metadata Types (no code changes required)
- 🚀 Dynamic Trigger Deployment: Automatically generate and deploy triggers for configured objects
- 📊 Lightning Web Component UI: User-friendly interface for configuration and deployment
- 🔧 Flexible Direction Mapping: Support for "Entering"/"Exiting" and "In"/"Out" direction values
- 🔗 Flow-Compatible: Includes invocable action for Flow Builder integration
- 📦 2GP Package: Easy installation via Second Generation Package
-
Install the DateBuddy package using the latest version:
https://login.salesforce.com/packaging/installPackage.apexp?p0=04tWs000000aWuDIAU -
Grant appropriate permissions to users who will configure DateBuddy:
- Custom Metadata Types access
- Modify All Data (for trigger deployment)
-
Navigate to the DateBuddy app in the App Launcher
# Install the package
sf package install --package 04tWs000000aWuDIAU --target-org YOUR_ORG_ALIAS --wait 10
# Verify installation
sf package installed list --target-org YOUR_ORG_ALIAS# Clone the repository
git clone [repository-url]
cd DateBuddy
# Deploy to your org
sf project deploy start --target-org [your-org-alias]
# Run tests
sf apex run test --target-org [your-org-alias] --code-coverageDateBuddy uses Custom Metadata Type records (Date_Stamp_Mapping__mdt) for configuration:
-
Navigate to Setup → Custom Metadata Types → Date Stamp Mapping → Manage Records
-
Create a new record with the following fields:
| Field | Description | Required |
|---|---|---|
Object_API_Name__c |
Target object API name (e.g., Account, Custom_Object__c) |
✅ |
Picklist_API_Name__c |
Picklist field to monitor (e.g., Status__c) |
✅ |
Picklist_Value__c |
Specific picklist value to track (e.g., Active) |
✅ |
Date_Field_API_Name__c |
Entry date field API name | ✅* |
Exit_Date_Field_API_Name__c |
Exit date field API name | ❌ |
Direction__c |
Direction when only one date field is used (Entering/Exiting/In/Out) |
❌ |
* Required unless Exit_Date_Field_API_Name__c is populated
Understanding how DateBuddy maps your configuration to date stamping behavior is crucial for proper setup. The system uses a sophisticated logic to determine when to stamp entry vs. exit dates based on which fields you populate.
Performance Note: The Lightning Web Component (LWC) now processes direction determination client-side for improved performance, reducing server-side computation during record processing.
| Field Configuration | Behavior | Direction Field Effect |
|---|---|---|
Both Date_Field_API_Name__c AND Exit_Date_Field_API_Name__c populated |
Entry field tracks ENTRY events Exit field tracks EXIT events |
IGNORED - Two fields = bidirectional tracking |
ONLY Date_Field_API_Name__c populated |
Default: Tracks ENTRY events Exception: If Direction = "Exiting" or "Out" → Tracks EXIT events |
RESPECTED - Determines tracking direction |
ONLY Exit_Date_Field_API_Name__c populated |
Always tracks EXIT events | IGNORED - Exit field ALWAYS maps to exiting behavior |
When both date fields are specified, DateBuddy automatically provides complete entry/exit tracking:
Date_Field_API_Name__c: Entry_Date__c → Stamps when value ENTERS
Exit_Date_Field_API_Name__c: Exit_Date__c → Stamps when value EXITS
Direction__c: [ANY VALUE] → IGNORED
Result: Full lifecycle tracking with separate timestamps for entry and exit events.
When only the entry field is populated, DateBuddy defaults to entry tracking:
Date_Field_API_Name__c: Status_Date__c → Stamps when value ENTERS
Exit_Date_Field_API_Name__c: [BLANK] → Not used
Direction__c: [BLANK/NULL/Entering/In] → Entry tracking (default)
Result: Only entry events are tracked.
Use the Direction field to override default behavior for exit tracking:
Date_Field_API_Name__c: Status_Date__c → Stamps when value EXITS
Exit_Date_Field_API_Name__c: [BLANK] → Not used
Direction__c: Exiting OR Out → Forces exit tracking
Result: Only exit events are tracked using the entry field.
When only the exit field is populated, it always tracks exits:
Date_Field_API_Name__c: [BLANK] → Not used
Exit_Date_Field_API_Name__c: Exit_Date__c → Stamps when value EXITS
Direction__c: [ANY VALUE] → IGNORED
Result: Only exit events are tracked.
| Direction Value | Synonym | Behavior |
|---|---|---|
Entering |
In |
Forces entry tracking when only one field is populated |
Exiting |
Out |
Forces exit tracking when only one field is populated |
null/blank |
- | Uses default behavior based on field configuration |
Use Case: Track both when Opportunities enter and exit "Negotiation" stage
Label: Opportunity Negotiation Lifecycle
Object_API_Name__c: Opportunity
Picklist_API_Name__c: StageName
Picklist_Value__c: Negotiation
Date_Field_API_Name__c: Negotiation_Entry_Date__c
Exit_Date_Field_API_Name__c: Negotiation_Exit_Date__c
Direction__c: [Leave blank - ignored for bidirectional]
Behavior:
- When StageName changes TO "Negotiation" →
Negotiation_Entry_Date__c= TODAY - When StageName changes FROM "Negotiation" →
Negotiation_Exit_Date__c= TODAY
Use Case: Track when Opportunities reach "Closed Won" (never need exit date)
Label: Opportunity Closed Won Entry
Object_API_Name__c: Opportunity
Picklist_API_Name__c: StageName
Picklist_Value__c: Closed Won
Date_Field_API_Name__c: Closed_Won_Date__c
Exit_Date_Field_API_Name__c: [Leave blank]
Direction__c: Entering (or leave blank - default behavior)
Behavior:
- When StageName changes TO "Closed Won" →
Closed_Won_Date__c= TODAY - When StageName changes FROM "Closed Won" → Nothing happens
Use Case: Track when Accounts leave "Qualification" status (using single field for exit)
Label: Account Qualification Exit
Object_API_Name__c: Account
Picklist_API_Name__c: Account_Status__c
Picklist_Value__c: Qualification
Date_Field_API_Name__c: Qualification_Left_Date__c
Exit_Date_Field_API_Name__c: [Leave blank]
Direction__c: Exiting
Behavior:
- When Account_Status__c changes TO "Qualification" → Nothing happens
- When Account_Status__c changes FROM "Qualification" →
Qualification_Left_Date__c= TODAY
Use Case: Track different stages of the same opportunity with separate date fields
Configuration Set 1:
Label: Opportunity Prospecting Entry
Object_API_Name__c: Opportunity
Picklist_API_Name__c: StageName
Picklist_Value__c: Prospecting
Date_Field_API_Name__c: Prospecting_Date__c
Configuration Set 2:
Label: Opportunity Qualification Entry
Object_API_Name__c: Opportunity
Picklist_API_Name__c: StageName
Picklist_Value__c: Qualification
Date_Field_API_Name__c: Qualification_Date__c
Configuration Set 3:
Label: Opportunity Closed Won Entry
Object_API_Name__c: Opportunity
Picklist_API_Name__c: StageName
Picklist_Value__c: Closed Won
Date_Field_API_Name__c: Closed_Won_Date__c
Behavior: Each stage transition stamps its respective date field, building a complete timeline.
Use Case: Service Request lifecycle with bidirectional tracking for "In Progress" and exit-only for resolution
Configuration Set 1:
Label: Service Request In Progress Lifecycle
Object_API_Name__c: Service_Request__c
Picklist_API_Name__c: Status__c
Picklist_Value__c: In Progress
Date_Field_API_Name__c: In_Progress_Start_Date__c
Exit_Date_Field_API_Name__c: In_Progress_End_Date__c
Configuration Set 2:
Label: Service Request Resolution
Object_API_Name__c: Service_Request__c
Picklist_API_Name__c: Status__c
Picklist_Value__c: Resolved
Date_Field_API_Name__c: Resolution_Date__c
Behavior:
- Status → "In Progress":
In_Progress_Start_Date__c= TODAY - Status away from "In Progress":
In_Progress_End_Date__c= TODAY - Status → "Resolved":
Resolution_Date__c= TODAY
Business Requirement: Track progression through opportunity stages with detailed timestamps.
Step-by-Step Setup:
-
Identify Fields Needed:
- Opportunity object has standard
StageNamefield - Create custom date fields for each stage you want to track
- Opportunity object has standard
-
Create Custom Date Fields (Setup → Object Manager → Opportunity):
Prospecting_Date__c (Date field) Qualification_Date__c (Date field) Needs_Analysis_Date__c (Date field) Proposal_Date__c (Date field) Closed_Won_Date__c (Date field) -
Configure DateBuddy Mappings (Setup → Custom Metadata Types → Date Stamp Mapping):
Record 1 - Prospecting Entry:
Label: Opportunity Prospecting Stage Entry Object_API_Name__c: Opportunity Picklist_API_Name__c: StageName Picklist_Value__c: Prospecting Date_Field_API_Name__c: Prospecting_Date__c Exit_Date_Field_API_Name__c: [blank] Direction__c: [blank - defaults to entering]Record 2 - Qualification Entry:
Label: Opportunity Qualification Stage Entry Object_API_Name__c: Opportunity Picklist_API_Name__c: StageName Picklist_Value__c: Qualification Date_Field_API_Name__c: Qualification_Date__cRecord 3 - Needs Analysis Entry:
Label: Opportunity Needs Analysis Stage Entry Object_API_Name__c: Opportunity Picklist_API_Name__c: StageName Picklist_Value__c: Needs Analysis Date_Field_API_Name__c: Needs_Analysis_Date__cRecord 4 - Proposal Entry:
Label: Opportunity Proposal Stage Entry Object_API_Name__c: Opportunity Picklist_API_Name__c: StageName Picklist_Value__c: Proposal/Price Quote Date_Field_API_Name__c: Proposal_Date__cRecord 5 - Closed Won Entry:
Label: Opportunity Closed Won Entry Object_API_Name__c: Opportunity Picklist_API_Name__c: StageName Picklist_Value__c: Closed Won Date_Field_API_Name__c: Closed_Won_Date__c -
Deploy Trigger:
- Open DateBuddy app
- Go to Trigger Deployer tab
- Select "Opportunity" from available objects
- Click "Deploy Triggers"
-
Test the Configuration:
- Create or edit an Opportunity
- Change StageName from "Prospecting" to "Qualification"
- Verify
Prospecting_Date__cwas stamped when it entered Prospecting - Verify
Qualification_Date__cwas stamped when it entered Qualification
Business Requirement: Track customer lifecycle with both entry and exit dates for "Active" status, plus resolution tracking.
Step-by-Step Setup:
-
Custom Object Setup (assuming Customer__c custom object):
Customer_Status__c (Picklist: New, Onboarding, Active, Inactive, Churned) Active_Start_Date__c (Date field) Active_End_Date__c (Date field) Churned_Date__c (Date field) -
DateBuddy Configuration:
Record 1 - Active Lifecycle (Bidirectional):
Label: Customer Active Status Lifecycle Object_API_Name__c: Customer__c Picklist_API_Name__c: Customer_Status__c Picklist_Value__c: Active Date_Field_API_Name__c: Active_Start_Date__c Exit_Date_Field_API_Name__c: Active_End_Date__c Direction__c: [blank - ignored for bidirectional]Record 2 - Churn Tracking (Entry Only):
Label: Customer Churn Entry Object_API_Name__c: Customer__c Picklist_API_Name__c: Customer_Status__c Picklist_Value__c: Churned Date_Field_API_Name__c: Churned_Date__c -
Expected Behavior:
- Customer Status → "Active":
Active_Start_Date__c= TODAY - Customer Status away from "Active":
Active_End_Date__c= TODAY - Customer Status → "Churned":
Churned_Date__c= TODAY
- Customer Status → "Active":
Symptoms:
- Configuration looks correct but date fields remain blank
- No errors visible to users
Diagnostic Steps:
-
Check Field Permissions:
Setup → Object Manager → [Object] → Fields & Relationships → [Date Field] → Set Field-Level SecurityEnsure the field is visible and editable for relevant profiles.
-
Verify Field API Names:
- Confirm the exact API name in Object Manager
- API names are case-sensitive and must include
__cfor custom fields - Standard fields like
StageNamedon't have__c
-
Check Picklist Values:
- Verify the exact picklist value spelling and capitalization
- Check for leading/trailing spaces in the configuration
-
Confirm Trigger Deployment:
- Navigate to Setup → Apex Triggers
- Look for trigger named
DateBuddyTrigger_[ObjectAPIName] - If missing, redeploy through DateBuddy interface
Common Fixes:
❌ Wrong: Date_Field_API_Name__c: myDateField
✅ Correct: Date_Field_API_Name__c: myDateField__c
❌ Wrong: Picklist_Value__c: closed won
✅ Correct: Picklist_Value__c: Closed Won
❌ Wrong: Object_API_Name__c: opportunities
✅ Correct: Object_API_Name__c: Opportunity
Symptoms:
- Dates are stamped but at the wrong time (exit when expected entry, or vice versa)
Diagnostic Steps:
- Review Configuration Logic (see Field Configuration Logic section above)
- Check Direction Field:
- If only one date field is populated, ensure Direction is set correctly
- Remember: Direction is ignored when both date fields are populated
Resolution Examples:
Problem: Want to track when Opportunity exits "Qualification" but date stamps when entering instead.
❌ Incorrect Configuration:
Date_Field_API_Name__c: Qualification_Date__c
Exit_Date_Field_API_Name__c: [blank]
Direction__c: [blank] ← This defaults to ENTERING tracking
✅ Correct Configuration:
Date_Field_API_Name__c: Qualification_Date__c
Exit_Date_Field_API_Name__c: [blank]
Direction__c: Exiting ← Forces EXIT tracking
Symptoms:
- Unexpected date stamps
- Dates being overwritten
- Performance issues
Diagnostic Steps:
-
Audit All Mappings for the object:
Setup → Custom Metadata Types → Date Stamp Mapping → Manage Records Filter by Object_API_Name__c -
Look for Overlapping Configurations:
- Same picklist value with different date fields
- Different Direction settings for same value/field combination
Resolution:
- Ensure each picklist value + direction combination maps to only one date field
- If you need multiple date stamps for the same event, use separate metadata records with different date fields
Example of Proper Multiple Mappings:
✅ Valid: Multiple values, different fields
Record 1: StageName = "Prospecting" → Prospecting_Date__c
Record 2: StageName = "Qualification" → Qualification_Date__c
✅ Valid: Same value, entry vs exit to different fields
Record 1: Status = "Active" → Active_Start_Date__c + Active_End_Date__c (bidirectional)
❌ Invalid: Same value, same direction, different fields
Record 1: StageName = "Prospecting" → Date_Field_1__c (Direction: Entering)
Record 2: StageName = "Prospecting" → Date_Field_2__c (Direction: Entering)
Symptoms:
- "Deployment failed" messages in DateBuddy interface
- Metadata API errors
Diagnostic Steps:
-
Check User Permissions:
- User needs "Modify All Data" or equivalent permissions
- User needs access to deploy metadata
-
Review Deployment Logs:
- Check debug logs for specific error messages
- Look for field permission or API name errors
-
Validate Object and Field Existence:
- Ensure target object exists and is accessible
- Confirm all referenced date fields exist
Common Deployment Errors and Fixes:
Error: "INVALID_FIELD: No such column 'Field_Name__c'"
✅ Fix: Verify the field exists and API name is correct
Error: "INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY"
✅ Fix: Grant field-level security access for the date fields
Error: "DUPLICATE_DEVELOPER_NAME"
✅ Fix: Previous trigger deployment may have failed partially
Go to Setup → Apex Triggers → Delete existing trigger → Redeploy
For advanced troubleshooting, enable debug logging:
- Setup → Debug Logs → New
- Select your user
- Set Apex Code level to FINEST
- Reproduce the issue
- Review logs for DateBuddyHandler entries
Look for log entries like:
DEBUG|DateBuddyHandler: Processing field change: Status__c from 'Old Value' to 'New Value'
DEBUG|DateBuddyHandler: Matched mapping for Opportunity.Status__c = 'Active'
DEBUG|DateBuddyHandler: Stamping entry date on field: Active_Date__c
After creating your metadata records:
- Open the DateBuddy Lightning App
- Navigate to the Trigger Deployer tab
- Select your target object from the available configurations
- Click "Deploy Triggers" to generate and deploy the necessary triggers
The system will automatically:
- Generate trigger code based on your metadata configuration
- Deploy triggers using the Salesforce Metadata API
- Validate the deployment and report status
DateBuddy maintains high code quality standards:
- Minimum Required Coverage: 90%
- Current Coverage: 90%+
- Test Classes:
DateBuddyHandlerTest- 96% coverageDateStampTriggerDeployerTest- 86% coverageDateBuddyDeployControllerTest- 90% coverageUpdateDateFieldActionTest- 90% coverageMetadataServiceTestRemoteSiteHelperTest
Run tests after any configuration changes:
sf apex run test --target-org [your-org] --code-coverageTrack when opportunities move through different stages:
- Qualification Date: When
StageNamebecomesQualified - Proposal Date: When
StageNamebecomesProposal - Closed Won Date: When
StageNamebecomesClosed Won
Monitor customer status changes:
- Onboarding Start: When
Customer_Status__cbecomesOnboarding - Active Date: When
Customer_Status__cbecomesActive - Churn Date: When
Customer_Status__cleavesActive
Maintain detailed audit trails:
- Approval Dates: Track when records are approved
- Review Cycles: Monitor when reviews are completed
- Status Changes: Log all critical status transitions
To track when a Contact's Status changes to "Connected":
-
Create a Custom Metadata record:
- Object API Name:
Contact - Picklist API Name:
Contact_Status__c - Picklist Value:
Connected - Date Field API Name:
Date_Entered_Connected__c - Exit Date Field API Name:
Date_Exited_Connected__c
- Object API Name:
-
Deploy the trigger via the LWC interface
-
Automatic date stamping:
- When
Contact_Status__cchanges TO "Connected" →Date_Entered_Connected__cis stamped - When
Contact_Status__cchanges FROM "Connected" →Date_Exited_Connected__cis stamped
- When
- Salesforce CLI (sf)
- VS Code with Salesforce Extension Pack
- Access to a Salesforce org with appropriate permissions
- DevHub org with 2GP enabled (for package development)
-
Clone the repository:
git clone [repository-url] cd DateBuddy -
Authorize your DevHub:
sf org login web --set-default-dev-hub --alias MyDevHub
-
Create a scratch org:
sf org create scratch --definition-file config/project-scratch-def.json --alias DateBuddyScratch --set-default
-
Deploy source:
sf project deploy start
-
Run tests:
sf apex run test --code-coverage
- Trigger Context: All DateBuddy triggers run in BEFORE SAVE context
- Deployment Method: Uses JSZip methodology from apex-mdapi (not Zippex)
- Handler Logic: Centralized in
DateBuddyHandlerclass - Metadata Service: Custom implementation for dynamic trigger deployment
- Client-Side Processing: LWC handles direction determination for improved performance
- Dependency Management: See Dependencies.md for complete dependency mapping
- Date_Stamp_Mapping__mdt: Configuration for field mappings
Object_API_Name__c: Target objectPicklist_API_Name__c: Picklist field to monitorPicklist_Value__c: Value to trackDate_Field_API_Name__c: Entry date fieldExit_Date_Field_API_Name__c: Exit date fieldDirection__c: Direction indicator (Entering/Exiting/In/Out)
- DateBuddyHandler: Core trigger handler for date stamping (96% coverage)
- UpdateDateFieldAction: Invocable action for Flow Builder (90% coverage)
- DateBuddyDeployController: LWC controller for trigger deployment (90% coverage)
- DateStampTriggerDeployer: Utility for deploying triggers dynamically (86% coverage)
- MetadataService: Metadata API wrapper for deployments
- RemoteSiteHelperController: Remote site settings management
- MetadataServiceExamples: Example implementations from apex-mdapi
- dateBuddyTriggerDeployer: UI for deploying triggers to objects
- Package Name: DateBuddy
- Package ID:
0HoWs0000001qqzKAA - Current Version: 1.4.0
- Subscriber Package Version ID:
04tWs000000aWuDIAU - Package Type: Unlocked
- DevHub:
- API Version: 64.0
https://login.salesforce.com/packaging/installPackage.apexp?p0=04tWs000000aWuDIAU
- 1.4.0 (
04tWs000000aWuDIAU) - Enhanced deployment status and LWC UX improvements - 1.3.0 (
04tWs000000aWMLIA2) - Added metadata service components, dependency mapping, and test improvements - 1.2.0-1 (
04tWs000000aWKjIAM) - Enhanced UI with client-side processing and improved terminology - 1.1.0-1 (
04tWs000000aW1NIAU) - Added MetadataServiceTest class for improved test coverage - 1.0.0-1 (
04tWs000000aVzlIAE) - Initial release with core DateBuddy functionality
# Create new package version
sf package version create --package DateBuddy --installation-key-bypass --wait 20 --skip-validation
# Install package
sf package install --package 04tWs000000aWuDIAU --target-org YOUR_ORG --wait 10
# List package versions
sf package version list --package DateBuddy- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Maintain 90%+ test coverage
- Follow Salesforce development best practices
- Update documentation for new features
- Test in scratch org before submitting PR
- Deploy ONLY the files being worked on directly
This project is licensed under the MIT License - see the LICENSE file for details.
For issues, questions, or contributions:
- Issues: GitHub Issues
- Documentation: This README and inline code comments
- Salesforce Trailblazer Community: Search for "DateBuddy"
Made with ❤️ for the Salesforce Community