URL variable not respected when ajax call happens in user registration form There are some fields (some required, some not) I need to (conditionally) hide in the user creation form:

function hook_form_alter(&amp;$form, &amp;$form_state, $form_id){
    switch($form_id) {
    case 'user_register_form':
        //variable_name is sent using hook_menu_output_alter()
        if ($_REQUEST['variable_name'] == 'variable_value') {
            $form['field_id'][LANGUAGE_NONE]['#required'] = FALSE;
            $form['field_id']['#access'] = FALSE;
        }
}


This generally works but not when I override a (required) entity reference field in the form to change its autocomplete path:

//in another module:
function hook_alter(&amp;$form, &amp;$form_state, $form_id){
    $form['entity_ref_field'][LANGUAGE_NONE][0]['target_id']['#autocomplete_path']= 'custom_autocomplete';
    $form['entity_ref_field'][LANGUAGE_NONE][0]['target_id']['#ajax'] = array(
            'callback' =&gt; 'custom_callback',
            'wrapper' =&gt; 'custom_wrapper',
            'method' =&gt; 'html',
            'effect' =&gt; 'fade',
    );
}

function hook_menu(){
    $items = array();
    $items['custom_autocomplete'] = array(
        'title' =&gt; '',
        'page callback' =&gt; 'custom_page_autocomplete',
        'access arguments' =&gt; array('access content'),
        'type' =&gt; MENU_CALLBACK,
    );
    return $items;
}

function custom_page_autocomplete($string) {
  $string = array(':s' =&gt; $string . '%');
    if ($string) {
        $results = db_query("SELECT title, nid FROM {node} as n WHERE type = 'custom_type' and title LIKE :s", $string);

        $items = array();

        foreach ($results as $result) {
            $items[$result-&gt;title.' ('.$result-&gt;nid.')'] = $result-&gt;title;
        }
        print drupal_json_encode($items);
    }
}

function custom_callback(&amp;$form, &amp;$form_state) {
    //if an entity reference field value is set, then find &amp; assign values to some other fields, based on that entity:
    $entity_id = $form_state['values']['field_id']['und'][0]['target_id'];
    if ($selected_entity = node_load($entity_id)) {
        $form['field_somefield1'][LANGUAGE_NONE][0]['value']['#value'] = $selected_entity-&gt;field_someotherfield1['und'][0]['value'];
        $form['field_somefield2'][LANGUAGE_NONE][0]['value']['#value'] = $selected_entity-&gt;field_someotherfield2['und'][0]['value'];
        $form['field_somefield3'][LANGUAGE_NONE][0]['value']['#value'] = $selected_entity-&gt;field_someotherfield3['und'][0]['value'];
    }

    $commands = array();
    $commands[] = ajax_command_replace("#edit-field-user-somefield1", render($form['field_user_somefield1']));
    $commands[] = ajax_command_replace("#edit-field-user-somefield2", render($form['field_user_somefield2']));
    $commands[] = ajax_command_replace("#edit-field-user-somefield3", render($form['field_user_somefield3']));
    return array('#type' =&gt; 'ajax', '#commands' =&gt; $commands);
}


So, whenever an AJAX call is made by the overridden field and I then submit the form, all the (previously hidden) fields are visible again and the required fields become required again.

Any ideas why this is happening?
The AJAX callback shouldn't alter the form (that should be handled by the form constructor or a form alter). This tends to be a good thing, because it forces you to put all your form logic in form functions, and that means they can degrade gracefully if JS isn't available. When dealing with AJAX forms, the callback typically just decides what part of the form to return. Take a look at the example from the examples module:

/**
 * Basic AJAX callback example.
 *
 * Simple form whose ajax-enabled 'changethis' member causes a text change
 * in the description of the 'replace_textfield' member.
 *
 * See @link http://drupal.org/node/262422 Form API Tutorial @endlink
 */
function ajax_example_simplest($form, &amp;$form_state) {
  $form = array();
  $form['changethis'] = array(
    '#title' =&gt; t("Choose something and explain why"),
    '#type' =&gt; 'select',
    '#options' =&gt; array(
      'one' =&gt; 'one',
      'two' =&gt; 'two',
      'three' =&gt; 'three',
    ),
    '#ajax' =&gt; array(
      // #ajax has two required keys: callback and wrapper.
      // 'callback' is a function that will be called when this element changes.
      'callback' =&gt; 'ajax_example_simplest_callback',
      // 'wrapper' is the HTML id of the page element that will be replaced.
      'wrapper' =&gt; 'replace_textfield_div',
      // There are also several optional keys - see ajax_example_autocheckboxes
      // below for details on 'method', 'effect' and 'speed' and
      // ajax_example_dependent_dropdown for 'event'.
    ),
  );

  // This entire form element will be replaced whenever 'changethis' is updated.
  $form['replace_textfield'] = array(
    '#type' =&gt; 'textfield',
    '#title' =&gt; t("Why"),
    // The prefix/suffix provide the div that we're replacing, named by
    // #ajax['wrapper'] above.
    '#prefix' =&gt; '&lt;div id="replace_textfield_div"&gt;',
    '#suffix' =&gt; '&lt;/div&gt;',
  );

  // An AJAX request calls the form builder function for every change.
  // We can change how we build the form based on $form_state.
  if (!empty($form_state['values']['changethis'])) {
    $form['replace_textfield']['#description'] = t("Say why you chose '@value'", array('@value' =&gt; $form_state['values']['changethis']));
  }
  return $form;
}

/**
 * Callback for ajax_example_simplest.
 *
 * On an ajax submit, the form builder function is called again, then the $form
 * and $form_state are passed to this callback function so it can select which
 * portion of the form to send on to the client.
 *
 * @return array
 *   Renderable array (the textfield element)
 */
function ajax_example_simplest_callback($form, $form_state) {
  // The form has already been submitted and updated. We can return the replaced
  // item as it is.
  return $form['replace_textfield'];
}


Note how the dynamic change of the form happens in the form constructor while the callback is minimal.