Create Custom SQL APIs in Drupal
The Create SQL API feature allows administrators to expose Drupal database records through secure REST API endpoints without writing any code. Using the visual Query Builder, you define the source table, joins, filter conditions, calculated fields, grouping, sorting, and output options — the module builds and executes the SQL query for you and returns the results as JSON.
1. Basic Configuration
- Navigate to the Create SQL API tab of the module and click the + Create SQL API button.
| Field | Description |
| API Name | A descriptive name for the API. Must be unique across all SQL APIs. |
| Method | HTTP method for the endpoint. GET — clients pass filter values via the query string (?param=value). POST — clients send filter values as a JSON request body. GET is available in the Community version; POST is a Premium feature. |
| Endpoint | The slug part of the endpoint. The final URL becomes https://<your-site>/custom-api/<slug>. The slug is automatically normalized to lowercase letters, numbers, and underscores, and must be unique. |
| Authentication Providers | Select one or more authentication mechanisms required to access the API (e.g., Cookie, Basic Auth, or any other authentication provider enabled on the site). Leave empty to create a public API endpoint. |

2. Query Builder
The Query Builder allows you to create SQL-based API endpoints using a visual interface, eliminating the need to write SQL queries manually. By configuring tables, joins, conditions, and output settings, you define exactly what data the API returns.
Every section below supports multiple rows:
- Click + to add a new row.
- Click − to remove a row.
- Drag rows using the drag handle (Order column) to reorder them.
Once you select the From Table, all Column fields offer autocomplete suggestions, click into a field to see the available columns.
2.1 Select From Table
- Choose the database table that is the primary source of data for the API (the SQL FROM table). All other Query Builder sections build on this selection.
2.2 Joins
- The Joins section combines data from multiple tables into a single API response.
| Field | Description |
| Type | Join type: INNER, LEFT, RIGHT, or CROSS. |
| Table | The table to be joined (autocomplete suggests existing database tables). |
| Left Column | Column from the base table (or another joined table) used in the join condition. |
| Right Column | Matching column from the joined table. Autocomplete automatically switches to the joined table's columns. |
| Operator | Comparison operator for the ON clause: =, !=, <>, >, <, >=, <=. Typically =. |
When one or more joins are configured, every column reference in the Query Builder must use the table.column format (e.g., node.nid) so the module knows which table each column belongs to. Validation enforces this. Duplicate joins (same type, table, columns, and operator) are rejected.
2.3 Conditions
- Conditions filter the records before they are returned by the API.
| Field | Description |
| Type | where — all where rows must match (combined with AND). orWhere — all orWhere rows are combined into a single OR group. having — applied after Group By; use it to filter on aggregated values. |
| Column | The database column to filter. For having, this may also be a column or expression alias (e.g., cnt). |
| Operator | =, !=, <>, >, <, >=, <=, LIKE, NOT LIKE, IS NULL, IS NOT NULL. |
| Value | The value to compare against. Not required for IS NULL / IS NOT NULL. |
- Dynamic values from the request: Set the Value to a placeholder starting with a colon (e.g., :status) and the module resolves it at request time — from the query string for GET APIs (?status=1) or from the JSON body for POST APIs ({"status": 1}). If the request does not supply the parameter, the literal value is used as-is.
2.4 Expressions
- Expressions add calculated or aggregated values to the API response.
| Field | Description |
| Alias | The key under which the value appears in the API response. Required. Must start with a letter or underscore and contain only letters, numbers, and underscores. Must be unique across all columns and expressions. |
| Expression | A SQL expression, e.g. COUNT(node.nid), CONCAT(first_name, ' ', last_name), ROUND(price, 2). |
- Only the following SQL functions are allowed inside expressions:
- COUNT, SUM, AVG, MIN, MAX, CONCAT, CONCAT_WS, GROUP_CONCAT, COALESCE, IFNULL, NULLIF, IF, NOW, DATE, YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, DATE_FORMAT, STR_TO_DATE, DATEDIFF, TIMEDIFF, LENGTH, CHAR_LENGTH, UPPER, LOWER, TRIM, LTRIM, RTRIM, REPLACE, SUBSTRING, SUBSTR, LEFT, RIGHT, ROUND, FLOOR, CEIL, CEILING, ABS, MOD, CAST, CONVERT, DISTINCT.
- For security, statements and unsafe syntax are blocked — for example ;, --, /* */, UNION, SELECT, INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, TRUNCATE.
2.5 Columns
- The Columns section defines which fields are returned in the API response.
| Field | Description |
| Alias | Optional custom key for the field in the response. Same naming rules as expression aliases. |
| Column | The database column to return (use table.column format when joins are configured). |
- Only the selected columns (plus expressions) appear in the response.
- If no Columns and no Expressions are configured, all columns of the base table are returned. With joins, all columns of each joined table are also included, keyed as <join_table>__<column> to avoid name clashes.
2.6 Group By
- Group By combines records that share the same value, typically together with an aggregate expression (COUNT, SUM, …).
- Add one row per grouping column (e.g., node.type).
- A column or expression alias is also accepted.
- Use a having condition to filter the resulting groups.
2.7 Order By
- Order By controls the sorting of the returned records.
| Field | Description |
| Column | Column used for sorting. A column or expression alias (e.g., cnt) is also accepted. |
| Direction | ASC (ascending) or DESC (descending). |
- Multiple rows create a multi-level sort in the order listed.
2.8 Options
| Option | Description |
| DISTINCT | Returns only unique rows; duplicate records are removed from the response. |
| Limit | Maximum number of rows to return. 0 or empty = no limit. A ?size= request parameter overrides this value. |
| Offset | Number of rows to skip before returning results. |
- Once done, click the Save Query button (or Update Query when editing an existing API).

3. After Saving
- The API appears in the SQL API list, where you can:
- Copy the full endpoint URL with one click.
- View the saved configuration, including pagination help.
- Edit the API configuration.
- Test the API in a popup without leaving Drupal.
- Delete the API.
Calling the API
- GET https://<your-site>/custom-api/<slug>
- A successful response looks like:
{
"success": true,
"type": "SELECT",
"data": [
{ "type": "article", "cnt": "12" },
{ "type": "page", "cnt": "7" }
],
"count": 2
}
Pagination (GET APIs)
| Parameter | Purpose | Example |
| page | 1-based page number. Use together with size. | ?page=2&size=25 |
| size | Number of rows per page. Omit to return all rows (or the configured Limit). | ?size=25 |
| offset | 1-based starting row. Overrides page when provided. Use with size. | ?offset=11&size=10 |

4. Example: Content-Type Counts API
Goal: Return each content type and how many nodes it has, but only for types with more than 5 nodes, sorted by count.
- Basic Configuration: Name = Content type counts, Method = GET, Endpoint = type_counts.
- From Table: node
- Columns: Alias = type, Column = node.type
- Expressions: Alias = cnt, Expression = COUNT(node.nid)
- Group By: node.type
- Conditions: Type = having, Column = cnt, Operator = >, Value = 5
- Order By: Column = cnt, Direction = DESC
- Click Save Query, then call:
GET https://<your-site>/custom-api/type_counts
{
"success": true,
"type": "SELECT",
"data": [
{ "type": "article", "cnt": "12" }
],
"count": 1
}
Need any help?
If you face any issues or need any help in configuration, please feel free to reach out to us at drupalsupport@xecurify.com. You can also connect with us on the Drupal Slack channel.
Help improve this page
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion