In a busy non-profit or service-oriented environment, data entry needs to be as friction-less as possible. One common frustration for CiviCRM users is the CiviCRM activity default status. By default, CiviCRM often sets new activities to "Scheduled." While this makes sense for future meetings, it is an extra step for case workers who are logging work they have already completed, such as a quick phone call or document verification.
If your staff frequently forgets to change the status from "Scheduled" to "Completed," your database ends up cluttered with overdue tasks that aren't actually overdue. To solve this, you can customize your CiviCRM instance to automatically select a default status based on the specific activity type being logged. In this guide, you will learn how to implement this logic using a custom extension, combining PHP hooks and JavaScript for a seamless user experience.
The Challenge of Static Activity Defaults
CiviCRM's core functionality provides a unified interface for all activity types. While this consistency is generally a benefit, it lacks the granularity to distinguish between "retrospective" activities (things that just happened) and "prospective" activities (things planned for the future).
For example, a "Review Meeting" might always be scheduled for next month, while a "Referral Made" activity is almost always completed at the moment it is recorded. By automating the status selection, you reduce clicks, improve data integrity, and ensure that your reporting on "Completed" vs. "Scheduled" work remains accurate.
Implementing Server-Side Defaults with hook_civicrm_buildForm
The most robust way to modify form behavior in CiviCRM is through the hook_civicrm_buildForm hook. This allows you to intercept the form rendering process and inject your own default values.
When a user opens an activity form from a specific context (like a Case or a Contact record), the activity type is often already known. You can use this information to set the status ID. Here is the PHP logic you would include in your custom extension's .php file:
/**
* Implement myextension_civicrm_buildForm
* This hook modifies the activity form to set custom default statuses.
*/
function myextension_civicrm_buildForm($formName, &$form) {
// Target the main Activity form
if ($formName == 'CRM_Activity_Form_Activity') {
// Only apply logic when adding a new activity (not editing)
// and when the context is not 'standalone'
if ($form->getAction() == CRM_Core_Action::ADD && $form->_context != 'standalone') {
// Define which activities should have which default statuses
$activities = array(
'Completed' => array('Meeting', 'Phone Call', 'Referral'),
'No-Show' => array('Custom Activity'),
'My Custom Status' => array('Another Custom Activity')
);
foreach ($activities as $status => $activity_types) {
if (in_array($form->_activityTypeName, $activity_types)) {
// Fetch the status_id dynamically by Label using API3
$result = civicrm_api3('OptionValue', 'getsingle', array(
'sequential' => 1,
'option_group_id' => 'activity_status',
'label' => $status,
));
// Apply the default value to the form object
$defaults['status_id'] = $result['value'];
$form->setDefaults($defaults);
}
}
}
}
}
Handling Dynamic Changes with JavaScript
The PHP approach works perfectly when the activity type is pre-selected. However, if a user goes to Contacts -> New Activity, they land on a "standalone" form where they select the activity type from a dropdown after the page has loaded. In this scenario, the PHP hook has already finished its job.
To handle this, you need to use JavaScript to listen for changes to the activity_type_id field and update the status_id field dynamically using the CiviCRM API. This ensures the CiviCRM activity default status updates in real-time as the user interacts with the form.
Updated PHP Hook for JS Integration
First, update your hook to pass your configuration to the front-end and load a custom JavaScript file:
function myextension_civicrm_buildForm($formName, &$form) {
if ($formName == 'CRM_Activity_Form_Activity' && $form->getAction() == CRM_Core_Action::ADD) {
$activities = array(
'Completed' => array('Meeting', 'Phone Call'),
'No-Show' => array('Custom Activity')
);
if ($form->_context != 'standalone') {
// Use the PHP logic for pre-selected activities (as shown in the previous section)
// ... (insert previous logic here)
} else {
// Pass the configuration array to CiviCRM's JS object
CRM_Core_Resources::singleton()->addVars('myextension', array('defaultedActivities' => $activities));
// Load the external JS file
CRM_Core_Resources::singleton()->addScriptFile('myextension', 'js/defaultedActivities.js');
}
}
}
The JavaScript Logic (defaultedActivities.js)
Create a file named defaultedActivities.js within your extension's assets folder. This script watches the dropdown and triggers an API call whenever the activity type changes:
CRM.$(function($) {
// Listen for changes on the Activity Type select2 element
$( '#activity_type_id' ).change( function() {
var defaultedActivities = CRM.vars.myextension.defaultedActivities;
var activity = $( this ).select2( 'data' );
$.each( defaultedActivities, function( status, activityTypes ) {
activityTypes.map( function( currentActivity ) {
if ( currentActivity == activity.text ) {
// Fetch the correct status ID via AJAX
CRM.api3( 'OptionValue', 'getsingle', {
'sequential': 1,
'option_group_id': 'activity_status',
'label': status
}).done( function( statusId ) {
// Update the status dropdown and trigger change for UI consistency
$("#status_id").val( statusId.value ).trigger("change");
});
}
});
});
});
});
Why Use API3 for Status IDs?
You might be tempted to hard-code the Status IDs (e.g., 1 for Completed, 2 for Scheduled). However, this is a risky practice. In CiviCRM, Option Value IDs can vary between different installations or can be changed by administrators. By using civicrm_api3 to look up the ID by its label (e.g., 'Completed'), you ensure your code is portable and won't break if the database IDs change.
Frequently Asked Questions
Does this work with CiviCase?
Yes. CiviCase activities use the same underlying CRM_Activity_Form_Activity form. The logic provided will work within the case management dashboard just as it does on a standard contact record.
Will this affect existing activities when I edit them?
No. The code specifically checks if the action is CRM_Core_Action::ADD. This ensures that when you edit an existing activity, the current saved status is preserved rather than being overwritten by the default logic.
How do I find the internal name of my activity types?
When defining the $activities array, ensure you use the exact Label or Name as defined in Administer > Customize Data and Screens > Activity Types. If your label is "Phone Call," the array must match that string exactly.
Key Takeaways for Activity Automation
Customizing the CiviCRM activity default status is a high-impact, low-effort improvement for any organization logging high volumes of data. By implementing a simple extension with hook_civicrm_buildForm and a touch of jQuery, you can:
- Reduce Human Error: Eliminate the common mistake of leaving immediate tasks as "Scheduled."
- Save Time: Reduce the number of clicks required for every single activity logged.
- Improve Reporting: Ensure that your dashboards reflect reality by accurately distinguishing between what has been done and what is planned.
Always remember to test your extension in a staging environment first, especially when working with JavaScript triggers on core forms, to ensure compatibility with your specific CiviCRM version and theme.