When we want to make data available to Views, we need to define it in a way that Views can interpret. For content entities, the EntityViewsData::getViewsData() method accomplishes this, making data accessible for Views. However, when working with custom data, we use hook_views_data() to define how Views should handle it.
In the context of Views, a “field” isn’t limited to actual entity fields or database fields. It can represent any individual piece of data, whether it’s a column in a table, a property from an API, or even other sources. Similarly, the concept of a “table” in Views doesn’t have to be a traditional database table—it could refer to an external resource or a custom data structure.
If we’re working with a database table, Views knows how to query it directly, simplifying things. But if our data source is external, we’d need to implement the logic for querying it ourselves, typically by creating a custom ViewsQuery plugin to handle the data retrieval.
Here’s an example of creating a custom query with ViewsQuery to retrieve data from an external source.
Step 1: Create a Custom ViewsQuery Plugin
To start, we’ll create a plugin that extends ViewsQuery and defines the logic for interacting with an external API. Let’s assume we have an API that returns data in JSON format.
First, we define the file src/Plugin/views/query/ExternalApiQuery.php in our module:
namespace Drupal\my_module\Plugin\views\query;
use Drupal\views\Plugin\views\query\ViewsQueryBase;
use Drupal\views\ViewExecutable;
use Drupal\views\ResultRow;
/
* Defines a custom Views query plugin for external API.
*
* @ViewsQuery(
* id = "external_api_query",
* title = @Translation("External API Query"),
* help = @Translation("Query data from an external API.")
* )
*/
class ExternalApiQuery extends ViewsQueryBase {
/
* {@inheritdoc}
*/
public function execute(ViewExecutable $view) {
// Perform the request to the external API.
$response = \Drupal::httpClient()->get('https://api.example.com/data');
$data = json_decode($response->getBody(), TRUE);
// Prepare results for Views.
foreach ($data as $item) {
$row = new ResultRow();
// Bind data to fields defined in hook_views_data().
$row->field_name = $item['field_name'];
$row->another_field = $item['another_field'];
$view->result[] = $row;
}
}
}Step 2: Implement hook_views_data
Next, we need to register the data for Views so it recognizes them as available fields. This is done with hook_views_data():
/
* Implements hook_views_data().
*/
function my_module_views_data() {
$data = [];
// Define a virtual table 'external_data' that doesn’t exist in the database.
$data['external_data']['table'] = [
'group' => t('External Data'),
'provider' => 'my_module',
'base' => [
'field' => 'field_name',
'title' => t('External Data'),
'help' => t('Data from an external API.'),
],
];
// Define fields for this table.
$data['external_data']['field_name'] = [
'title' => t('Field Name'),
'help' => t('A field from the external API.'),
'field' => [
'id' => 'standard',
],
];
$data['external_data']['another_field'] = [
'title' => t('Another Field'),
'help' => t('Another field from the external API.'),
'field' => [
'id' => 'standard',
],
];
return $data;
}Step 3: Use in Views
After enabling the my_module, you can create a new view in the Views section and select the data source from the external API. In the Views interface, choose External API Query** as the query method, and add the Field Name and Another Field as fields.
This approach allows you to customize API queries, process the retrieved data, and provide it to Views as if it were standard Drupal fields, making them available for sorting, filtering, and display.
Comments4
It's worth noting that there…
It's worth noting that there are contributed modules that may be the best place to start: views_database_connector, views_json_source, and views_csv_source
You’re absolutely right 👍 —…
You’re absolutely right 👍 — before rolling your own
@ViewsQueryplugin, it’s worth checking whether an existing contributed module already solves most of the problem.Lets you connect Views directly to an external database (MySQL, PostgreSQL, etc.). If your data lives in another DB, this is often the simplest solution.
Lets you pull data from a JSON feed or REST API into Views. For your example with
https://jsonplaceholder.typicode.com/comments?postId=1, this module could expose that endpoint to Views without writing custom query plugins.Similar idea, but for CSV files. Useful if your external system exports CSV instead of offering an API.
✅ These modules give you a starting point with less custom code.
⚡ But if you need fine-grained control, custom
hook_views_data()+@ViewsQueryis still the way to go.Error: the external table is not found
After implementing your solution, I get the following error just above the Views' preview window:
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'testable.external_data' doesn't exist: SELECT DISTINCT "external_data"."id" AS "external_data_id", "external_data"."field_name" AS "field_name" FROM "external_data" "external_data" LIMIT 5 OFFSET 0; Array ( )
You mentioned "In the Views interface, choose External API Query**", are you referring to the query settings in the Advanced panel? If yes, I do not have any such selection appearing for me there.
Note: I also noticed that fields defined in the views_data() hook cannot be added as filter fields.
This is a common pitfall…
This is a common pitfall when first experimenting with custom data sources in Views. Let me break down what’s happening:
Why you’re seeing
ExternalDataQueryplugin extendsSql, which means Views assumes there’s a real SQL table calledexternal_data.SELECT … FROM external_dataquery.Two important clarifications
Sqlif you don’t have a database table.Instead, you need to extend
ViewsQueryBase(non-SQL query) and implement your own logic inexecute().They must be connected via
hook_views_data()where you define a base table withquery_id = "external_data_query".Without this, Views doesn’t know that your plugin should handle the “table”.
Minimal working pattern
1. Define query plugin
👉 Notice: extends
ViewsQueryBase(notSql).2. Define
hook_views_data()👉 Critical difference:
query_idbinds your fake “table” to theExternalDataQueryplugin.3. Add it in Views
External Data Queryshould now appear as a selectable data source.idandname.About “fields not available as filters”
That’s expected unless you define
filterhandlers inhook_views_data():Without those, Views only knows how to display the fields, not how to filter/sort them.
✅ So the fix:
ViewsQueryBaseinstead ofSql.query_idto your base table definition.hook_views_data().