Custom cards on native statistics dashboards
When none of the ready-made native statistics cards shows the metric you need, you can calculate it with your own query: SQL against the instance MySQL database or an aggregation pipeline against Mongo. You check the query with Test before saving, then put the saved card on a custom dashboard next to ready-made cards. Dashboard filters (period, channel, operator and so on) can be passed into the query.
When to use this
- You need a metric that no ready-made card has: your own grouping, field or breakdown.
- An analyst needs a table with columns of their choice and CSV/XLSX export.
- You want to check an idea on the instance data quickly, without ordering a new card.
What to know
- The feature is internal. Only the ConnectiveOne team enables it, and only for its own administrators; it is not enabled on client instances. It needs native statistics and custom dashboards.
- A query only reads data. One
SELECTorWITHquery is allowed. Data changes, several statements separated by;, system schemas (mysql,information_schema,performance_schema,sys) and functions such asSLEEPare rejected before the query runs. The database checks it a second time: the query runs in a read-only session. - Only the current instance database. Tables of another database on the same server (
other_db.table) cannot be read: such a query is rejected on test, on save and when the card is shown. Write tables of your database without a prefix or with the current database name. A table alias that matches the name of another database on the server is rejected too — give it another name. - Only visible characters in a MySQL query. Control characters (except tab and line break), invisible characters (zero-width, BOM, text direction marks) and non-standard spaces (non-breaking, narrow, etc.) are not allowed anywhere in the query, not even inside quotes: the check and the database could read such a query differently. These characters often come in when text is copied from documents or messengers — retype the spaces. If a value needs such a character, build it with
CHAR()(a non-breaking space isCHAR(0xC2A0 USING utf8mb4)) or pass it as a filter value. /*! … */comments with a version number (/*!50700 … */),/*M! … */comments and a comment inside such a comment are rejected when the server could read them differently depending on its version. Plain--,#and/* … */comments are allowed.- Mongo queries are strict JSON
{"collection": "…", "pipeline": [ … ]}. Write stages ($out,$merge), service stages,$where,$function,$accumulator,system.*collections and references to another database ({"db": …, "coll": …}in$lookup,$unionWith,$graphLookup) are not allowed. - Run limits: up to 30 seconds per query and up to 10,000 result rows. A larger result shows the first 10,000 rows and is marked as truncated; an export of it ends with a note row saying the data was cut. The same limits apply to the test, to the card on a dashboard and to exports.
- Card limits: name up to 120 characters, query up to 20,000 characters, up to 20 filter bindings, up to 200 custom cards per instance.
- One card, one source. You cannot combine MySQL and Mongo in one card.
- A card on a dashboard is a link. Change the query and every dashboard with the card shows the new data. Delete the card and dashboards show a grey Card unavailable tile in its place.
Access rights
An administrator grants the rights in Settings → Roles → Statistics.
| Right | What it gives |
|---|---|
| Custom dashboards → Save | Put existing custom cards on dashboards and see the data of any of them |
| Custom cards (together with dashboards Save) | Create, test, edit and delete custom cards and see their queries |
| Custom dashboards → View | See the data of custom cards that are on saved dashboards. Viewers never see the query text |
The Custom cards right is off by default and has no effect without dashboards Save.
Before you grant Custom cards: it lets the user read any non-system MySQL table or Mongo collection of the instance, with no restriction by role, skill group or operator. Grant it only to trusted internal users such as analysts.
Before you start
Steps: create a card
-
Open Menu → Statistics → Custom dashboards, choose a dashboard and click Edit.
-
Click + Cards and, in the Custom cards group, click + Custom card.
-
Enter a name and choose the source (MySQL or Mongo) and the look: number, table, bars, line, area, pie or rows.
-
Write the query. A MySQL example, rooms by channel:
SELECT kw_channel AS channel, COUNT(*) AS rooms FROM op_chat_rooms WHERE 1=1 [[AND {{period}}]] GROUP BY kw_channel -
If the card must follow the dashboard filters, add bindings (see below). In the example, the Date filter is bound to the
periodplaceholder and theop_chat_rooms.createdAtfield. -
Click Test. The query runs with the filters currently selected on the dashboard; the result appears below in the chosen look, with a line like «Done in … s · rows: N».
-
Click Save. The card appears in the Custom cards group and is added to a dashboard like a ready-made card.
A Mongo example, dialogs by channel:
{"collection": "statistics",
"pipeline": [
{"$match": "{{period}}"},
{"$group": {"_id": "$channel", "dialogs": {"$sum": 1}}},
{"$project": {"_id": 0, "channel": "$_id", "dialogs": 1}}
]}
Here the Date filter is bound to period and the createdAt field. Write dates in the query as {"$date": "2026-10-01T00:00:00Z"}.
Dashboard filters in the query
A filter gets into the query only through a binding: filter → placeholder → field. Write the placeholder in the query as {{name}} (lowercase Latin letters, digits, _). The field is column or table.column for MySQL and a field path without $ for Mongo.
| Filter | MySQL: {{tag}} becomes |
Mongo: "{{tag}}" becomes |
|---|---|---|
| Date (period) | field >= start AND field < end + 1 day |
{field: {$gte: …, $lt: …}} |
| One value | field = value |
{field: value} |
| Several values | field IN (…) |
{field: {$in: […]}} |
| Filter not selected | 1=1; a [[ … ]] block disappears completely |
{} |
- A
[[ … ]]block is an optional part of the query: when its filter is not selected, the block is removed. SoWHERE 1=1 [[AND {{period}}]]works with and without a period. - Filter values are escaped before they are substituted into the query, so quotes or special characters in a value cannot break it.
- Write
[[and]]only in the query itself, not inside a quoted string, a backtick name or a comment. Otherwise the query is rejected. - A placeholder without a binding is an «unknown placeholder» error.
- Time zone. For MySQL, the period bounds are calendar dates as they are stored in the table; the Time zone selector does not shift them, the same as in ready-made SQL cards of native statistics. For Mongo, the bounds are calculated in the time zone selected in the panel.
- A card without bindings ignores the dashboard filters and always shows the same result.
How the result becomes a chart
There are no axis settings; the look follows the types and the order of the result columns:
- bars, line, area, rows — a category or a date and numbers. The first non-numeric column becomes the category and the numeric columns become the values, wherever they stand in the result: in a Mongo
$projecta number field may come before the label. When all columns are numeric, or all are text, the first column is the category. For stacked bars return three columns: date, category, value; - pie — two columns: a label and a number, in any order;
- number — the first numeric column of the first row;
- table — all columns, with CSV/XLSX export.
Give columns clear aliases with AS: they become the headers. Two columns with the same name are an error.
Edit or delete a card
- In dashboard edit mode, open + Cards → Custom cards.
- Click the pencil next to a card to edit it, or ✕ to delete it. Before deleting, the system shows how many dashboards use the card.
If someone has just changed the same card, your save fails: reload the card and repeat your changes.
How to check it worked
- The test shows a result and a line like «Done in … s · rows: N».
- Compare the number with the same query in the database for the same dates.
- Change the period on the dashboard and click Apply: a card bound to the date changes its number, a card without bindings does not.
Troubleshooting
| What you see | What to do |
|---|---|
| «Query rejected: only one SELECT or WITH query is allowed» | Keep one query that starts with SELECT or WITH. A semicolon is allowed only at the very end |
| «Query rejected: forbidden word / function / schema …» | The query changes data, locks tables or reads system schemas. Rewrite it as a plain read |
| «Query rejected: query to another database: …» | The query reads a table of another database on the server, or an alias matches the name of another database. Keep only tables of the current database, rename the alias |
| «Query rejected: could not check which databases the query addresses» | The database did not answer the check. Try again; if it repeats, contact support |
| «Query rejected: forbidden character U+…» | The query has an invisible character or a non-standard space (its code is in the message). Retype that part; build a value with such a character with CHAR() or pass it as a filter value |
| «Query rejected: a /!…/ comment with a version…» | Remove the version number from the /*! … */ comment, or the comment itself |
| «Query rejected: colon outside a string: :name» | A colon before a word is reserved. Put the text in quotes or remove the colon |
| «Query rejected: unknown placeholder …» | Add a binding for the placeholder or remove it from the query |
| «Query rejected: forbidden stage: $out» (Mongo) | Remove the write stage. A card only reads data |
| «Query failed after … s: Unknown column …» | The query itself has an error: check the table and column names |
| «Several columns are named …» | Give the columns distinct aliases with AS |
| The query was stopped on time | Narrow the period, filter by an indexed field or aggregate the data |
| No Custom cards group in + Cards | The feature is off on the instance, or you have no dashboards Save |
| No + Custom card button, pencil or ✕ | Your role has no Custom cards right. You can still place existing cards |
| A grey Card unavailable tile on a dashboard | The card was deleted or custom cards are off on the instance. In edit mode remove the tile with ✕ |