Last updated November 21, 2013. Created on August 7, 2007.
Edited by drupalshrek, tunic, steinmb, tim.plunkett. Log in to edit this page.

This workflow actually includes some of the nodeapi hooks as well as FAPI workflow to help illustrate the places where form elements can be manipulated.

Drupal 7

Complete FAPI workflow for Drupal 7.x. It's grouped by phases. Rebuild form and AJAX calls flows reference those phases. Click to enlarge.
Drupal 7.x FAPI workflow

Drupal 6 and 5

FAPI workflow

Looking for support? Visit the forums, or join #drupal-support in IRC.


merlinofchaos’s picture

This workflow leaves off what happens after Save. In the normal workflow, it does a drupal_goto (back to the page you're looking on).

-- Merlin

[Point the finger: Assign Blame!]
[Read my writing:]
[Read my Coding blog: Angry Donuts]

-- Merlin

[Read my writing:]
[Read my Coding blog: Angry Donuts]

jp.stacey’s picture

Hi Merlin,

Thanks for the feedback. Does the drupal_goto() call the same URL as was originally visited, by default? Is there any control over that with a FAPI property?

I'll check with KarenS what to do, as she has documentation-fu.


J-P Stacey, software gardener, Magnetic Phield

merlinofchaos’s picture

Yes, and there are ways to control it:

The submit function can return the destination to go to

$form['#redirect'] -- set to the destination or to NULL to disable redirects

-- Merlin

[Read my writing:]
[Read my Coding blog: Angry Donuts]

-- Merlin

[Read my writing:]
[Read my Coding blog: Angry Donuts]

jakeg’s picture

This seems great (just printed off a copy) but it seems to be missing hook_prepare, no?

School and university yearbooks

ax’s picture

great chart. one note: drupal_retrieve_form($form_id) doesn't necessarily call hook_form(), but any form building function like $form_id. or, if that doesn't exist, a result of hook_forms():

function drupal_retrieve_form($form_id) {
  // ...
  // We first check to see if there's a function named after the $form_id.
  // If there is, we simply pass the arguments on to it to get the form.
  if (!function_exists($form_id)) {
    // In cases where many form_ids need to share a central builder function,
    // such as the node editing form, modules can implement hook_forms(). It
    // maps one or more form_ids to the correct builder functions.
    // We cache the results of that hook to save time, but that only works
    // for modules that know all their form_ids in advance. (A module that
    // adds a small 'rate this comment' form to each comment in a list
    // would need a unique form_id for each one, for example.)
    // So, we call the hook if $forms isn't yet populated, OR if it doesn't
    // yet have an entry for the requested form_id.
    if (!isset($forms) || !isset($forms[$form_id])) {
      $forms = module_invoke_all('forms', $saved_args);
    $form_definition = $forms[$form_id];
    if (isset($form_definition['callback arguments'])) {
      $args = array_merge($form_definition['callback arguments'], $args);
    if (isset($form_definition['callback'])) {
      $callback = $form_definition['callback'];
  // If $callback was returned by a hook_forms() implementation, call it.
  // Otherwise, call the function named after the form id.
  $form = call_user_func_array(isset($callback) ? $callback : $form_id, $args);
  // ...
edgar83’s picture

In the workflow '#afterbuild' should be '#after_build' with an underscore.

burningdog’s picture

A correction on the diagram: "presave" is called just before saving the node, and once it's done, one of "update" (if the node has been edited), "insert" (if the node has just been created), or "delete" (if the node has just been deleted) is called. This diagram caused me some confusion when I expected form elements to be saved if I changed them in hook_nodeapi in "insert", since it shows that the node is only saved after the "insert", which it is not. The diagram also leaves out when "update", "load", "prepare", "view" and "alter" are called in hook_nodeapi (for a full list of possible actions implemented in hook_nodeapi, take a look at )

A helpful description of which action runs when is found in hook_hook_info():
'presave' runs 'When either saving a new post or updating an existing post'
'insert' runs 'After saving a new post'
'update' runs 'After saving an updated post'
'delete' runs 'After deleting a post'
'view' runs 'When content is viewed by an authenticated user'

sergh27’s picture

How to create a profile when the form is submitted

theresonant’s picture

I noticed that when I want to edit a node, and that node has a custom element in its editing form (custom as in defined by me), the custom element does not have its #default_value copied into #value by the time it reaches the theme function (I set a breakpoint just when it reached the theme function). Here's what I'm doing:

 * Implementation of hook_elements()
function stringtree_elements() {
  $type['stringtree'] = array(
    '#input' => TRUE,  
    '#submit' => array('stringtree_submit'),

 * Implementation of hook_theme()
function stringtree_theme() {
  $theme = array();
  $theme['stringtree'] = array( 'arguments' => array('element' => NULL));
  return $theme;

 * Theme function for the stringtree element, using a simple textarea.
function _theme_stringtree_textarea($element) {
  return theme('textarea', $element);

And the form that uses this 'stringtree' element:

function doctemplate_form(&$node, $form_state) {
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => 'Title',
    '#default_value' => $node->title,
    '#required' => TRUE,
  $form['body'] = array(
    '#type' => 'stringtree',   // here's the problem, see below
    '#title' => 'Code',
    '#default_value' => $node->body,
    '#required' => TRUE,
    '#rows' => 20,
  return $form;

Nothing fancy here. I open the browser, go to the form to create a node of type 'doctemplate', I add a bunch of text into the 'stringtree' element of the form (which, as the theme function says, is a boring textarea). I click save, it gets into the database. When I go to the "Edit" tab, though, only the Title field actually has contents. The Code field (which uses my 'stringtree') is empty.

Even stranger: if, in the 'doctemplate_form()' function, I replace 'stringtree' with 'textarea' and refresh the page, the text in $node->body fills the textarea. Why doesn't it happen for the 'stringtree'? As I said, I placed a breakpoint in the theme_stringtree() function, and the $element array had no #value field (only #default_value).

Any suggestions? I started stepping through all the process followed by the form API, watching the form while having 'textarea' for the Code field, trying to see when exactly the #value appears, but I'm still not done yet :)

theresonant’s picture

It was this piece, from

  if (isset($form['#input']) && $form['#input']) {
    _form_builder_handle_input_element($form_id, $form, $form_state, $complete_form);

This didn't get called, because my element had no $form['#input'], and I didn't understand why. Guess what. I wasn't returning $type in hook_elements(). See code in previous comment. Stupid.

Should this be rather in a forum? Please tell me if so, and I'll remove it from this page.

mortona2k’s picture

This doesn't mention element validation. What happens first, element or form, and is this different for D7?

The other Andrew Morton

mossill’s picture

thank you for this chart. this is awesome!

rayvaughn’s picture

I'm looking for a processing flow diagram for form generation in Drupal 7. Can anyone help with a link?

jp.stacey’s picture

Sounds like there's demand for a D7 version (and maybe a D8 one too, eventually): along with the comments here, there've been requests on #drupal-uk IRC.

I'd like to do one at some point, but if anyone else has got the urge or wants to help me out then do get in touch.

(Golly, this page takes me back. In case anyone's interested, the diagram was originally on, but a documentation page is definitely the best place for it.)

J-P Stacey, software gardener, Magnetic Phield

jp.stacey’s picture

Here are some notes I just cobbled together for the D7 version:

1. drupal_build_form (wrapped by drupal_get_form())

  • merge in form_state_defaults()
  • return if batch processing: drupal_rebuild_form($form_id, $_SESSION['batch_form_state'])
  • try cache: form_get_cache($form_state['input']['form_build_id'])
  • otherwise
    • drupal_retrieve_form()
      • array from hook_forms() callback or $form_id()
    • drupal_prepare_form()
      • generate #form_build_id and optional #token
      • form defaults incl. #tree, #method, #validate, #submit, #theme
      • hook_form_alter
      • hook_form_BASE_FORM_ID_alter
      • hook_form_FORM_ID_alter
  • with cached or new form: drupal_process_form()
    • with form_builder()
      • element_info(#type)
      • user #input
      • #process happens on the way down with recursion
      • set any values, inherit any #disabled, #access etc.
      • (recurse into children with form_builder())
      • #after_build happens on the way back up with recursion
    • form submission? drupal_validate_form()
      • #token
      • _form_validate()
        • #maxlength
        • #options
        • #required
        • validate:
          • $form_state['#validate_handlers']
          • or
          • $form['#validate']
        • #element_validate
      • no errors?
        • #submit
        • cache clear
        • batch process
        • drupal_redirect_form()
      • drupal_form_submit() - quit now
      • multistep: $form_state['rebuild']
        • drupal_rebuild_form()
    • form/form state caching

2. drupal_rebuild_form()

  • drupal_retrieve_form()
  • AJAX / partial form - reuse #build_id, fix #action
  • drupal_prepare_form()
  • cache earlier than drupal_process_form()
  • form_builder()
  • [unlike drupal_process_form(), no submission validation]

3. drupal_form_submit()

  • merge in form_state_defaults()
  • drupal_retrieve_form()
  • (always submit; suppress errors)
  • drupal_prepare_form()
  • drupal_process_form()

J-P Stacey, software gardener, Magnetic Phield

texas-bronius’s picture

This Drupal Workflow module Form API process workflow diagram is Gold! But I think it's for an older version of Workflow. Can someone confirm? And is there one for a current version?

80s themed Drupal T-Shirts