Tables are described to Views by implementing hook_views_data(). This should be placed in and it will be autoloaded (see Views' API). The hook implementation should return an array of table information, keyed by the name of the table. For example, if your module is describing three tables, 'foo', 'bar' and 'baz', your hook will look like this:
   * Implements hook_views_data().
  function MODULENAME_views_data() {
    $data = array(
      'foo' => array(
        // about the table named 'foo'...
      'bar' => array(
        // about the table named 'bar'...
      'baz' => array(
        // about the table named 'baz'...
    return $data;
The key should be the actual database name of the table (not including prefix), but it can be an alias as long as the join information (explained later) contains the real name of the table. Each item in the array should be a field in the table, with the exception of a special information section called 'table'. Example:
$data['foo'] = array(
  'table' => array(
    // ... info about the table, described later ...
  'bar' => array(
    // ... info about the field named 'bar', i.e,,
  'baz' => array(
    // ... info about the field named 'baz', i.e, foo.baz,
Once you get down to an array that contains actual data, that piece of the array will often be referred to as the definition.

The 'table' section

Each table should have a 'table' section in it, which is used to set default information for the table, such as the group, as well as the very important joins and whether or not this is a base table. First, there are several items that are actually for fields but can be placed here so that all fields within the table inherit them:
The name of the group this item will be with. In the UI, this is displayed as Group: Title. For example, "Node: Node ID", "Taxonomy: Term description", etc. It is important to be consistent with groups, because the UI sorts by group, and allows filtering by group to find fields as well.
The actual name of the field; it should be concise and descriptive.
A longer description to help describe what the field is or does. It should try to be only a line or two so as not to clutter the UI.
In general, having 'title' and 'help' at the table level doesn't make a lot of sense, but usually every item in a table is in the same group. Thus it is very common to define the 'group':
  $data['foo']['table']['group'] = t('Foo');
The other items in the 'table' section are described in the following sections.

'base': Base table

If your table is a base table -- meaning it can be the primary, central table for a View to use, you can declare it to be a base table. This primarily provides UI information so that it can be selected. For example:
  // Advertise this table as a possible base table
  $data['node']['table']['base'] = array(
    'field' => 'nid',
    'title' => t('Node'),
    'help' => t("Nodes are a Drupal site's primary content."),
    'weight' => -10,
The following items are available in the base section:
The primary key field for this table. For Views to treat any table as a base table, it must have a primary field. For node this is the 'nid', for users this is the 'uid', etc. Without a single primary key field (i.e. not a composite key), Views will not be able to utilize the table as a base table. If your table does not have a primary key field, it is not too difficult to just add a serial field to it, usually.
The title of this table in the UI. It should be singular and describe the object that this table contains from the perspective of the user.
A short piece of text to describe what object this table contains.
If this table is held in a different database from your Drupal database, specify it as a string in the exact same format as the settings.php file. This is a special purpose variable that will probably be only used in site specific code, and it must be the same database type as your Drupal database. Also, don't try to join it to any table that isn't in the same database. That'll just create all kinds of silly errors. For example:
  // In settings.php for your site
  // Your drupal (site) database needs to be called 'default'
  $db_url['default'] = 'mysqli://user:pass@host/drupal_db';
  $db_url['budget'] = 'mysqli://user:pass@host/other_db';
Then when you are describing the external database in your base table you would write something like this:
  $data[$table]['table']['base'] = array(
    'field' => 'Primary key',
    'title' => t('Field name'),
    'help' => t('Field description'),
    'database' => 'budget',
    'weight' => -10,

'join': Linking your table to existing base tables

For Views to use your table, it has to either be a base table, or know how to link to an existing base table. Or sometimes both. Views uses this information to create a path to the base table; when the table is added to the query, Views will walk along this path, adding all tables required into the query.
How taxonomy_term_data joins to node
In the above example, to use these with 'node' as the base table, both 'taxonomy_term_data' and 'term_node' need to be defined, and they each need a join handler for node:
$data['taxonomy_term_data']['table']['join']['node'] = array(
  'left_table' => 'term_node',
  'left_field' => 'tid',
  'field' => 'tid',
The above can be read as "In order to join to the node table, the taxonomy_term_data table must first link to the term_node table, and they join on the 'tid' field.". When adding this table to the query for a node view, Views will look at this and then look for the term_node table.
$data['term_node']['table']['join']['node'] = array(
  'left_field' => 'nid',
  'field' => 'nid',
Above, the fact that 'left_table' is left out lets us know that term_node links directly to the node table, using the 'nid' field on both sides of the join. Quite a few more fields are available in this definition:
The name of the handler object to use. Defaults to 'views_join'. You may create custom join handlers that may or may not use any of the data below, as they see fit.
Table to join. This is optional, and should only be used if the table being referenced is an alias.
Field to join on. This is required.
The next step toward the final destination. If this is the final destination it may be omitted.
The field to join to on the left side. This is required.
Either LEFT (default) or INNER.
Either a string that's directly added, or an array of items. Each item is, itself, an array:
table should not be set in most cases, as it would be filled with the right table alias. Set it to NULL if you want to use a formular in "field"
Field or formula, therein "%alias" can be used to reference the right table.
Similar to filters, this is the operator, such as >, <, =, etc. Defaults to = or IN.
Must be set. If an array, operator will be defaulted to IN.
If true, the value will not be surrounded in quotes, and %d will be used for its placeholder.
extra type
How all the extras will be combined. Either AND or OR. Defaults to AND.

Describing fields on tables

Aside from the special table tag, each table can also have an unlimited number of field designations; these correspond roughly to fields on the table, though it is very common to use non-fields to display data that isn't directly in a field, such as data arrived from formulae, or special links related to the object the table is part of. Each field is described in the view data with an array, keyed to the database name of the field. This array may contain some information fields, plus an entry in each of the five types of items Views has per field: argument, field, filter, relationship, sort. For example:
$data['node']['nid'] = array(
  // The item it appears as on the UI,
  'title' => t('Nid'),
  // The help that appears on the UI,
  'help' => t('The node ID of the node.'),
  // Information for displaying the nid
  'field' => array(
    'handler' => 'views_handler_field_node',
    'click sortable' => TRUE,
  // Information for accepting a nid as an argument
  'argument' => array(
    'handler' => 'views_handler_argument_node_nid',
    // The field to display in the summary.
    'name field' => 'title',
    'numeric' => TRUE,
    'validate type' => 'nid',
  // Information for accepting a nid as a filter
  'filter' => array(
    'handler' => 'views_handler_filter_numeric',
  // Information for sorting on a nid.
  'sort' => array(
    'handler' => 'views_handler_sort',
The above example describes the 'nid' field on the 'node' table, providing 4 of the 5 handlers. Note that while field is normally expected to be the database name of the field, it doesn't have to be; you can use an alias (which is how you get multiple handlers per field) or something completely made up for items that aren't tied to the database. For example:
$data['node']['edit_node'] = array(
  'field' => array(
    'title' => t('Edit link'),
    'help' => t('Provide a simple link to edit the node.'),
    'handler' => 'views_handler_field_node_link_edit',
The above handler definition an edit link to a node, but this isn't a field in and of itself. For aliased fields, here is another example:
$data['users']['uid_current'] = array(
  'real field' => 'uid',
  'title' => t('Current'),
  'help' => t('Filter the view to the currently logged in user.'),
  'filter' => array(
    'handler' => 'views_handler_filter_user_current',
The above definition provides an alternate filter handler on the uid field for the current user. The following items are allowed in the field definition:
group, title, help
As above, these fields are for the UI. If placed here, any of these fields will override a setting on the base table.
real field
If this field is an alias, the "real field" may be placed here, and the handler will never know the difference.
A handler definition for the "Field" section, which is a field that may be displayed in a view. The definition is an array; the contents of the array are completely up to the handler, other than the 'handler' definition. If omitted, handler will default to 'views_handler_field'.
A handler definition for the "Filters" section, which will be used to apply WHERE clauses to the view. The definition is an array; the contents of the array are completely up to the handler, other than the 'handler' definition. If omitted, handler will default to 'views_handler_filter'.
A handler definition for the "Sort criteria" section, which will be used to add an ORDER BY clause to the view. The definition is an array; the contents of the array are completely up to the handler, other than the 'handler' definition. If omitted, handler will default to 'views_handler_sort'.
A handler definition for the "Field" section, which is a way to bring in new or alternative base tables in the view. The definition is an array; the contents of the array are completely up to the handler, other than the 'handler' definition. If omitted, handler will default to 'views_handler_relationship'. All relationships need the 'base' to be set. The basic relationship handler also requires 'base field' to be set. 'base' and 'base field' represent the "right" half of the join that will use this field as the left side.
A handler definition for the "Field" section, which is method of accepting user input from the URL or some other source. The definition is an array; the contents of the array are completely up to the handler, other than the 'handler' definition. If omitted, handler will default to 'views_handler_argument'.
For more information about what handlers need/use what data, visit the Views API site and check out the available handlers.

Support old tabledata

If you need to rename some tables/fields you can create some references in the views data to be able to continue to work. Therefore you create the whole table structure of the current views data. If you have to rename a single table you need to specify
$data['oldtable']['moved to'] = 'newtable';
If you have to rename/move a single a field to another table you specify
$data['oldtable']['oldfield']['field']['moved to'] = array('newtable', 'newfield');
$data['oldtable']['oldfield']['moved to'] = array('newtable', 'newfield');