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**: @username_1 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](https://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_post) 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:
  
  1. User creates a contribution record.
  2. Contribution record created.
  3. Our hook runs, no custom value exists so it creates the default record.
  4. 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.  
  
  ---
  
  