4.6.x core.php hook_user($op, &$edit, &$user, $category = NULL)
4.7.x core.php hook_user($op, &$edit, &$account, $category = NULL)
5.x core.php hook_user($op, &$edit, &$account, $category = NULL)
6.x core.php hook_user($op, &$edit, &$account, $category = NULL)

Act on user account actions.

This hook allows modules to react when operations are performed on user accounts.


$op: What kind of action is being performed. Possible values (in alphabetical order):

  • after_update: The user object has been updated and changed. Use this (probably along with 'insert') if you want to reuse some information from the user object.
  • categories: A set of user information categories is requested.
  • delete: The user account is being deleted. The module should remove its custom additions to the user object from the database.
  • form: The user account edit form is about to be displayed. The module should present the form elements it wishes to inject into the form.
  • insert: The user account is being added. The module should save its custom additions to the user object into the database and set the saved fields to NULL in $edit.
  • load: The user account is being loaded. The module may respond to this and insert additional information into the user object.
  • login: The user just logged in.
  • logout: The user just logged out.
  • register: The user account registration form is about to be displayed. The module should present the form elements it wishes to inject into the form.
  • submit: Modify the account before it gets saved.
  • update: The user account is being changed. The module should save its custom additions to the user object into the database and set the saved fields to NULL in $edit.
  • validate: The user account is about to be modified. The module should validate its custom additions to the user object, registering errors as necessary.
  • view: The user's account information is being displayed. The module should format its custom additions for display, and add them to the $account->content array.

$edit: The array of form values submitted by the user.

$account: The user object on which the operation is being performed.

$category: The active category of user information being edited.

Return value

This varies depending on the operation.

  • categories: An array of associative arrays representing the categories added by the implementing module. Each array can have the following keys:

    • name: The internal name of the category.
    • title: The human-readable, localized name of the category.
    • weight: An integer specifying the category's sort ordering.
    • access callback: Name of a menu access callback function to use when editing this category. Defaults to using user_edit_access() if not specified. See hook_menu() for more information on menu access callbacks.
    • access arguments: Arguments for the access callback function. Defaults to array(1) if not specified.
  • delete: None.
  • form, register: An array containing the form elements to add to the form.
  • insert: None.
  • load: None.
  • login: None.
  • logout: None.
  • submit: None.
  • update: None.
  • validate: None.
  • view: None.

Related topics

20 functions implement hook_user()

Note: this list is generated by pattern matching, so it may include some functions that are not actually implementations of this hook.

block_user in modules/block/block.module
Implementation of hook_user().
blogapi_validate_user in modules/blogapi/blogapi.module
Ensure that the given user has permission to edit a blog.
blog_feed_user in modules/blog/blog.pages.inc
Menu callback; displays an RSS feed containing recent blog entries of a given user.
blog_page_user in modules/blog/blog.pages.inc
Menu callback; displays a Drupal page containing recent blog entries of a given user.
blog_user in modules/blog/blog.module
Implementation of hook_user().

... See full list


developer/hooks/core.php, line 2438
These are the hooks that are invoked by the Drupal core.


function hook_user($op, &$edit, &$account, $category = NULL) {
  if ($op == 'form' && $category == 'account') {
    $form['comment_settings'] = array(
      '#type' => 'fieldset',
      '#title' => t('Comment settings'),
      '#collapsible' => TRUE,
      '#weight' => 4,
    $form['comment_settings']['signature'] = array(
      '#type' => 'textarea',
      '#title' => t('Signature'),
      '#default_value' => $edit['signature'],
      '#description' => t('Your signature will be publicly displayed at the end of your comments.'),
    return $form;


kiamlaluno’s picture

Drupal 7 doesn't use anymore hook_user(), but it uses a set of new hooks; see Converting 6.x modules to 7.x for more details.

asiby’s picture

If at least one module redirect Drupal while $op == "login" in hook_user(), then the other implementation of the hook will never be called.

For instance, if you use drupal_goto(), than no other hook_user will be called.



xurizaemon’s picture

Any use of drupal_goto() in a hook implementation will prevent later implementations of the same hook being called, because the final line of drupal_goto() is exit() ...

boneless’s picture

foo_user($op, &$edit, &$account, $category = NULL) {}

make sure you are using the "&" for &$account otherwise anything you change in $account will not be loaded with the user object.

DanChadwick’s picture

To save new information for the user in the user table's "data" field, upon insert, you need to:

switch ($op) {
  case 'insert': {
     $account->field_whatever = your_value;
     $edit['field_whatever'] = NULL;

This is documented above, but not completely explicitly. The $edit['field_whatever'] will cause field_whatever to be picked out of the $account object, serialized and stored in the user table.

Hope this helps the next person poking around in this area.

dalguete’s picture

I did the opposite, to make it work:

switch ($op) {
  case 'insert': {
     $account->field_whatever = NULL;
     $edit['field_whatever'] = your_value;

NULL in the $account field_whatever property, and the value in the $edit array. Am I wrong? Because I understood the concept the other way, when I read the docs and your comment.

UPDATE: Better than that, I only had to set the value in the $edit array:

switch ($op) {
  case 'insert': {
     $edit['field_whatever'] = your_value;
DanChadwick’s picture

As stated above correctly, for insert, the value in $account is ignored and the value in $edit is written to {users}, unless the value is NULL (in which case it is not included). See user.module / user_save():

    // Build and save the serialized data field now.
    $data = array();
    foreach ($array as $key => $value) {
      if ((substr($key, 0, 4) !== 'auth') && ($key != 'roles') && (!in_array($key, $user_fields)) && ($value !== NULL)) {
        $data[$key] = $value;
    db_query("UPDATE {users} SET data = '%s' WHERE uid = %d", serialize($data), $user->uid);
tmsimont’s picture

For 'insert' and 'update', changing either $account or $edit in any way described here only affects what is serialized into the user table's data field, not the user profile module's profile_values table.

This means that trying to alter the user this way will break any views or anything else you're doing that might involve profile_load_profile(), which utilizes the profile_values table.

So basically altering the user (at least as 'update') completely breaks the connection between the user edit form and what's actually going on in the profile_values table, because the profile_values table does not get updated along with the users table data cell.

HudsonKane’s picture

is it possible to unset validation errors in hook_user 'validate' since there is no $form_state? If a custom user form field is filled out a certain way I want to allow a required field to be empty. I can see the error set in my validation function doing print_r(form_get_errors());
Array ( [mymod_maybe_required] => This field is usually required, but not always field is required.) If I reset the error with form_set_error('mymod_maybe_required','',TRUE);
then print_r(form_get_errors()); shows an empty array, but the error message still displays
Thank You

HudsonKane’s picture

I think I found the solution here should anyone else have the need

firebus’s picture

you can't return a FAPI array like $form['account']['my_field'] because rather than being appended to $form['account'], it will replace $form['account'];

you need to return $form['my_field'] which will then, possibly annoyingly, result in a fieldset being placed around the account fields.

hook_form_alter is also an option here.

Garrett Albright’s picture

When using this function with the 'view' $op parameter, what needs to be added to $account->content should be in a format similar to that used by Form API, like this:

$account->content['my_user_info_section'] = array(
  // This outer user_profile_category item defines a "section" for your content
  // to appear in.
  '#type' => 'user_profile_category',
  '#title' => t('Title of my section'),
  '#weight' => 10,
  'child_element' => array(
    // For child elements, use the user_profile_item type.
    '#type' => 'user_profile_item',
    // Finally, you can add whatever mark-up you need in this element's #value.
    '#value' => '

' . t('This user has a UID of @uid.', array('@uid' => $account->uid)) . '

', ), ), );
Duncan.HY’s picture

I am new to Drupal. So please correct me if I am wrong.

Roles can be set at this stage by
It seems that it is the key:rid that counts.
see also: http://drupal.org/node/137989

If you use profile to create extra fields, you would like to save it to db table profile_values via your own code. Otherwise, those values will be saved to user.data instead. If you browse the user info, everything seems to be OK until you write code to retrieve profile value from the database.

hargobind’s picture

I'm working on some code to delete nodes when a user is deleted.

I found out that $op 'delete' runs AFTER a user has already been deleted and all nodes by that user have been set to UID=0. What this means is that you can't perform a delete on nodes when hook_user($op = 'delete') is called.

This is not new behavior. The example code below was originally presented in the thread at #218189

//This doesn't work because the order of operations:
//  The user gets deleted, all their nodes are set to uid = 0,
//  and THEN this hook is called
    case 'delete':
      $query = db_query("SELECT nid FROM {node} WHERE uid = %d", $account->uid);
      while ($row = db_fetch_object($query)){

A viable solution using hook_form_alter() to perform actions BEFORE a user is deleted was proposed at #1043200. However, that example was a little incomplete since it only handled deleting users from the User Management Admin page. Below is a more complete example which works on both the User Management form and when a user is deleted right from the User Edit form.

 * Alter the user delete confirmation form so we can perform actions prior to the users being deleted.
function mymodule_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'user_multiple_delete_confirm' || $form_id = 'user_confirm_delete') {
    //- Move our handler before the regular form submit. -//
    array_unshift($form['#submit'], '_mymodule_preprocess_user_delete');

  if ($form_id == 'user_confirm_delete') {
    //- Change the form description since we are no longer switching nodes to anonymous. -//
    $form['description']['#value'] = t('All submissions made by this user will be deleted. This action cannot be undone.');

 * Perform actions before users are deleted.
function _mymodule_preprocess_user_delete($form, $form_state) {
  //- Get the UIDs to act on. -//
  if ($form['form_id']['#value'] == 'user_multiple_delete_confirm')
    $uids = $form_state['values']['accounts'];
  else if ($form['form_id']['#value'] == 'user_confirm_delete')
    $uids = array($form_state['values']['_account']->uid);
  foreach ($uids as $uid) {
    //- Delete user-created nodes. -//
    $nodes = db_query("SELECT nid FROM {node} WHERE uid = %d", $uid);
    while ($nid = db_result($nodes)) {

It should be noted that nodes will only be deleted if the account doing the user delete also has access to delete nodes of that type. So make sure to check the node permissions for that user role.

akalata’s picture

I'm trying to do the same thing -- this explains why my Rule wasn't firing.

fuzzy76’s picture

Not really. What happens is that node.module's hook_user implementation (which is what reassigns the nodes) is called before yours. Changing your module's weight in the system table will change this behaviour AFAIK.

jduhls’s picture

I can't update the username to match the email address. Ideas why? This doesn't work at all:

function mymodule_user($op, &$edit, &$account, $category = NULL){
	if($op == 'insert' || $op == 'update' || $op == 'submit') {
		if($account->name != $account->mail) {
			$edit['name'] = $edit['mail'];
			$account->name = $account->mail;
mattcasey’s picture

Try 'validate' for $op instead to alter the user name.

Also keep in mind if you use something like content_profile for registration, your CCK fields will only show up here on registration -- not when editing an existing account!

focal55’s picture

Thank you mattwad, by targeting the 'validate' $op I was able to modify the username of a newly registered user.

RealName Registration, Auto Username and Email Registration are all great modules for automating the creation of usernames in Drupal 6 but because I was using Content Profile with my registration process I was not able to get my CCK fields to populate the username.

From my testing, it seems that hook_user runs before any custom submit functions added to the FAPI #submit callbacks. The node created by Content Profile gets generated via a custom #submit callback (content_profile_registration_user_register_submit) which makes it not present during hook_user's 'insert' operation. However it is present at the 'validate' operation. So along with jduhls and mattwad's post I was able to add this to my custom module's implementation of hook_user:

 * Implementation of hook_user().
function MYCUSTOMMODULE_user($op, &$edit, &$account, $category = NULL) {
  switch ($op) {
    case 'validate':
    $edit['name'] = $edit['field_profile_first_name'][0]['value'].'.'.$edit['field_profile_last_name'][0]['value'];
    $account->name = $edit['field_profile_first_name'][0]['value'].'.'.$edit['field_profile_last_name'][0]['value'];

My goal was to get the CCK field 'field_profile_first_name' and 'field_profile_last_name' to be concatenated with a period in the middle of them.