Answers.Body: <p>Based on the answer of @artfulrobot (thanks!), i have made a version of the code that use the definition of the custom field itself. If we have a default value, then force the creation of the field, otherwise, just ignore. Of course, it's probably a bit slower because we do some extra api calls, but it means we could change the defaults in the UI without changing the code.</p>
  
  
  
  <pre><code>
  function myext_civicrm_post($op, $objectName, $objectId, &amp;$objectRef) {
  
    if ($objectName == 'Contribution') {
  
      $result = civicrm_api3('CustomField', 'get', [
        'sequential' =&gt; 1,
        'return' =&gt; ["default_value", "custom_group_id", "name"],
        'default_value' =&gt; ['IS NOT NULL' =&gt; 1],
        // use your own custom group id
        'custom_group_id.id' =&gt; 1,
      ]); 
      if ($result['count']) {
        // use the first one (any will do because we know that we are in one custom group)
        $check_field = 'custom_' . $result['values'][0]['id'];
  
        // See if we have a field that belongs to the fieldset.
        $existResult = civicrm_api3('CustomValue', 'get', [
          'sequential' =&gt; 1,
          'entity_id' =&gt; $objectId,
          'entity_type' =&gt; "Contribution",
          'return.' . $check_field =&gt; 1,
        ]); 
  
        if ($existResult['count'] == 0) {
          // insert a new entry with defaults values
          $p = [ 
            'sequential' =&gt; 1,
            'entity_id' =&gt; $objectId,
            'entity_type' =&gt; 'Contribution'
          ];  
          foreach ($result['values'] as $row) {
            $id = $row['id'];
            $p['custom_' . $id] = $row['default_value'];
          }   
  
          // Create the default entry.                                                                                                        
          $result = civicrm_api3('CustomValue', 'create', $p);
        }   
      }
  
    }
  
  }
  </code></pre>
  
  <p>I guess with a bit of extra effort, one could make a generic extension that works for any custom fields that attach to any entity type.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2019-11-06T14:00:55.327Z
Answers.Text: Nb. I think CiviCRM does this stuff now.
Answers.UserDisplayName: artfulrobot
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2019-11-15T15:58:44.790Z
Answers.Text: Ok, good to know thanks! Do you have any reference of the issue / pull request ?
Answers.UserDisplayName: samuelsov
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2019-11-15T17:35:01.507Z
Answers.Text: No, I just noticed it at 5.18.
Answers.UserDisplayName: artfulrobot
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2019-04-08T15:53:37.500Z
Answers.LastActivityDate: 2019-04-08T15:53:37.500Z
Answers.UserDisplayName: samuelsov
Answers.Body: <p>I've handled this using <a href="https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_custom" rel="nofollow noreferrer">hook_civicrm_custom</a>.  This fires after the db write, which isn't ideal, but has a much simpler set of params than hook_civicrm_pre.</p>
  
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2017-01-05T17:49:49.363Z
Answers.Text: Does this call when Contributions are created? My db is not showing any record being written to the custom data table, which I think would imply that this hook would not fire (because the docs say "This hook is called AFTER the db write on a custom table" and no write has happened)?
Answers.UserDisplayName: artfulrobot
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2017-01-05T17:51:51.190Z
Answers.Text: Ah - I guess in my instance, I had other custom fields that were being written.  I don't think my solution will apply to your use case.
Answers.UserDisplayName: Jon G - Megaphone Tech
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2017-01-05T17:45:23.880Z
Answers.LastActivityDate: 2017-01-05T17:51:19.743Z
Answers.LastEditDate: 2017-01-05T17:51:19.743Z
Answers.UserDisplayName: Jon G - Megaphone Tech
Answers.Body: <h2>Used to work, before CiviCRM v5.18</h2>
  
  <p>Here's how I've opted to do it. Please do submit alternative answers if there's a better way.</p>
  
  <p>Use the <a href="https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_post" rel="nofollow noreferrer">civicrm_post hook</a> like this:</p>
  
  <p><strong>Note that you need to replace <code>N</code> with your custom field's ID number</strong></p>
  
  <pre><code>function myext_civicrm_post($op, $objectName, $objectId, &amp;$objectRef) {
  
    if ($objectId &amp;&amp; $op == 'create' &amp;&amp; $objectName == 'Contribution') {
  
      // See if we have a field that belongs to the fieldset.
      $result = civicrm_api3('CustomValue', 'get', [
        'sequential' =&gt; 1,
        'entity_id' =&gt; $objectId,
        'entity_type' =&gt; "Contribution",
        'return.custom_N' =&gt; 1,
      ]);
  
      if ($result['count'] == 0) {
        // Create the default entry.
        $result = civicrm_api3('CustomValue', 'create', [
          'sequential' =&gt; 1,
          'entity_id' =&gt; $contribution_id,
          'entity_type' =&gt; "Contribution",
          'custom_N' =&gt; 'unknown',
          // custom_M ...
        ]);
      }
  
    }
  }
  </code></pre>
  
  <p>Then when you create a Contribution, the defaults are properly set.</p>
  
  <h3>Interesting observations</h3>
  
  <p>My interest was in creating the default record when the contribution was created by the API. But I thought I'd look into how this works with using the UI too.</p>
  
  <p>If you use the web UI to create a contribution then the code above gets in before the main process creates/sets the custom values. i.e. This is the order of things:</p>
  
  <ol>
  <li>User creates a contribution record.</li>
  <li>Contribution record created.</li>
  <li>Our hook runs, no custom value exists so it creates the default record.</li>
  <li>The user-entered values (inc. the defaults that were set up in the form) overwrite the default created by our hook.</li>
  </ol>
  
  <p>The same process happens when you use the API to create a contribution but include values for the custom data also.</p>
  
  <h3>Why this no longer works</h3>
  
  <p>The above worked fine until sometime around CiviCRM version 5.18 at which point it stopped working. It stopped because during creation of a contribution this code would run, creating a custom data record, then the calling code would also try to create the same custom data record - causing a database error.</p>
  
  <h2>Works, if you don't need to use phpunit (so, doesn't really work!)</h2>
  
  <p>I found that I could acheive what I had been trying to do as follows:</p>
  
  
  
  <pre><code>function myext_civicrm_post($op, $objectName, $objectId, &amp;$objectRef) {
  
    if ($objectId &amp;&amp; $op == 'create' &amp;&amp; $objectName == 'Contribution') {
       CRM_Core_Transaction::addCallback(
         CRM_Core_Transaction::PHASE_POST_COMMIT,
         'myfixercallback', [$objectId]);
    }
  }
  </code></pre>
  
  <p>Then <code>myfixercallback</code> would do the same work. However, if you are using this then beware phpunit tests that use transaction interface: your code won't run until the test ends - which is too late!</p>
  
  <p><strong>However!</strong> <del>it seems that CiviCRM - at least from 5.18, possibly earlier, now properly creates default custom data. So this hack is simply not needed any more.</del> Sorry, certainly 5.24.2 does not do this.</p>
  
  <h2>API Wrapper solution (tested with 5.24.2)</h2>
  
  <p>You can implement an API wrapper, but you need to implement it for both API3 and API4.</p>
  
  
  
  <pre><code>
  function giftaid_civicrm_apiWrappers(&amp;$wrappers, $apiRequest) {
    if ($apiRequest['entity'] === 'Contribution' &amp;&amp; $apiRequest['action'] === 'create') {
      $wrappers[] = new CRM_MyApiWrapperContributionCreate();
    }
  }
  
  // ... Implement the class ...
  
  class CRM_MyApiWrapperContributionCreate implements API_Wrapper {
  
    public function fromApiInput($apiRequest) {
  
      if ($apiRequest instanceof Civi\Api4\Generic\AbstractAction) {
        // APIv4
        $params = $apiRequest-&gt;getParams();
        if (empty($params['id']) &amp;&amp; noneOfTheCustomFieldsAreDefinedApi4($apiRequest)) {
          $apiRequest-&gt;addValue('my_fieldset_name.my_field_name', '');
        }
      }
      if (empty($apiRequest['params']['id']) &amp;&amp; noneOfTheCustomFieldsAreDefinedApi3($apiRequest)) {
          $apiRequest['params']['custom_1234'] = '';
        }
      }
      return $apiRequest;
    }
  
    public function toApiOutput($apiRequest, $result) {
      return $result;
    }
  }
  </code></pre>
  
  <p>In this example</p>
  
  <ul>
  <li><p><code>my_fieldset_name</code> is the custom fieldset, in which <code>my_field_name</code> is a field that has <code>''</code> as a default value, and an internal ID of 1234 which explains where <code>custom_1234</code> comes from.</p></li>
  <li><p>Rather than relying on setting a value to <code>''</code> you could look up the default value for a field, but if you're doing this for a specific solution you can do it this way to be more efficient.</p></li>
  <li><p><code>noneOfTheCustomFieldsAreDefinedApi3</code> needs to scan the params for fields named <code>custom_N</code> where <code>N</code> is the known ID value of each of your fields. And <code>noneOfTheCustomFieldsAreDefinedApi4</code> needs to scan the params for keys beginning with <code>my_fieldset_name.</code></p></li>
  </ul>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-07-15T14:40:21.020Z
Answers.Text: As a follow-up, you don't need to use the API wrapper construct which is deprecated; instead you can just listen to the Symfony `\Civi\API\Events::PREPARE` event, which is cleaner. Please see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_apiWrappers/ for details
Answers.UserDisplayName: artfulrobot
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2017-01-06T10:27:05.473Z
Answers.LastActivityDate: 2020-04-14T13:32:51.013Z
Answers.LastEditDate: 2020-04-14T13:32:51.013Z
Answers.UserDisplayName: artfulrobot
Body: <p><strong>EDIT: this is NOT working, at least in CiviCRM 5.24.2</strong><br>
  <del><strong>EDIT: this is done automatically by CiviCRM from 5.18+</strong></del></p>
  
  <p>I have a custom fieldset on Contributions that uses an option list. The option list has a default value set, but when contributions are created by non-UI processes (e.g. API) they do not have this default value; the field is empty/null/not there.</p>
  
  <p>The fieldset is created by an extension. How could I ensure the default is set for all contributions?</p>
  
  <p>Seems the options might be:</p>
  
  <ul>
  <li>Use <a href="https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_post" rel="nofollow noreferrer">hook_civicrm_post</a> then use the API to make a call.</li>
  <li>Or could <a href="https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_pre" rel="nofollow noreferrer">hook_civicrm_pre</a> be used for this? Can you set custom fields in this part of the process?</li>
  <li>or a horrible way would be to write an API job to somehow create default entries and run this by cron. Perhaps this might be necessary to set a default for existing contributions though?</li>
  </ul>
  
  <p>Anyone done this? If so, how d'ya do it?</p>
  
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2019-11-07T21:56:38.297Z
Comments.Text: think you should update your answer too - perhaps insert at top of it
Comments.UserDisplayName: petednz - fuzion
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2019-11-08T09:48:44.977Z
Comments.Text: @petednz-fuzion erm, I did already!
Comments.UserDisplayName: artfulrobot
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2019-11-09T00:42:06.697Z
Comments.Text: odd. i must be tired. I see "EDIT: this is done automatically by CiviCRM from 5.18+" in your question but don't see equivalent in your answer
Comments.UserDisplayName: petednz - fuzion
Tags: <api><developer><hooks>
Title: How to set a default value for a custom field
ThreadText: # How to set a default value for a custom field
  
  (Asked by: username_0 on 2017-01-05)
  
  **EDIT: this is NOT working, at least in CiviCRM 5.24.2**  
  ~~**EDIT: this is done automatically by CiviCRM from 5.18+**~~
  
  I have a custom fieldset on Contributions that uses an option list. The option list has a default value set, but when contributions are created by non-UI processes (e.g. API) they do not have this default value; the field is empty/null/not there.
  
  The fieldset is created by an extension. How could I ensure the default is set for all contributions?
  
  Seems the options might be:
  
  * Use [hook\_civicrm\_post](https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_post) then use the API to make a call.
  * Or could [hook\_civicrm\_pre](https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_pre) be used for this? Can you set custom fields in this part of the process?
  * or a horrible way would be to write an API job to somehow create default entries and run this by cron. Perhaps this might be necessary to set a default for existing contributions though?
  
  Anyone done this? If so, how d'ya do it?
  
  **Tags:** api, developer, hooks
  
  > **Comments:**
  > **username_1**: think you should update your answer too - perhaps insert at top of it  
  > **username_0**: @petednz-fuzion erm, I did already!  
  > **username_1**: odd. i must be tired. I see "EDIT: this is done automatically by CiviCRM from 5.18+" in your question but don't see equivalent in your answer  
  
  ---
  
  ## 3 answers
  
  ### Answer by username_0 on 2017-01-06. Score: 4 | Accepted answer
  
  Used to work, before CiviCRM v5.18
  Here's how I've opted to do it. Please do submit alternative answers if there's a better way.
  Use the civicrm_post hook like this:
  Note that you need to replace N with your custom field's ID number
  function myext_civicrm_post($op, $objectName, $objectId, &$objectRef) {
  
    if ($objectId && $op == 'create' && $objectName == 'Contribution') {
  
      // See if we have a field that belongs to the fieldset.
      $result = civicrm_api3('CustomValue', 'get', [
        'sequential' => 1,
        'entity_id' => $objectId,
        'entity_type' => "Contribution",
        'return.custom_N' => 1,
      ]);
  
      if ($result['count'] == 0) {
        // Create the default entry.
        $result = civicrm_api3('CustomValue', 'create', [
          'sequential' => 1,
          'entity_id' => $contribution_id,
          'entity_type' => "Contribution",
          'custom_N' => 'unknown',
          // custom_M ...
        ]);
      }
  
    }
  }
  
  Then when you create a Contribution, the defaults are properly set.
  Interesting observations
  My interest was in creating the default record when the contribution was created by the API. But I thought I'd look into how this works with using the UI too.
  If you use the web UI to create a contribution then the code above gets in before the main process creates/sets the custom values. i.e. This is the order of things:
  
  User creates a contribution record.
  Contribution record created.
  Our hook runs, no custom value exists so it creates the default record.
  The user-entered values (inc. the defaults that were set up in the form) overwrite the default created by our hook.
  
  The same process happens when you use the API to create a contribution but include values for the custom data also.
  Why this no longer works
  The above worked fine until sometime around CiviCRM version 5.18 at which point it stopped working. It stopped because during creation of a contribution this code would run, creating a custom data record, then the calling code would also try to create the same custom data record - causing a database error.
  Works, if you don't need to use phpunit (so, doesn't really work!)
  I found that I could acheive what I had been trying to do as follows:
  function myext_civicrm_post($op, $objectName, $objectId, &$objectRef) {
  
    if ($objectId && $op == 'create' && $objectName == 'Contribution') {
       CRM_Core_Transaction::addCallback(
         CRM_Core_Transaction::PHASE_POST_COMMIT,
         'myfixercallback', [$objectId]);
    }
  }
  
  Then myfixercallback would do the same work. However, if you are using this then beware phpunit tests that use transaction interface: your code won't run until the test ends - which is too late!
  However! it seems that CiviCRM - at least from 5.18, possibly earlier, now properly creates default custom data. So this hack is simply not needed any more. Sorry, certainly 5.24.2 does not do this.
  API Wrapper solution (tested with 5.24.2)
  You can implement an API wrapper, but you need to implement it for both API3 and API4.
  
  function giftaid_civicrm_apiWrappers(&$wrappers, $apiRequest) {
    if ($apiRequest['entity'] === 'Contribution' && $apiRequest['action'] === 'create') {
      $wrappers[] = new CRM_MyApiWrapperContributionCreate();
    }
  }
  
  // ... Implement the class ...
  
  class CRM_MyApiWrapperContributionCreate implements API_Wrapper {
  
    public function fromApiInput($apiRequest) {
  
      if ($apiRequest instanceof Civi\Api4\Generic\AbstractAction) {
        // APIv4
        $params = $apiRequest->getParams();
        if (empty($params['id']) && noneOfTheCustomFieldsAreDefinedApi4($apiRequest)) {
          $apiRequest->addValue('my_fieldset_name.my_field_name', '');
        }
      }
      if (empty($apiRequest['params']['id']) && noneOfTheCustomFieldsAreDefinedApi3($apiRequest)) {
          $apiRequest['params']['custom_1234'] = '';
        }
      }
      return $apiRequest;
    }
  
    public function toApiOutput($apiRequest, $result) {
      return $result;
    }
  }
  
  In this example
  
  my_fieldset_name is the custom fieldset, in which my_field_name is a field that has '' as a default value, and an internal ID of 1234 which explains where custom_1234 comes from.
  Rather than relying on setting a value to '' you could look up the default value for a field, but if you're doing this for a specific solution you can do it this way to be more efficient.
  noneOfTheCustomFieldsAreDefinedApi3 needs to scan the params for fields named custom_N where N is the known ID value of each of your fields. And noneOfTheCustomFieldsAreDefinedApi4 needs to scan the params for keys beginning with my_fieldset_name.
  
  > **Comments:**
  > **username_0**: As a follow-up, you don't need to use the API wrapper construct which is deprecated; instead you can just listen to the Symfony `\Civi\API\Events::PREPARE` event, which is cleaner. Please see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_apiWrappers/ for details  
  
  ---
  
  ### Answer by username_2 on 2019-04-08. Score: 1
  
  Based on the answer of @username_0 (thanks!), i have made a version of the code that use the definition of the custom field itself. If we have a default value, then force the creation of the field, otherwise, just ignore. Of course, it's probably a bit slower because we do some extra api calls, but it means we could change the defaults in the UI without changing the code.
  
  function myext_civicrm_post($op, $objectName, $objectId, &$objectRef) {
  
    if ($objectName == 'Contribution') {
  
      $result = civicrm_api3('CustomField', 'get', [
        'sequential' => 1,
        'return' => ["default_value", "custom_group_id", "name"],
        'default_value' => ['IS NOT NULL' => 1],
        // use your own custom group id
        'custom_group_id.id' => 1,
      ]); 
      if ($result['count']) {
        // use the first one (any will do because we know that we are in one custom group)
        $check_field = 'custom_' . $result['values'][0]['id'];
  
        // See if we have a field that belongs to the fieldset.
        $existResult = civicrm_api3('CustomValue', 'get', [
          'sequential' => 1,
          'entity_id' => $objectId,
          'entity_type' => "Contribution",
          'return.' . $check_field => 1,
        ]); 
  
        if ($existResult['count'] == 0) {
          // insert a new entry with defaults values
          $p = [ 
            'sequential' => 1,
            'entity_id' => $objectId,
            'entity_type' => 'Contribution'
          ];  
          foreach ($result['values'] as $row) {
            $id = $row['id'];
            $p['custom_' . $id] = $row['default_value'];
          }   
  
          // Create the default entry.                                                                                                        
          $result = civicrm_api3('CustomValue', 'create', $p);
        }   
      }
  
    }
  
  }
  
  I guess with a bit of extra effort, one could make a generic extension that works for any custom fields that attach to any entity type.
  
  > **Comments:**
  > **username_0**: Nb. I think CiviCRM does this stuff now.  
  > **username_2**: Ok, good to know thanks! Do you have any reference of the issue / pull request ?  
  > **username_0**: No, I just noticed it at 5.18.  
  
  ---
  
  ### Answer by username_3 on 2017-01-05. Score: 0
  
  I've handled this using [hook\_civicrm\_custom](https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_custom). This fires after the db write, which isn't ideal, but has a much simpler set of params than hook\_civicrm\_pre.
  
  > **Comments:**
  > **username_0**: Does this call when Contributions are created? My db is not showing any record being written to the custom data table, which I think would imply that this hook would not fire (because the docs say "This hook is called AFTER the db write on a custom table" and no write has happened)?  
  > **username_3**: Ah - I guess in my instance, I had other custom fields that were being written.  I don't think my solution will apply to your use case.  
  
  ---
  
  