How can I specify required fields when programmatically loading a node creation form? I'm building out a custom workflow. Newly registered users can create a node that has several fields - some of the fields are required, others are pre-filled, and others are disabled. The benefits for having 1 content type to handle this greatly outweigh the negatives.

Fields that I set the #default_value, #access, or any other FAPI attribute on are working properly, but fields where I set #required = TRUE are not validating and are not stopping the node from being created!

Here's my code:

function my_custom_callback($arg) {
  module_load_include('inc', 'node', 'node.pages');
  $page['node_form'] = node_add('content_type_name');

  if ($arg == 'foo') {
    $page['node_form']['field_favorite_color'][LANGUAGE_NONE][0]['value']['#required'] = TRUE;
  } else if ($arg == 'bar') {
    $page['node_form']['field_favorite_food'][LANGUAGE_NONE][0]['value']['#required'] = TRUE;
  }
}


If I visit this page with $arg = 'foo',  I can submit this form without entering anything in field_favorite_color, even though this field shows the red asterisk and has the class 'required' and is showing as #required = TRUE in the form's render array when I do a dpm() within the callback function. 

Why won't Drupal require that this required fields be filled out? Do I need to rebuild the form somehow after I've altered the fields? If I set the same code in a hook_form_alter() then it works as expected, but in that case I don't have the control that I get through my custom callback to lead the user through a multi-step workflow.

Edit: This is Drupal 7. Tried adding that tag but am seeing 'drupal-7' and 'drupal7' tags are not allowed.
In short, to answer your question "How can I specify required fields when programmatically loading a node creation form?" ... use hook_form_alter() and keep your sanity! :)

Here is what I ended up doing after toying with a bunch of proposed solutions I ran across wile learning hook_form_alter() and Drupal's Form API.

I'm sure this solution could be massively improved, but this worked for me and it seems to be fairly simple to manage/maintain.

Managing a Single Content Type With a Complex Workflow Using Node Edit Forms

First after hours of debugging, I decided not to use a custom callback.  Instead I edited the built in node add/edit form using nothing more than hook_form_alter(). (I like hook_form_FORM_ID_alter() instead of hook_form_alter() as it helps me keep my code organized. A personal preference for sure.)


  Note: Drupal 7 use the Administration theme by default for creating
  new nodes/content. You can get around this by unchecking the 'Use
  the administration theme when editing or creating content'
  field at the bottom of 'admin/appearance' page in the admin panel.


Using the Rules Module To Keep Track Of Workflow Stages

I added a new field called "workflow" to my node type (Added through UI at: http://example.com/admin/structure/types/manage/%content_type%/fields) so I could keep track of what stage the user was currently at.  

My workflow field ($form['field_workflow']) was a select list with predefined options (stages).  

 'new' =&gt; 'New', //Default Value For New Nodes
 'foo' =&gt; 'Foo',
 'bar' =&gt; 'Bar'


I used the Rules module to move the workflow along.  Rules allowed me to move a bunch of the 'submit function' code into rules configuration.  This made updates simpler as I don't need to make as many module edits.

Rules makes it very simple to do things like 'if the node is new, set the workflow to foo after saving' or 'if the workflow is set to foo, set the new workflow state to bar after saving.'

Organization: Ugh...  Setting Up The Actual Flow

First I tried to keep each stage of the workflow as clean as possible by packing alterations in their own functions with a custom naming convention.

function MODULE_stage_new(&amp;$form) {
    //Insert all alterations here for use when node has not yet been saved. It's new!
    $form['field_favorite_color'][LANGUAGE_NONE][0]['value']['#access'] = FALSE;
    $form['field_favorite_food'][LANGUAGE_NONE][0]['value']['#access'] = FALSE;
}

function MODULE_stage_foo(&amp;$form) {
    //Insert all 'foo' stage alterations here.
    $form['field_favorite_color'][LANGUAGE_NONE][0]['value']['#required'] = TRUE;
}

function MODULE_stage_bar(&amp;$form) {
    //Insert all 'bar' stage alterations here.
    $form['field_favorite_food'][LANGUAGE_NONE][0]['value']['#required'] = TRUE;
}

/**
* Implements hook_form_FORM_ID_alter().
*/
function MODULE_form_FORM_ID_alter(&amp;$form, $form_state) {
    /**
    * Place any alterations here that apply to all stages here before the 
    * 'if' statements below.
    *
    * I generally place my validation callbacks and CSS additions here.
    **/

    if (empty($form['nid']['#value'])) {
        MODULE_stage_new($form);
    } else if ($arg == 'foo') {
        MODULE_stage_foo($form);
    } else if ($arg == 'bar') {
        MODULE_stage_bar($form);
    }
}


Now that hook_form_alater() is being used exclusively without a custom callback, you shouldn't have any issues with the ['#required'] elements.

Occasionally I would have to add an '#after_build' function (yea, yet another function to keep track of...) but did so with a common naming convetion.  For example, if I needed to add a new stylesheet at the 'foo' stage I would use the following functions.

function MODULE_stage_foo(&amp;$form) {
    //Insert all 'foo' stage alterations here.
    $form['field_favorite_color'][LANGUAGE_NONE][0]['value']['#required'] = TRUE;
    $form['#after_build'][] = 'MODULE_stage_foo_after_build';
}

function MODULE_stage_foo_after_build($form, &amp;$form_state) {
    drupal_add_css(path_to_theme() . "/css/foo.css");
    return $form;
}


I hope this wasn't too overkill or off topic.  I honestly found that sticking to hook_form_alter() for node add/edit form modifications saved me a ton of headaches over how I was trying to do it before.  The custom callback method using module_load_include('inc', 'node', 'node.pages'); never worked for me the way it seemed to for people writing the examples I found.  Then again, I'm still pretty new to Drupal. :)