How do I add a #process callback to a fieldset form element without overwriting the existing callbacks? I am using the following code.

$form['my_fieldset'] = array(
  '#type' =&gt; 'fieldset',
  '#title' =&gt; t('My Title'),
  '#collapsible' =&gt; TRUE,
  '#collapsed' =&gt; TRUE,
  '#access' =&gt; TRUE,
  '#process' =&gt; array('my_callback_function'),
);


If a fieldset is collapsible, it gets two callbacks at some point later. 

form_process_fieldset | (Callback) form_process_fieldset();
ajax_process_form | (Callback) ajax_process_form();


They aren't added to the array with my callback function. If I look at the form array, there is only my callback; so, there is no collapse functionality.

I tried to avoid that by adding the callback in hook_form_alter(), but this is too early; the callback functions are still not there.

$form['my_fieldset']['#process'][] = 'my_callback_function'; //Not working eather


So where is the right place to add a #process callback to my own form element, so it gets added as last callback in the callback array, or at least is not destroying the callbacks implemented by this specific form element itself?

I do some calculations in the callback to set #access = FALSE for this fieldset under certain conditions. This works fine, but maybe this is the wrong way to do it.

The documentation on #process says:


  You will probably want to add to the existing array rather than writing over it, so don't follow this usage example exactly.


The documentation is not very helpful, I don't follow but there is no info how it is done right.
When you add a #process function to a form element, the default ones defined from hook_element_info() are not added. Using hook_form_alter() to add a #process function doesn't change the result, as hook_form_alter() is invoked before the default #process functions are added to the form element. This happens in form_builder(), which uses the following code.

 // Use element defaults.
  if (isset($element['#type']) &amp;&amp; empty($element['#defaults_loaded']) &amp;&amp; ($info = element_info($element['#type']))) {
    // Overlay $info onto $element, retaining preexisting keys in $element.
    $element += $info;
    $element['#defaults_loaded'] = TRUE;
  }


If $element['#process'] is already set, it doesn't get overriden by $info['#process'], which means the default functions are not added.

What you can do is invoke the default #process function from the one you define. If mymodule_process_function() is the function you add, it should contain code similar to the following one.

function mymodule_process_function($element, &amp;$form_state) {
  if (isset($element['#type']) &amp;&amp; ($info = element_info($element['#type'])) &amp;&amp; isset($info['#process'])) {
    foreach ($info['#process'] as $process) {
      $process($element, $form_state);
    }
  }
  // ...
  return $element;
}


If the #process function is necessary in any form where a fieldset is present, then you can implement hook_element_info_alter() to alter the information returned from system_element_info() about the fieldset form element.

  $types['fieldset'] = array(
    '#collapsible' =&gt; FALSE, 
    '#collapsed' =&gt; FALSE, 
    '#value' =&gt; NULL, 
    '#process' =&gt; array('form_process_fieldset', 'ajax_process_form'), 
    '#pre_render' =&gt; array('form_pre_render_fieldset'), 
    '#theme_wrappers' =&gt; array('fieldset'),
  );


The alternative that requires less code to be written is to add a #after_build function to the fieldset. #after_build functions are functions that are called after the #process ones, receive $element, and &amp;$form_state as argument, and need to return $element. As system_element_info() doesn't set any #after_build function, there is less code to execute.
For compatibility with any module that implement hook_element_info_alter(), and which could alter the fieldset definition, code similar to the one I shown before should be used also for the #after_build property.