In the previous post, I walked through what an end user sees when they open the Business Dashboard pane in M-Files. From this post on, the audience shifts more towards the administrator’s side, looking at how a dashboard is actually defined.

Every dashboard is a single JSON document. The Admin tab in M-Files Admin offers a Visual Designer that builds this JSON for you, but in the end the JSON is the source of truth, so I think it is worth understanding its shape before getting into widget types and queries in the next posts. Think of this post as the reference map the rest of the series will refer back to.

1. The minimum valid dashboard

Before discussing every field, here is the (almost) smallest dashboard JSON that the engine accepts. It is intentionally trivial – a single KPI widget counting all Documents in the vault:

{
  "schemaVersion": 1,
  "id": "",
  "name": "My Dashboard",
  "widgets": [
    {
      "id": "",
      "title": "Total Documents",
      "type": "kpiNumber",
      "query": {
        "objectType": "Document",
        "aggregation": { "type": "summary" }
      }
    }
  ]
}

Note 1: Boolean-like fields are exposed as the strings “Yes” or “No”. This is what M-Files shows as booleans, don’t ask me why it’s not True/False like the rest of the world ;).

Note 2: Display names (object types, classes, properties, value-list items) must match what the vault shows in M-Files Admin. They are case-insensitive but must otherwise be exact.

2. The dashboard-level fields

The top-level object can carry the following fields: required ones are marked, all others are optional with default values that make sense, usually.

2.1. Identification and presentation

These are the main parameters of a dashboard that you will usually set at the beginning:

  • schemaVersion: Integer (currently 1). The Business Dashboard validation process emits a warning when this field is absent or lower than expected. This version is incremented when breaking changes are introduced, so admins will automatically be flagged that they need to update something.
  • id (required): typically a UUID v4 value (e.g. “12345678-abcd-1234-5678-abcd12345678”) but it isn’t restricted to that (e.g. “d01-contracts” works as well). The ID should be unique for each dashboard, otherwise you risk overriding an existing dashboard when you save. You can leave the field ID empty (as done above), the Business Dashboard will automatically generate one for you.
  • name (required): Text shown in the end-user dropdown, you should keep it concise (<250 chars).
  • description: Optional subtitle shown in italic below the top bar (hidden when absent). As described in the previous post, the first two lines will be shown on the user side, with a hover tooltip for the full text.

2.2. Auto-refresh

These are parameters that controls whether or not the dashboard will refresh itself or allows controls about the refresh more globally:

  • autoRefreshEnabled: “Yes” or “No” (default “No”). Whether auto-refresh is enabled when the dashboard first loads.
  • autoRefreshIntervalSeconds: Integer (default 300, minimum 15). Typical values can be 60, 300 or 900 for example. This is the interval of time between two auto-refresh of all widgets done when the auto-refresh is enabled (either via autoRefreshEnabled or because userCanToggleAutoRefresh is “Yes” and a user enabled it manually). The minimum is set to 15s to avoid overloading the vault in case of mis-configuration.
  • userCanToggleAutoRefresh: “Yes” or “No” (default “No”). Whether the user can disable or enable the auto-refresh feature.

2.3. Exports and drill-through

These are the optional features that you can enable:

  • exportToPdfEnabled: “Yes” or “No” (default “No”). Shows the ⎙ PDF button in the top bar, to allow users to export the dashboard to PDF.
  • exportToCsvEnabled: “Yes” or “No” (default “No”). Shows the ⊞ CSV button on all widgets (except kpiNumber & gauge) as well as on the drill-through modal, to allow users to export the widget details to CSV.
  • drillThroughEnabled: “Yes” or “No” (default “No”). Makes chart elements and rows clickable to open the drill-through modal (c.f. previous post if you don’t know what the drill-through is).

These three flags are intentionally off by default. Turning them on is an explicit choice per dashboard, which I think is the right place to make that decision. A dashboard meant for casual “at-a-glance” use might not need them while a dashboard meant for active investigation might.

2.4. Performance settings

These are security and performance settings:

  • skipTemplateCheck: “Yes” or “No” (default “No”). When “No” (default), objects marked as template (i.e. they have the “is Template” property and it is checked) are automatically excluded from any results. When “Yes”, this additional verification is skipped and templates can therefore be part of the results (faster execution).
  • skipObjectPermissionCheck: “Yes” or “No” (default “No”). When “No” (default), every queries will respect the user-level accesses / ACLs, so different users might see different results / numbers / lists of objects, depending on their permissions. When “Yes”, server-level accesses are used, so different users will see the same results, always (faster execution).
  • serverScanMaxResults: Integer (default 500). The maximum number of M-Files objects fetched per widget query. You can set it to 0 for unlimited.
  • drillThroughMaxResults: Integer (default 300). Maximum rows shown in drill-through modals and list tables. You can set it to 0 for unlimited. The pagination for the opened modal is set at 15 rows per page. Therefore, there can be up to 20 pages by default.

I will dedicate a full post (Post 8 in the series) to these details and to the trade-offs they imply, so I will not spend too much time on them here. The defaults are reasonable for most use-cases but you can change everything, of course.

2.5. Access control

Finally, the last parameter is about access control to the dashboard:

  • assignedTo: An object with two optional arrays, users and groups. When the arrays are absent or empty, the dashboard is public. This means that ANY M-Files user will be able to select the dashboard from the dropdown. It does NOT mean that he will see something inside the widgets! Therefore, it gives access to the dashboard itself, but widget content still relies on the user’s ACLs by default (or server-level access if skipObjectPermissionCheck is set to “Yes”, c.f. above section). On the other hand, when at least one of the arrays is present, access is granted to any user who appears in the users array OR is a member of any group in the groups array.

In this example, the dashboard will be accessible by the user whose Login Account is Morgan.Patou OR any users part of the Finance Team OR Management groups:

"assignedTo": {
  "users": ["Morgan.Patou"],
  "groups": ["Finance Team", "Management"]
}

2.6. Widgets

  • widgets (required): An array of widget objects. The order in this array is the order they appear on the grid. The size of the widget (column span, row span) is part of each widget’s definition (i.e. inside the array).

3. The widget object

Each entry in the widgets array has the following shape:

{
  "id": "",
  "title": "My Widget",
  "description": "An example description for My Widget",
  "type": "kpiNumber",
  "gridColumnSpan": 3,
  "gridRowSpan": 1,
  "query": { ... },
  "display": { ... }
}

Here is a quick description of the different fields:

  • id (required): Same description as for the dashboard
  • title (required): Name of the widget shown at the top of the tile
  • description: Optional one-line italic subtitle just below the title. Annotate what the widget measures, or flag a caveat
  • type (required): One of kpiNumber, gauge, donut, bar, line, area, table (covered in posts 4a, 4b, 4c)
  • gridColumnSpan: Integer from 1 to 12 (default 4). Represent the width of the widget on the dashboard
  • gridRowSpan: Integer (default 1). Represent the height of the widget on the dashboard
  • query (required): The query the widget will execute. This can be pretty complex, so it will be detailed in Post 5 (filters) and Post 6 (aggregations), later
  • display: Type-specific display options, like color thresholds, unit, decimals, etc. It will be covered per widget type in posts 4a, 4b, 4c

4. The query object in one paragraph

As mentioned, the detailed walkthrough of queries is the topic of Post 5 / 6, but for completeness, here is the basic shape of a query:

"query": {
  "objectType": "Document",
  "class": "Contract or Agreement",
  "filters": [
    {
      "property": "Effective through",
      "operator": "greaterOrEqual",
      "value": "@today",
      "valueType": "dateToken"
    }
  ],
  "aggregation": {
    "type": "groupByProperty",
    "propertyName": "Agreement type",
    "resolveValueListLabels": "Yes",
    "includeEmptyResults": "No"
  }
}

objectType can be a string or an array of strings (multi-object-type query). class is optional but strongly recommended for performance and to check only objects that actually matter… For example, you don’t need to fetch all documents (irrespective of their class) if you only want to count the number of active contracts. filters is an array of 1-to-N filter combined by AND. Finally, aggregation specifies the shape of the result and the reduction applied. To know more about these, please wait for the next posts.

5. The 12-column grid

The layout is computed on a CSS grid with 12 columns. gridColumnSpan sets the width of a widget, while gridRowSpan sets its height in row units.

Therefore, most common column layouts include:

  • 4 widgets per row: gridColumnSpan: 3 each. Typical for KPI tiles.
  • 3 widgets per row: gridColumnSpan: 4 each. Medium tiles or narrow charts.
  • 2 widgets per row: gridColumnSpan: 6 each. Charts and tables.
  • 1 widget per row: gridColumnSpan: 12. Full-width bar chart or wide table.

These are just examples. Nothing prevents you to set 3 widgets on a row with gridColumnSpan: 5, gridColumnSpan: 4, gridColumnSpan: 3 respectively. The Visual Designer is great for that since you get an exact view of what it will look like directly while creating the dashboard. More on that on the Post 9a.

The widgets simply fill the rows from left to right in the order they appear in the widgets array, wrapping to the next row when a row’s spans sum exceeds 12. So planning the layout is largely a matter of ordering the widgets and giving each one a sensible column span.

For row height, a small subtlety: gridRowSpan: 2 does not give you exactly twice the canvas height of gridRowSpan: 1. It actually gives you roughly three times the canvas height, because each row unit subtracts a fixed overhead (header, padding, borders) from the available space. The practical consequence is that chart widgets (bar, line, area, donut, gauge) should almost always use gridRowSpan: 2 or higher. A single-row chart has very little drawing space and ends up cramped.

KPI number tiles, on the other hand, look fine at gridRowSpan: 1 and that is the right default for them. As a rule of thumb: KPIs at 1, everything else at 2.

6. Where the definition is stored

Dashboard definitions are stored in the vault’s Named Value Storage (NVS). They are NOT stored as a specific object in the vault. I initially thought about creating them as standard vault objects but in the end decided the NVS would probably make it easier for deployments and usage in the long run. The trade-offs of that choice:

  • Pros: no extra object type to provision when installing the module, no ACL setup, no admin training on a new object type, no per-customer deployment friction. Backups of the vault include the dashboards automatically because NVS is part of the vault.
  • Cons: there is no per-definition version history of the kind you would get with vault objects. I mitigated this by offering Import and Export buttons in the Admin tab that produce .json files. What I would recommend is to manage those files in a Git repository, which gives a clean and conventional version history outside the vault, with diff support.

The Export / Import buttons are the topic of Post 7. The TL;DR is that I would recommend that you should develop your dashboards on a DEV. Then, export them all into your git. Finally, when ready, import them into the TEST/QA/PROD environments, either manually or via CI/CD pipelines.

7. Quick overview of a full dashboard JSON definition

Putting everything together, here is a slightly more complete (but still small) example. It has some of the dashboard-level fields modified from their default values to show-case actions and buttons, and two widgets:

{
  "schemaVersion": 1,
  "id": "",
  "name": "Contracts",
  "description": "Quick overview of contracts",
  "autoRefreshEnabled": "Yes",
  "autoRefreshIntervalSeconds": 60,
  "userCanToggleAutoRefresh": "Yes",
  "exportToPdfEnabled": "Yes",
  "exportToCsvEnabled": "Yes",
  "skipTemplateCheck": "No",
  "drillThroughEnabled": "Yes",
  "skipObjectPermissionCheck": "No",
  "serverScanMaxResults": 500,
  "drillThroughMaxResults": 300,
  "assignedTo": {
    "users": [],
    "groups": ["Legal Team"]
  },
  "widgets": [
    {
      "id": "",
      "title": "Active Contracts",
      "type": "kpiNumber",
      "gridColumnSpan": 3,
      "gridRowSpan": 2,
      "query": {
        "objectType": "Document",
        "class": "Contract or Agreement",
        "filters": [
          {
            "property": "Effective through",
            "operator": "greaterOrEqual",
            "value": "@today",
            "valueType": "dateToken"
          }
        ],
        "aggregation": { "type": "summary" }
      },
      "display": { "unit": "contracts" }
    },
    {
      "id": "",
      "title": "By Agreement Type",
      "type": "donut",
      "gridColumnSpan": 9,
      "gridRowSpan": 2,
      "query": {
        "objectType": "Document",
        "class": "Contract or Agreement",
        "aggregation": {
          "type": "groupByProperty",
          "propertyName": "Agreement type",
          "resolveValueListLabels": "Yes"
        }
      }
    }
  ]
}

This dashboard renders as a single KPI tile and a donut chart on the same row (3 + 9 = 12 columns), with auto-refresh every minute and access restricted to the Legal Team group. Nothing exotic, this is the kind of dashboard most customers should probably start with.

If you wonder what it would look like, you can check the 1st and 2nd posts, they include these same widgets (though arranged slightly differently).

8. What comes next

In the next three posts (4a, 4b, 4c) I will go through the seven widget types one family at a time:

  • 4a – Scalar widgets: kpiNumber and gauge.
  • 4b – Trend widgets: line and area.
  • 4c – Distribution and tabular widgets: donut, bar, table.

After the widget tour, Post 5 covers the query side (object types, classes, filters, date tokens) and Post 6 covers aggregations and reducers. By the end of those, you will have everything needed to write a non-trivial dashboard from scratch – if that’s what you want to do… Or you can just use the Visual Designer and follow along.

In the meantime, I hope this anatomy post gives you a clean mental map. If something is unclear or if you spotted a field whose behavior I have not explained here, please reach out so I can update the post.

Want to know more about this Business Dashboard? Contact us and we will be happy to showcase it on M-Files.