Drupal Charts Module — Comprehensive Documentation¶
Complete reference for the charts module (5.2.x) on Drupal core 10.3+/11/12.
Table of Contents¶
- 1. Introduction & Overview
- 1.1 What the module does
- 1.2 The core idea (how it works technically)
- 1.3 Value proposition
- 1.4 Charting libraries (providers)
- 1.5 Module maturity & maintainers
- 1.6 Who this documentation is for
- 2. Concepts & Terminology
- 2.1 Provider (Library)
- 2.2 Chart type
- 2.3 Render elements
- 2.4 Series, axis, and data item
- 2.5 Single-axis vs. dual-axis
- 2.6 Chart definition
- 2.7 CDN vs. local library
- 2.8 The debug object
- 3. Requirements & Installation
- 3.1 System requirements
- 3.2 Installing the module
- 3.3 Enabling a provider
- 3.4 Installing the JavaScript libraries
- 3.5 Verifying the installation
- 3.6 Troubleshooting library installation
- 4. Getting Started (Quickstart)
- 4.1 Choose a default library
- 4.2 Your first chart with the API
- 4.3 Your first chart with Views
- 4.4 Where to go next
- 5. Building Charts in the UI
- 5.1 Configuration page tour
- 5.2 Charts with Views
- 5.3 Combo charts (multiple chart types in one)
- 5.4 Exposing the chart type to visitors
- 5.5 Specialized Views data fields
- 5.6 Charts with a Chart field
- 5.7 Charts with a Chart block
- 5.8 The data-collector table
- 5.9 Color customization
- 5.10 Other creation methods (ecosystem modules)
- 6. Developer Guide — The Charts API
- 6.1 The render-array model
- 6.2 Element reference
- 6.3 Worked examples
- 6.4 The example-builder pattern
- 6.5 Raw options — the escape hatch
- 6.6 Programmatic library and type selection
- 6.7 The accessible-table fallback
- 7. Provider Reference
- 7.1 How provider capabilities work
- 7.2 Chart type support matrix
- 7.3 The portable core
- 7.4 Per-provider reference
- 7.5 Additional ecosystem providers
- 7.6 Choosing a provider
- 8. Extending the Module
- 8.1 The extension spectrum
- 8.2 Altering the render array —
hook_chart_alter() - 8.3 Adding native options —
#raw_options - 8.4 Altering the generated definition —
hook_chart_definition_alter() - 8.5 Replacing the definition entirely — the
#chart_definitionescape hatch - 8.6 Adding a chart type to the module
- 8.7 Registering a new provider (brief)
- 8.8 Choosing the right mechanism
- 9. Configuration & Data Model Reference
- 9.1 The
charts.settingsconfig object - 9.2 Config schema and reusable data types
- 9.3 The
chart_configfield (per-entity storage) - 9.4 Config translation
- 9.5 How library selection becomes a dependency
- 9.6 Config import safety
- 9.7 Uninstall protection
- 9.8 Updates and upgrades
- 10. Theming & Front-End Integration
- 10.1 The template
- 10.2 CSS hooks
- 10.3 The
data-chartattribute contract - 10.4 The JavaScript API
- 10.5 Overriding a chart in JavaScript
- 10.6 The debug output
- 10.7 CDN rewriting
- 11. Permissions & Security
- 11.1 Permissions
- 11.2 How chart data is sanitized
- 11.3 Security considerations for developers
- 11.4 Third-party assets (CDN)
- 12. Testing & Quality
- 12.1 Test suites
- 12.2 Running the tests
- 12.3 Static analysis and coding standards
- 12.4 Contributing tests
- 13. Troubleshooting & FAQ
- 14. Upgrade & Migration Guide
- 14.1 Deprecations and backward-compatibility notes
- 14.2 Settings migration
- 14.3 Recommended upgrade procedure
- 14.4 If you're coming from a very old version
- 15. Tutorials & Case Studies
- 15.0 How to use this section
- 15.1 A Views chart of content over time
- 15.2 Multiple series by grouping (a line per category)
- 15.3 A combo chart in Views
- 15.4 Small multiples with Views Field View
- 15.5 Let visitors switch the chart type
- 15.6 A per-entity chart with the Chart field (CSV import)
- 15.7 A chart in Layout Builder or a block
- 15.8 Embed a chart inside body content (Charts Text Filter)
- 15.9 Render a chart in a Twig template (Charts Twig)
- 15.10 A first chart in code (the API)
- 15.11 Case study: escape the chart-type limits with
#chart_definition - 15.12 Case study: add a first-class chart type
- 15.13 Case study: theme and alter one chart
- 15.14 Case study: switch providers without rebuilding
- 15.15 Canonical HowTo map
- 16. Reference Appendices
- 16.1 Chart type catalog
- 16.2 Element property reference
- 16.3 Hook & event index
- 16.4 Service & class index
- 16.5 Route & permission index
- 16.6 Architecture & data-flow map
- 16.7 Glossary
- 16.8 External links
1. Introduction & Overview¶
In brief — the Charts module renders charts from Drupal data through a library-agnostic API: describe a chart once and switch the rendering library (Highcharts, Google Charts, Chart.js, C3, Billboard.js, and others) by changing a setting. Site builders build charts with no code via Views, a Chart field, or a Chart block; developers use a render-array API.
1.1 What the module does¶
The Charts module turns data into information. It lets you build dynamic, interactive charts on a Drupal site without writing a line of code — and, if you do write code, it gives you a clean API for generating and updating charts programmatically.
There are many JavaScript charting libraries available, each with its own API, its own strengths, and its own quirks. To use one directly — without this module — you would need to learn that library's API, add its JavaScript to your pages, and hand-write code for every chart you want. That's a lot of effort, and it assumes a level of front-end knowledge and site access that many site builders don't have.
Charts removes that burden. Its goal is simple: anyone who wants a chart on their site should be able to have one, and be happy with the result. Site builders get a point-and-click experience through Views, fields, and blocks; developers get a Drupal-friendly render API that stays the same no matter which library does the drawing.
1.2 The core idea (how it works technically)¶
The module is built around one central idea: separate the description of a chart from the library that renders it.
At a high level, the flow is:
- You describe a chart — its type, data series, axes, and display options — either through the UI or as a Drupal render array.
- The Charts "core" module normalizes that description into a single configuration object and serializes it to JSON.
- That JSON is stored as a
data-chartattribute on an HTML element on the page. - A small piece of the module's JavaScript reads the attribute and hands the configuration to your selected charting library, which draws the chart.
Your data + settings
(UI or render array)
│
▼
Charts core: normalize → JSON
│
▼
<div data-chart='{…}'> ← markup on the page
│
▼
Provider JS (e.g. Highcharts) ← reads the attribute, renders
│
▼
Rendered chart
The architecture has two layers that map onto this flow:
- A core module (
charts) that handles all the shared logic — the render elements, the settings forms, Views/field/block integration, and the normalization to JSON. It draws nothing on its own. - One or more provider submodules (also called "libraries"), each of which teaches the core how to talk to a specific charting library and ships the JavaScript that actually renders the chart.
Because the description is library-agnostic, the same chart definition can be handed to a different library just by changing a setting. That decoupling is the whole point of the module, and it's what makes the value proposition below possible.
1.3 Value proposition¶
- No code required. Build charts entirely through the UI — via Views, a Chart field on an entity, or a Chart block.
- No vendor lock-in. Switching charting libraries can be as simple as enabling a different provider module and updating your default chart settings; your chart definitions don't have to change.
- One API across many libraries. Developers learn a single Drupal render-array syntax and reuse it regardless of which library is active.
- Wide chart-type coverage. The module standardizes on the chart types common to most libraries — Area, Area Range, Bar, Boxplot, Bubble, Candlestick, Column, Donut, Gauge, Heatmap, Line, Pie, Radar, Scatter, and Spline. Types a given library doesn't support simply don't appear as options for that library, which keeps the UI honest.
- Views integration. Visualize any Views result set as a chart, including combination charts.
- Data entry for non-site data. The Chart field and Chart block let you chart data that isn't already in your site — typed into a table or imported as CSV/JSON — without touching the API.
- Built-in examples. The
charts_api_examplesubmodule ships a gallery of working charts (viewable at/charts/example/display) to copy from when building your own. - Accessibility. Charts can render an accompanying data table so the underlying numbers remain available to assistive technology.
1.4 Charting libraries (providers)¶
A provider (or "library") is the JavaScript charting engine that renders your chart. The module ships five providers as bundled submodules, and the wider Charts ecosystem adds several more as separate contributed projects.
Bundled providers¶
Enable at least one of these submodules to start charting:
| Provider | Submodule | Notes |
|---|---|---|
| Highcharts | charts_highcharts |
One of the premier charting solutions — powerful and polished, with the widest chart-type support here. Free for non-commercial use; a commercial license is required for commercial use. |
| Google Charts | charts_google |
Interactive SVG charts, loaded from Google. |
| Chart.js | charts_chartjs |
Simple, flexible, and popular; open source. |
| C3.js | charts_c3 |
A D3-based reusable chart library. |
| Billboard.js | charts_billboard |
A fork of C3 with extra features such as radar charts. |
The most important practical distinction among these is licensing: Highcharts is the standout, being free only for non-commercial use, while the others are permissively licensed open source. Beyond licensing, each library differs in which chart types it supports and in visual style — see the per-provider reference (Section 7) and the comparison matrix there.
Additional providers in the Charts ecosystem¶
Beyond the bundled five, several more charting libraries are integrated through separate contributed modules that build on the same Charts API. Notably:
- Charts ECharts (
charts_echarts) — integrates Apache ECharts (Apache-2.0 licensed). - ApexCharts (
charts_apexcharts) — integrates ApexCharts.js (MIT licensed; free for commercial use). - Charts Kendo UI — integrates Kendo UI's charts (a commercially licensed library).
- Charts Plotly (
charts_plotly) — integrates Plotly.js (MIT licensed; free for commercial use).
These install and behave like the bundled providers: enable the module, set it as your default library at /admin/config/content/charts, and build charts the same three ways. Between the bundled providers and these, the module covers essentially all of the widely used web charting libraries — so for most projects, choosing a provider is a matter of picking from the existing list rather than integrating something new.
The ecosystem also includes companion modules that extend charting behavior rather than add a library — for example Twig-based chart rendering, a WYSIWYG text filter for embedding charts, and Highcharts-specific add-ons (maps, drilldown, grouped categories). These live alongside the provider modules as part of the Charts project's ecosystem on Drupal.org.
1.5 Module maturity & maintainers¶
Charts is a long-established, actively maintained contributed module. This documentation targets the 5.2.x series, which requires Drupal core ^10.3 || ^11 || ^12 and is distributed under the GPL-2.0-or-later license (package name drupal/charts).
The project is maintained by a team including Daniel Cothran (andileco), Nia Kathoni (nikathone), Nate Lampton (quicksketch), brmassa, Rafal W (kenorb), Pierre Vriens (pierrevriens), and Majali (rashad612). The same maintainer team is behind the additional provider modules listed above, which is part of why they integrate so cleanly.
Key project resources:
- Project page: https://www.drupal.org/project/charts
- Documentation: https://www.drupal.org/docs/contributed-modules/charts
- Issue queue: https://www.drupal.org/project/issues/charts
- Source: https://git.drupalcode.org/project/charts
Contributions are welcome — the maintainers specifically call out help with documentation, tests, and example use cases.
1.6 Who this documentation is for¶
Different readers need different parts of this manual. Use this map to jump to what's relevant to you:
| If you are a… | You mainly want… | Start with |
|---|---|---|
| Content editor | To fill in data on a chart someone already set up | §5.6–5.8 (Chart field, Chart block, data-collector table) |
| Site builder | To create charts through the UI without code | §3 (Install), §4 (Quickstart), §5 (Building Charts in the UI) |
| Developer | To build or manipulate charts in code | §4.2 (First chart via the API), §6 (The Charts API), §8 (Extending) |
| Themer / front-end developer | To control chart markup, styling, and client-side behavior | §10 (Theming & Front-End Integration) |
| Site owner / evaluator | To decide whether the module fits, and which library to use | §1 (this section), §7 (Provider Reference & comparison) |
If you're new to the module, read on to Section 2 (Concepts & Terminology) to get the vocabulary — provider, chart type, series, axis, chart definition — that the rest of the documentation assumes.
2. Concepts & Terminology¶
In brief — eight terms recur throughout this manual: provider/library, chart type, render elements, series/axis/data item, single- vs. dual-axis, chart definition, CDN vs. local, and the debug object. This section defines each; the consolidated glossary is in the Reference Appendices.
The Charts module has a small, consistent vocabulary. Learning these eight terms up front makes every other section easier to follow, because they recur constantly in the UI, the API, and the configuration.
2.1 Provider (Library)¶
A provider — also called a library — is the JavaScript charting engine that actually draws the chart in the browser. Highcharts, Google Charts, Chart.js, C3, and Billboard are the five bundled providers; ECharts, ApexCharts, Kendo UI, and Plotly are available as separate ecosystem modules.
Each provider is implemented as a Drupal plugin that teaches the core module how to translate a chart into that library's format, and ships the JavaScript that renders it. You choose one provider as your site default, and can override it per chart. The whole design goal is that swapping providers requires changing a setting, not rebuilding your charts.
The two words are used interchangeably throughout the module and this manual. "Provider" emphasizes the Drupal submodule; "library" emphasizes the underlying JavaScript — but they refer to the same thing.
2.2 Chart type¶
A chart type is the kind of chart: line, column, bar, pie, donut, gauge, scatter, bubble, area, spline, and so on. Types are defined declaratively (in *.charts_types.yml files) with metadata describing how each behaves — whether it uses one axis or two, whether its axes are inverted, whether it can stack series, and whether its data points are coordinates.
Crucially, not every provider supports every type. Each provider declares which types it can draw, and the UI only offers those. Select Chart.js and you won't see "Gauge"; select Highcharts and you'll see extras like "dumbbell" and "solid gauge." This keeps the interface honest — you're never offered a type the active library can't produce.
2.3 Render elements¶
Everything the module draws is ultimately a Drupal render element. There are five element types, and you compose them like nested arrays:
chart— the container. Holds chart-wide settings and all the child elements.chart_data— a data series (see 2.4).chart_xaxis/chart_yaxis— the horizontal and vertical axes.chart_data_item— an individual data point, when you need per-point control (its own color, tooltip, etc.).
Because they're ordinary render elements, they work anywhere Drupal renders arrays — controllers, blocks, preprocess hooks — and they benefit from caching, attachments, and alter hooks like any element. Section 6 documents each element and its properties in full.
2.4 Series, axis, and data item¶
These three describe the data model of a chart:
- A series (
chart_data) is one set of related values with a shared label and color — for example, "Installs of version 5.0.x." Its name appears in the legend. A chart can have one series (a simple pie) or many (a multi-line comparison). - An axis (
chart_xaxis/chart_yaxis) is a reference scale. The x-axis usually carries category labels ("Jan, Feb, Mar"); the y-axis usually carries the numeric scale. Charts can have a secondary y-axis for series measured on a different scale. - A data item (
chart_data_item) is a single point within a series. Most of the time you just supply raw numbers, but when you need one point to differ — a distinct color, a custom tooltip — you express it as a data item.
The relationship is hierarchical: a chart holds one or more series and its axes; a series holds data (numbers, coordinate pairs, or data items); an axis holds labels and scale settings.
2.5 Single-axis vs. dual-axis¶
Chart types fall into two families based on how many axes they use. The module names these internally as constants:
- Single-axis (
y_only) — the chart has one dimension. Pie, donut, and gauge are single-axis: each is a set of values with no x/y grid. You supply one series and (for pie/donut) the slice labels. - Dual-axis (
xy) — the chart plots values against two axes. Line, column, bar, area, scatter, and most others are dual-axis. These expect category labels on one axis and numeric values on the other.
This distinction drives real behavior. For dual-axis types the module will automatically supply default x- and y-axis elements if you didn't declare them, so a minimal two-axis chart still renders. Some dual-axis types are further marked as coordinate types (scatter, bubble, heatmap): their data points are [x, y] pairs (or [x, y, size] triples for bubble) rather than single numbers.
2.6 Chart definition¶
It's worth distinguishing two representations of the same chart:
- The render array is your description — the
#type => chartelement with its#propertiesand child series. It is library-agnostic. - The chart definition is the library-specific object the module produces from your render array — the actual JSON structure that Highcharts (or Google, or Chart.js) understands. Its shape differs from library to library.
The module's core job is converting the first into the second. This matters when you extend the module: hook_chart_alter() lets you change the render array before conversion, while hook_chart_definition_alter() lets you change the definition after conversion (and is therefore library-specific and more fragile). It's also the definition — not the render array — that you see when you turn on the debug object.
2.7 CDN vs. local library¶
A provider's JavaScript can be loaded two ways:
- From a CDN (content delivery network) — a third-party URL, used by default when the library isn't found locally. Zero setup, but adds an external dependency on every charted page and can interact poorly with Ajax/BigPipe.
- From a local copy — the library files installed under your site's
/librariesdirectory. Recommended for production; served from your own domain and version-pinned.
The module detects a local copy automatically and falls back to the CDN only when one isn't present (and only when CDN mode is enabled). Section 3 covers the several ways to get libraries installed locally; the mechanics of the fallback are explained there in 3.4.0.
2.8 The debug object¶
The debug object is a developer aid: when enabled (on the Advanced settings tab), the module renders the generated chart definition as formatted JSON in a collapsible code block beneath each chart. This is the single most useful troubleshooting tool in the module — the JSON can usually be pasted straight into the charting library's own online playground, which quickly tells you whether a problem lives in your render array, in the module's translation of it, or in the library itself.
With this vocabulary in hand, you're ready to build. Section 3 covers installation (especially getting libraries in place); Section 4 walks through your first chart both ways — in the UI and via the API.
3. Requirements & Installation¶
In brief — installing a chart requires the charts module, at least one provider submodule, and that provider's JavaScript library. The module and submodules install via Composer/Drush; installing the library is the hard part and can be done by CDN (default, zero-setup), npm workspaces, or one of three Composer methods (§3.4). CDN fallback means charts work before any local install.
Installing a working chart takes three things: the module, at least one provider submodule, and the JavaScript library that provider draws with. The first two are ordinary Drupal installs. The third is where the real work is — and it's what this section spends most of its time on.
3.1 System requirements¶
- Drupal core:
^10.3 || ^11 || ^12. - PHP: whatever your Drupal core version requires.
- Composer: strongly recommended for installing the module, and required for two of the library-installation methods below.
composer/installers: needed for most Composer-based library installs (it lets Composer place packages in/librariesinstead of/vendor).- Node.js / npm: only if you choose the npm-based library install method.
You do not need any special server extensions — charts are rendered in the browser by JavaScript, so PHP only assembles the configuration.
3.2 Installing the module¶
Install with Composer like any contributed module:
composer require drupal/charts
This pulls in the core charts module and all of its bundled provider submodules. Nothing is enabled yet.
3.3 Enabling a provider¶
The core module renders nothing on its own — it needs a provider. Enable the core module plus at least one provider submodule, for example:
drush en charts charts_highcharts
Or enable them at Administration › Extend. The bundled providers are charts_highcharts, charts_google, charts_chartjs, charts_c3, and charts_billboard. (These currently ship inside the main project; the maintainers intend to move them to their own project pages over time, so in the future you may install a provider as its own composer require. The workflow — enable it, then install its library — stays the same.)
After enabling a provider, set it as the site default at /admin/config/content/charts. At this point the module works immediately in CDN mode (see below), so you can build a chart before doing any further library setup — but for production you'll usually want the library installed locally.
3.4 Installing the JavaScript libraries¶
This is the hard part, so it's worth understanding what's actually happening before picking a method.
3.4.0 How the module finds a library (and why the method matters)¶
Each provider expects its JavaScript to live in a libraries directory in your web root — typically /libraries/<name> (for example /libraries/highcharts/highcharts.js or /libraries/google_charts/loader.js). On the Status report, each provider runs a detection routine that searches, in order:
profiles/<your_install_profile>/librarieslibraries(the usual location)sites/<site>/libraries
It looks for a specific sentinel file in each. If it finds one, the library is installed locally. If it doesn't, the provider falls back to loading the library from a CDN — provided the CDN option is enabled.
That fallback is defined in each provider's *.libraries.yml, which lists both the expected local path and a CDN URL for each file. Drupal's asset system uses the local file when present and the CDN URL when it isn't. This is why the module "just works" out of the box, and also why installing locally is simply a matter of getting the right files into the right directory — once they're there, detection flips from CDN to local automatically.
So every method below is really doing the same job: placing the correct library files under /libraries/<name>. They differ only in how the files get there and how much of it is automated.
3.4.1 Choosing a method¶
| Method | Best when… | Effort | Automated updates |
|---|---|---|---|
| A. CDN | Prototyping, low-traffic sites, or you can't add build tooling | None | N/A (always latest per URL) |
| B. npm workspaces | You already have (or can add) a Node build step | Low, once set up | Yes, on npm install |
C. Composer drupal-library |
Composer-managed site, library published as a plain file/archive (Highcharts, Google, C3, Billboard) | Medium (per-library repo entries) | Yes, on composer update |
| D. Composer via asset-packagist | Composer-managed site, library published on npm (Chart.js) | Medium (extra repos/installer-types) | Yes, on composer update |
| E. Composer merge-plugin | You want Composer installs without hand-copying each library's repository block | Medium (one-time) | Yes |
A good rule of thumb: use CDN to get going, then switch to npm or Composer before launch so your libraries are version-pinned and served from your own domain (which also avoids the Ajax/BigPipe caveats that come with CDNs).
Where to get the authoritative details. The exact versions, URLs, and package names drift as libraries update. Each provider submodule ships its own README.md, composer.json, and package.json with the current values. Treat those as the source of truth; the specifics below are accurate for 5.2.x and illustrate the shape of each method.
3.4.2 Method A — CDN (zero-setup default)¶
Nothing to install. Enable the provider, and if the library files aren't found locally the module loads them from a CDN.
To control this behavior, go to /admin/config/content/charts/advanced:
- Use a CDN by default for external libraries — checked (default): missing libraries load from a CDN.
- Unchecked: the module will not use a CDN, and it also suppresses "missing library" warnings on the Status report — appropriate only once you've installed locally.
Trade-offs. CDN mode is the fastest path to a working chart, but it means a third-party request on every page with a chart, a dependency on that CDN's availability, and — as the module's own help notes — potential issues with Ajax and BigPipe. It's great for development and fine for many small sites; for production, prefer a local install.
3.4.3 Method B — npm workspaces (recommended local install)¶
If your project has (or can have) a Node build step, this is the least fiddly local method. Each provider submodule ships a package.json that declares the library as a dependency and includes a libraries:copy script; that script deletes any stale copy and copies the needed files from node_modules into /libraries/<name>.
You wire it up once at your project root:
{
"name": "my_site",
"private": true,
"scripts": {
"postinstall": "npm run libraries:copy --workspaces --if-present"
},
"workspaces": [
"web/modules/contrib/charts/**/*"
]
}
If you already have a root package.json, just add the postinstall script and the workspaces entry. Then run:
npm install
What happens: npm treats each charts submodule as a workspace, installs each one's library dependency into node_modules, and the postinstall hook runs every submodule's libraries:copy script, populating /libraries/highcharts, /libraries/chart.js, /libraries/billboard, and so on.
Notes:
- The example assumes a
web/docroot with the module atweb/modules/contrib/charts. Adjust the workspace glob if your layout differs. - The broad glob
web/modules/contrib/charts/**/*picks up every bundled provider. To install only specific providers, point at their directories instead, e.g.web/modules/contrib/charts/modules/charts_billboard. - Re-running
npm installre-copies the files, so this doubles as your update mechanism.
3.4.4 Method C — Composer with drupal-library packages¶
Used for libraries distributed as a plain file or archive: Highcharts, Google Charts, C3, Billboard. The pattern is the same for each:
- Ensure
composer/installersis present:
composer require --prefer-dist composer/installers
- Add a
drupal-libraryinstall path in theextra.installer-pathssection of your rootcomposer.json:
"libraries/{$name}": ["type:drupal-library"]
- Add a package definition to the
repositoriessection describing where to fetch the library file(s). For example, Google Charts is a single loader file:
{
"type": "package",
"package": {
"name": "google/charts",
"version": "45",
"type": "drupal-library",
"extra": { "installer-name": "google_charts" },
"dist": {
"url": "https://www.gstatic.com/charts/loader.js",
"type": "file"
},
"require": { "composer/installers": "^1.0 || ^2.0" }
}
}
- Require it:
composer require --prefer-dist google/charts:45
New directories appear under /libraries. Highcharts follows the identical pattern but with many packages — the base library plus optional modules (more, exporting, export-data, accessibility, 3d, annotations, boost, coloraxis, data, dumbbell, solid-gauge, pattern-fill, and more) — all pinned to the same version and required in one composer require command. C3 and Billboard additionally require d3 as a separate library package (see per-library notes below). The exact repository blocks are spelled out in each provider's README.
3.4.5 Method D — Composer via asset-packagist (npm-published libraries)¶
Used when the library lives on npm rather than as a downloadable file — in the bundled set, that's Chart.js. This needs two extra pieces of Composer plumbing:
- Add the asset-packagist repository alongside the Drupal packages repository in
composer.json:
"repositories": [
{ "type": "composer", "url": "https://packages.drupal.org/8" },
{ "type": "composer", "url": "https://asset-packagist.org" }
]
- Install the extender that teaches Composer to place npm assets in
/libraries:
composer require --prefer-dist oomphinc/composer-installers-extender
- Declare
npm-assetas an installer type and route the packages into/librariesin theextrasection:
"extra": {
"installer-types": ["npm-asset"],
"installer-paths": {
"web/libraries/chart.js": ["npm-asset/chart.js"],
"web/libraries/chartjs-adapter-date-fns": ["npm-asset/chartjs-adapter-date-fns"],
"web/libraries/chartjs-plugin-datalabels": ["npm-asset/chartjs-plugin-datalabels"]
}
}
- Require the library and its companions:
composer require --prefer-dist \
npm-asset/chart.js:^4.4 \
npm-asset/chartjs-adapter-date-fns:^3.0 \
npm-asset/chartjs-plugin-datalabels:^2.0
Because npm packages ship a lot of extra files, this method pairs with an optional cleanup step (see 3.4.7).
3.4.6 Method E — Composer with the merge-plugin¶
Rather than hand-copying each library's repositories block into your root composer.json, you can let Composer read the submodule's own composer.json, which already contains the correct package definitions.
- Install
wikimedia/composer-merge-plugin. - Install
oomphinc/composer-installers-extender. - In your root
composer.jsonextra, ensureinstaller-typesincludesnpm-asset, andinstaller-pathscovers bothtype:drupal-libraryandtype:npm-asset:
"installer-types": ["npm-asset"],
"installer-paths": {
"web/libraries/{$name}": ["type:drupal-library", "type:npm-asset"]
}
- Add a
merge-pluginblock pointing at the provider'scomposer.json:
"merge-plugin": {
"include": [
"web/modules/contrib/charts/modules/charts_highcharts/composer.json"
]
}
- Run the
composer requirefrom that provider's README.
This keeps the library definitions in one place (the submodule) and reduces the chance of copy-paste errors in your root file.
3.4.7 Per-library notes and gotchas¶
Each library has quirks worth knowing before you start:
Highcharts (/libraries/highcharts)
- Free for non-commercial use only; a commercial license is required for commercial sites.
- Ships many optional modules; install the ones your charts need (3D needs highcharts_3d, solid gauges need highcharts_solid-gauge, etc.).
- Security check: if your Highcharts copy contains an exporting-server directory, the Status report raises an error — that directory holds dangerous sample files and must be deleted before the module will run. Newer distributions don't include it, but older or unusual installs might.
Google Charts (/libraries/google_charts)
- What you "install" locally is Google's small loader.js; the actual chart code is still fetched from Google at runtime. This means Google Charts is never fully self-hosted.
- If you hit an SSL error downloading the loader via Composer, change the URL to http://… and set "secure-http": false in your Composer config, per the provider README.
- Note its Status-report severity is a warning (not an error) when missing, reflecting that it works via Google's loader regardless.
Chart.js (/libraries/chart.js)
- Installed from npm (Method D). Needs two companions: the date-fns adapter (chartjs-adapter-date-fns) for time axes and the datalabels plugin (chartjs-plugin-datalabels) for inline labels.
- The npm package includes many files you don't need and shouldn't serve. The README provides a clean-chartjs.sh script and Composer post-install-cmd/post-update-cmd hooks to strip them automatically after each install.
C3 (/libraries/c3)
- Requires d3 as a separate library. C3 hasn't been updated since ~2019–2020 and depends on an old, insecure d3; the module brings in a secure d3 via a shim file to compensate.
- The maintainers recommend Billboard.js instead — it's an actively maintained fork of C3.
- Also has an optional clean-c3js.sh cleanup script.
Billboard (/libraries/billboard)
- Requires d3 (a modern version, e.g. 7.9.0) as a separate library, plus Billboard's own CSS file.
- Has an optional clean-billboardjs.sh cleanup script.
Why the cleanup scripts exist. When libraries are pulled from npm, the package directory contains source, docs, type definitions, and other files that Drupal doesn't need and that can be a minor security/footprint concern if served. The provided shell scripts (wired into Composer's post-install and post-update events) delete those extras so only the runtime files remain. They're marked optional but recommended.
3.4.8 Additional (non-bundled) providers¶
The ecosystem providers — Charts ECharts (charts_echarts), ApexCharts (charts_apexcharts), Charts Kendo UI (charts_kendo_ui), and Charts Plotly (charts_plotly) — install the same way conceptually: enable the module, then place its library under /libraries. Several of them use the asset-packagist approach (Method D) because their libraries are npm-published. Follow each project's own README for its exact package names and paths.
3.5 Verifying the installation¶
Go to Administration › Reports › Status report (/admin/reports/status). Each enabled provider reports its library status:
- Installed (OK) — the library was found locally.
- Available through a CDN (warning) — not found locally, but CDN mode is on; charts will render, but a local install is recommended.
- Not Installed (error/warning) — not found locally and CDN is off; charts won't render until you install the library or re-enable CDN mode.
Then confirm end-to-end by enabling the charts_api_example submodule and visiting /charts/example/display, which renders a gallery of charts using your default library. If those draw correctly, your library is wired up.
3.6 Troubleshooting library installation¶
- Chart area is blank, console shows a "library is not defined" error. The library file isn't being loaded. Check the Status report; if it says "Not Installed" with CDN off, either install locally or turn CDN back on at
/admin/config/content/charts/advanced. - Status report says "Not Installed" but the files are there. They're probably in the wrong directory or named unexpectedly. Detection looks for a specific sentinel file under
/libraries/<name>(e.g.libraries/highcharts/highcharts.js). Confirm the path and filename match what the provider expects, and that they're in one of the three searched locations. - Composer can't download the library (SSL error), especially Google. Apply the
http://+"secure-http": falseworkaround from the Google README, or fetch the file another way and place it manually. - Charts work in development but break with Ajax/BigPipe. This is the known CDN caveat — install the library locally rather than relying on the CDN.
- npm install runs but
/librariesstays empty. Check that theworkspacesglob actually matches your module path and that thepostinstallscript is present in the rootpackage.json; runnpm run libraries:copy --workspaces --if-presentmanually to see errors. - Highcharts Status report error about
exporting-server. Delete that directory from your Highcharts library folder; it contains unsafe sample files. - Chart.js pulled in a huge pile of files. Add the
clean-chartjs.shcleanup script and Composer post-install hooks from the provider README. - Still stuck? Every provider's README has the exact, current commands for that library; and the module's issue queue (https://www.drupal.org/project/issues/charts) is the place to report genuine install bugs.
4. Getting Started (Quickstart)¶
In brief — set a default library at /admin/config/content/charts, then build a first chart either via Views (site builders, §4.3) or a render array (developers, §4.2). Because of CDN fallback, charts render before any local library install.
By the end of this section you'll have rendered a chart two ways. Pick whichever track fits you — site builders will want 4.3; developers will want 4.2 — but skimming both is the fastest way to understand how the module fits together.
4.1 Choose a default library¶
Before anything renders, tell the module which provider to use.
- Go to Administration › Configuration › Content authoring › Charts (
/admin/config/content/charts). - Choose your default library (e.g. Highcharts).
- Choose a default chart type (e.g. Line) — only types the selected library supports are offered.
- Save. Library-specific settings appear after the first save.
This sets the defaults new charts inherit. Thanks to CDN fallback, the module can render immediately even if you haven't installed the library locally yet — though for production you'll want a local install (Section 3).
Sanity check: enable the
charts_api_examplesubmodule and visit/charts/example/display. If the example gallery draws, your library is wired up correctly and you're ready to build.
4.2 Your first chart with the API¶
A chart is a Drupal render array with #type => 'chart'. Here is a complete, minimal column chart — two values, two labels:
$chart = [
'#type' => 'chart',
'#chart_type' => 'column',
'series' => [
'#type' => 'chart_data',
'#title' => $this->t('Responses'),
'#data' => [60, 40],
],
'xaxis' => [
'#type' => 'chart_xaxis',
'#labels' => [$this->t('Yes'), $this->t('No')],
],
];
Return $chart from a controller or block build() method and Drupal renders it — no need to call the renderer yourself. If you do need the HTML directly (say, in a preprocess function):
$output = \Drupal::service('renderer')->render($chart);
That's the whole idea: you describe what to chart, and the active provider decides how to draw it. To add a second series, add another chart_data child; to give it a title, set #title on the chart; to change the type, change #chart_type. Full property references and richer examples (combo charts, gauges, scatter/bubble, loading from CSV) are in Section 6, and a live gallery ships in charts_api_example.
4.3 Your first chart with Views¶
Use Views when the data already lives in your site and may change over time.
- Create a View at
/admin/structure/views/add. Set the display format to Chart. - Add a label field under Fields — the field whose values label one axis or the pie slices (for example, a node title).
- Add a data field — a numeric field to plot (for example,
field_number_attendees). Its label becomes the series name in the legend. Add more data fields for more series. - Configure the chart: in the Format section, click Settings next to "Chart." Pick the library, chart type, the label field, and which field(s) are the data providers.
- Save the View.
If your content has no numeric field to plot, turn on aggregation for the View and use the count function on the data field to produce a number (e.g. "nodes per category").
Tip: build the View as a Table first to confirm the columns look right, then switch the display format to Chart. Seeing the data in rows and columns makes the eventual chart much easier to reason about.
4.4 Where to go next¶
You've now seen both tracks. From here:
| You want to… | Go to |
|---|---|
| Build charts through the UI (Views, fields, blocks) in depth | §5 — Building Charts in the UI |
| Learn the full API: every element and property, combo charts, examples | §6 — The Charts API |
| Install libraries locally for production | §3 — Requirements & Installation (especially §3.4) |
| Understand a term you hit here | §2 — Concepts & Terminology |
| Compare providers or see what each supports | §7 — Provider Reference |
| Troubleshoot a chart that won't render | §13 — Troubleshooting & FAQ |
If a chart doesn't appear, the first move is almost always the same: open the Status report to confirm the library is detected, and turn on the debug object (Advanced settings tab) to inspect the JSON the module generated. Those two checks resolve the large majority of first-chart problems.
5. Building Charts in the UI¶
In brief — there are six ways to make a chart: Views, a Chart field, a Chart block, the API (§6), Charts Twig, and Charts Text Filter. This section covers the three built-in UI methods plus the shared settings form, the data-collector table, and color options; the two ecosystem methods are summarized in §5.10 and the API is §6.
The Charts module lets you build charts entirely through the administrative interface — no code required. There are three built-in places a chart can live, and you choose based on where the data comes from and how it should be placed:
| Method | Best for | Data source |
|---|---|---|
| Views | Charting existing site content that changes over time | Dynamic — queried from entities |
| Chart field | A chart attached to a specific entity (e.g. one node) | Static — entered on the entity |
| Chart block | A reusable chart placed via Block layout or Layout Builder | Static — entered in the block |
All three share the same underlying settings form (the charts_settings element), so once you learn the options in one place they transfer to the others.
Beyond these three, there are three more ways to make a chart:
- The Charts API — build a chart in code as a render array. This is the developer path, covered in full in Section 6.
- Charts Twig (
charts_twig, a contributed ecosystem module) — provides achart()Twig function that renders a chart from a template via the Charts API (pass it an options array ofid,chart_type,title,series,axes,raw_options). Handy for themers who want a chart in a specific template region without Views or a block. - Charts Text Filter (
charts_text_filter, a contributed ecosystem module) — adds a Chart button to the CKEditor 5 toolbar plus a text filter, so content editors can configure and embed a chart inline within body copy through a dialog (no source editing).
The Twig and text-filter methods are separate installs, but they render through the same Charts API and the same library plugins as everything else — so the settings, providers, and alter hooks all behave identically. This section focuses on the three built-in UI methods; see Section 6 for the API and the respective project pages for the two ecosystem modules.
5.1 Configuration page tour¶
Global defaults live at Administration › Configuration › Content authoring › Charts (/admin/config/content/charts). The page has two tabs.
These are defaults only. As the on-page help states, the settings here are used to seed new charts. Changing them does not alter charts that already exist. Each chart keeps its own copy of the settings once created.
Default tab (/admin/config/content/charts)¶
This tab embeds the shared chart settings form and saves it to the charts.settings config object. Key options:
- Default library — which charting provider to use (Highcharts, Google, Chart.js, C3, or Billboard). You must have at least one library submodule enabled; the form will refuse to save without a library selected. After you save a library, additional library-specific settings become available.
- Default chart type — line, column, bar, pie, etc. Only types supported by the selected library are offered.
- Display options — title and title position, subtitle, legend and legend position, tooltips (and whether tooltips may use HTML), data labels, data markers, background, three-dimensional and polar toggles, and chart dimensions (width/height with unit selectors).
- Colors — the default color sequence applied to series. See 5.9.
- Gauge ranges — green/yellow/red from–to thresholds and min/max, used when the chart type is a gauge.
- Axis defaults — x-axis and y-axis titles, label rotation, min/max, prefix/suffix, decimal count.
A Reset to default configurations button appears once a library has been saved, letting you clear the stored defaults.
Advanced tab (/admin/config/content/charts/advanced)¶
Two settings:
- Enable Charts Debug — renders the JSON object generated for each chart in a code block beneath the chart. This is the single most useful troubleshooting tool in the module; see 6.7 and the debugging notes below.
- Use a CDN by default for external libraries — when checked, the module loads each library from a CDN unless a local copy is present. When unchecked, missing-library warnings on the Status report are suppressed. Note the built-in caution: relying on a CDN can cause issues with Ajax and BigPipe, so hosting libraries locally is recommended for production.
5.2 Charts with Views¶
Views is the right tool when the data is already in your site and may change — for example, "number of attendees per event" or "articles published per month."
Steps:
- Create a View at
/admin/structure/views/add. For the display format, choose Chart. - Add a label field. Under Fields, add the field whose values become the labels along one axis (or the pie slices). For an Event content type this might be the
titlefield. - Add one or more data fields. Add a field that supplies the numeric values — e.g.
field_number_attendees. The field's label becomes the series name in the legend. Add another data field for each additional series. Note that single-dimension types such as pie only support one data column. - Configure the chart. In the Format section, click Settings next to "Chart." Choose the library, chart type, the label field, and which fields are the data providers. The rest of the display, legend, tooltip, axis, and color options are the same shared settings described in 5.1.
- Save the View.
Workflow tip. It's often easier to build the View as a Table display first, confirm the columns look right, then switch the display format to Chart. Seeing the data laid out in rows and columns makes it much clearer what the chart will plot.
When you save a Views chart, the module also validates your selections: it will surface an error if no valid library is selected, or if the site-default library is chosen but hasn't actually been configured, or (outside the initial "add view" screen) if no data fields have been picked.
5.2.1 Plotting a field vs. aggregating¶
There are two fundamentally different ways your data field produces numbers, and choosing correctly is the most common stumbling point in Views charting.
-
Plot an existing numeric field (no aggregation). If each row of content already carries the number you want — an Event's
field_number_attendees, a product's price, a measurement — add that field as the data provider and leave the View unaggregated. Each result row becomes one data point, and the label field supplies its label. Use this when the value lives on the entity. -
Aggregate to compute a number. If the number you want doesn't exist as a field — you want how many nodes per category, or the sum of sales per month — you must turn on Use aggregation in the View's advanced settings, then apply an aggregation function (
count,sum,average, etc.) to the data field. Aggregation collapses many rows into grouped totals: for a "count of articles per month" chart, the month field is the label and the aggregated count is the value.
A quick test: "Is the number I want to chart already stored on each item?" If yes, plot the field. If the number only exists once you tally or total rows together, aggregate.
5.2.2 Getting multiple series: fields, grouping, and attachments¶
A chart with more than one series (more than one line, or grouped/stacked columns) can come about three different ways. Picking the right one is the other common point of confusion, so here they are side by side.
1. Multiple data-provider fields. Add several numeric fields as data providers; each becomes its own series, drawn side by side. Use this when your series are separate columns in the same row — e.g. "2023 revenue" and "2024 revenue" fields on each region.
2. Grouping. Add a single data field, then set a Grouping field (in the Format → Settings area; it's Views' native grouping, repurposed). The module splits the rows by that field's distinct values and turns each value into its own series from that one data column, all sharing the common axis. Key points:
- Grouping produces one series per distinct value of the grouping field. The classic use case: plotting a rate over time with a line per geographic area — time is the axis, the rate is the single data field, and grouping by region yields one line per region so you can compare them.
- Only one grouping level is supported (Charts hides the second).
- Grouping unlocks per-group colors: when you group by a taxonomy/entity-reference field, you can color each series from the referenced entity rather than from the default palette (the "entity grouping" color settings) — handy when each region/category has a canonical color.
- If you have only one data field and no grouping, there's nothing to split — you get a single series.
Use grouping when your series come from one column split by a category — one "rate" (or "amount") field, split into a series per "region" (or "status", "term", etc.).
Stacking is a separate concern. Whether series stack is controlled by an independent "Enable stacking" display option (available on stackable types such as area, bar, and column), not by grouping. Stacking stacks whatever series exist — whether they came from multiple data fields or from grouping — based on the label field. You can group without stacking (the geographic line-per-region example above is grouped but not stacked), and you can stack multiple data fields without any grouping. Treat "how many series and where they come from" (fields/grouping/attachment) and "do those series stack" (the stacking option + a stackable chart type) as two independent decisions.
3. A Chart attachment. Add a separate Chart attachment display and attach it to the parent (covered in 5.3). Its headline capability — and the main reason to reach for it — is the combo chart: because the attachment is its own display with its own chart type, it is the only way to mix chart types in a single chart, for example columns with a trend line drawn over them. A secondary y-axis is the common companion to that (combined series often measure different things and need different scales), but the defining feature is the ability to combine types. Use an attachment whenever a series needs its own chart type or axis rather than sharing the parent's.
Grouping vs. a Chart attachment — how to choose. They solve different problems, and the module's own form help points this out:
| Use grouping when… | Use a Chart attachment when… |
|---|---|
| All series share one chart type and one axis | You want a combo chart — mixing chart types (e.g. a line over columns) |
| Series come from one data column split by a category field | A series needs a separate (secondary) y-axis |
| You want one series per category value (e.g. a line per region) | You want to overlay an independently-configured series |
| Per-group coloring from a taxonomy/entity field is useful | The extra series comes from the same query but must be styled/typed on its own |
In short: grouping multiplies one series across a category within a single display; a Chart attachment brings in a separately-configured series via a second display, and is what makes combo charts (mixed chart types, optionally on a secondary axis) possible. If you need to mix chart types — or grouping simply can't express your extra series because it must differ in type or axis — reach for the attachment.
5.3 Combo charts (multiple chart types in one)¶
To combine chart types — for example columns overlaid with a line — use a Chart attachment display (internally the chart_extension Views display). This is the third route to multiple series described in 5.2.2; reach for it when grouping or extra data fields can't express what you need because a series must have its own chart type or axis.
- In your View, add a new display of type Chart attachment.
- In the attachment's settings, use the middle "Attach to" column to attach it to a parent chart display. This attachment is required — the extension renders as part of its parent.
- In the same area you can configure the attachment to:
- Inherit exposed or contextual filters from the parent, so both series respond to the same filtering.
- Use the primary or a secondary y-axis — put the line on its own axis when its scale differs from the columns.
- Configure the attachment's own Settings in the Format section (choose its chart type, e.g. "line").
The parent supplies one series (e.g. columns); the attachment supplies the other (e.g. a line), optionally on a second axis.
5.4 Exposing the chart type to visitors¶
The Exposed chart type Views field (field_exposed_chart_type) lets site visitors switch the chart type themselves via an exposed control. Add it like any other field; when present, the View stores the visitor's selection and overrides the configured chart type at render time. This is handy for dashboards where users may prefer, say, a line versus a column view of the same data.
5.5 Specialized Views data fields¶
Beyond ordinary numeric fields, the module ships Views field handlers for chart types whose data points are not single numbers:
- Scatter data (
field_charts_fields_scatter) — supplies[x, y]coordinate pairs for scatter charts. - Bubble data (
field_charts_fields_bubble) — supplies[x, y, size]triples for bubble charts, where the third value drives the bubble radius. - Numeric array (
field_charts_numeric_array) — provides a set of numeric values for a single field, useful for chart types that expect an array of numbers per row (for example boxplot-style data).
Add whichever matches your chart type under Fields, then select it as the data provider in the chart Settings. All three implement the same internal interface as ordinary data fields, so they slot into the label/data-provider selection identically.
5.6 Charts with a Chart field¶
When a chart belongs to one specific entity and its data doesn't need to be queried dynamically, use the Chart field (chart_config field type).
- On your content type (or any entity bundle), go to Manage fields › Add field and choose the Chart field type.
- Configure the field's widget. The default widget exposes one setting, "Allow users to change the default charting library":
- When enabled, editors can pick the library per entity.
- When disabled, the field locks to the site's default library (configured on the Charts settings page), and editors only see the remaining options.
- When creating or editing an entity, the field presents the full chart settings form plus a data-collector table for entering the series and labels. You can type data directly into the table or import it from a CSV (see 5.8).
- Save the entity. The default formatter renders the chart wherever the entity is displayed.
Because the field embeds the same charts_settings element (in "basic form" mode with series collection turned on), everything you can configure for a Views chart is available here too.
5.7 Charts with a Chart block¶
The charts_blocks submodule provides two block plugins for placing static charts through Block layout or Layout Builder. Enable charts_blocks to use them.
Charts block (charts_block)¶
The standard block. It embeds the full chart settings form and the AJAX-driven data-collector table, so you get the same rich data entry (manual table or CSV import) as the Chart field. Place it like any block and enter your data in the block configuration form.
Chart (Canvas) block (charts_canvas_block)¶
A variant built for Drupal Canvas (and any context where the AJAX data-collector table is awkward). Instead of the interactive table, data is entered as CSV or JSON in a textarea. Everything still renders through the same Charts API render element, so it remains library-agnostic and keeps the accessible-table fallback and alter hooks.
Choose a Data format (CSV or JSON), then paste your data:
- CSV — a header row whose first cell is the category label and whose remaining cells are series names, followed by one row per category:
Quarter,Product A,Product B
Q1,120,90
Q2,145,110
- JSON — an object with
categoriesandseriesarrays; each series may specify acolorand atarget_axis:
{
"categories": ["Q1", "Q2"],
"series": [
{"name": "Product A", "data": [120, 145], "color": "#1f77b4"},
{"name": "Product B", "data": [90, 110], "target_axis": "secondary_yaxis"}
]
}
For pie and donut charts, only the first series is used and the categories become the slice labels. The block validates input on save — JSON must parse and contain a non-empty series array; CSV must have a header row and at least one data row.
5.8 The data-collector table¶
The data-collector table (chart_data_collector_table form element) is the grid used for manual data entry in the Chart field and the standard Chart block. It lets you:
- Enter category labels and one or more series of values directly into a table.
- Import data from a CSV file. When CSV import is enabled, the element shows a CSV separator selector, a file upload restricted to
.csv, and an Upload CSV button. Importing overwrites all current data in the table, so use it on an empty or expendable grid.
The table is what feeds the chart's series and axis labels behind the scenes, so the shape you enter here maps directly to the render-array structure described in Section 6.
5.9 Color customization¶
- Default palette. New charts inherit the color sequence configured on the Charts settings page. The stored default palette contains roughly two dozen colors so charts with many series still get distinct colors; individual libraries fall back to their own built-in palette (blues, greens, reds…) if none is set.
- Per-series color. In any data-entry context you can override the color of a specific series.
- The color changer. Some libraries ship an optional interactive color changer (a small form, backed by each library's
color_changer.js) that lets a user recolor a rendered chart on the fly. It's toggled by thecolor_changerdisplay setting and is available where the library provides it.
5.10 Other creation methods (ecosystem modules)¶
Beyond the three built-in UI methods above and the API (Section 6), two contributed ecosystem modules add further ways to place a chart. Both are separate installs but render through the same Charts API and library plugins, so settings, providers, and alter hooks behave identically.
- Charts Twig (
charts_twig) — provides achart()Twig function that renders a chart from a template using the Charts API. Pass it an options array (id,chart_type,title,series,axes,raw_options). Best for themers who need a chart in a specific template region without Views or a block. Walkthrough: §15.9. - Charts Text Filter (
charts_text_filter) — adds a Chart button to the CKEditor 5 toolbar plus a text filter, letting content editors configure and embed a chart inline within body copy through a dialog. Walkthrough: §15.8.
Together with Views, the Chart field, the Chart block, and the API, these bring the total to six ways to make a chart. See the Provider Reference for the additional charting-library modules (ECharts, ApexCharts, Kendo UI, Plotly).
6. Developer Guide — The Charts API¶
In brief — a chart is a Drupal render array with #type => 'chart', holding chart_data series and optional chart_xaxis/chart_yaxis children; return it from a controller or block and the active provider renders it. This section documents all five render elements and their properties, worked examples, the #raw_options escape hatch, and the accessible-table fallback.
Everything the UI does is ultimately a Drupal render array. If you'd rather build charts in code — in a controller, a block, a preprocess hook, or a custom module — you construct a chart element and let Drupal render it. This section is the reference for that API.
The canonical reference implementation is the charts_api_example submodule. Enable it and visit /charts/example/display to see every example below rendered live, and read modules/charts_api_example/src/ChartExampleBuilder.php for the source.
6.1 The render-array model¶
A chart is an element with #type => 'chart'. You add child elements for the data series and (for two-axis charts) the axes, then render it:
$chart = [
'#type' => 'chart',
'#chart_type' => 'column',
'series' => [
'#type' => 'chart_data',
'#title' => $this->t('Responses'),
'#data' => [60, 40],
],
'xaxis' => [
'#type' => 'chart_xaxis',
'#labels' => [$this->t('Yes'), $this->t('No')],
],
];
$output = \Drupal::service('renderer')->render($chart);
Because it's a normal render array, you can return it directly from a controller or block build() method — you rarely need to call the renderer yourself. Caching, attachments, and alter hooks all work as they do for any element.
There are five element types in the API. Four are the ones you compose (chart, chart_data, chart_xaxis, chart_yaxis); a fifth (chart_data_item) styles an individual data point.
6.2 Element reference¶
6.2.1 chart — the container¶
The top-level element. It carries chart-wide settings and holds the series/axis children. Selected properties (see src/Element/Chart.php for the full list):
| Property | Purpose |
|---|---|
#chart_type |
The chart type ID: line, column, bar, pie, donut, gauge, scatter, bubble, area, spline, etc. |
#chart_library |
Which library renders it (e.g. highcharts). Omit or use site_default to resolve the configured default. |
#chart_id |
Optional identifier, used to target per-chart alter hooks. |
#title, #subtitle |
Chart titles, with companion #title_color, #title_font_size, #title_position (out/in), etc. |
#colors |
Array of hex colors for the series sequence. Defaults to the module palette. |
#legend, #legend_position |
Toggle and place the legend (right, bottom, …), with legend font/title options. |
#tooltips, #tooltips_use_html |
Enable tooltips; optionally allow HTML in them. |
#data_labels, #data_markers |
Show inline value labels / point markers. |
#stacking |
Stack series (for stackable types like column/area). |
#connect_nulls |
Whether lines bridge missing points. |
#background |
Chart background (default transparent). |
#width, #height |
Dimensions. |
#gauge |
Gauge threshold config (green_from/green_to, yellow_*, red_*, min, max). |
#accessible_table |
Accessible data-table fallback mode: disabled, collapsible, or a button variant. See 6.7. |
#raw_options |
Library-native options merged into the final definition. See 6.5. |
Two-axis charts don't strictly require you to declare axes — during pre-render the module injects default chart_xaxis and chart_yaxis children if you didn't supply them, so a minimal two-axis chart still renders. Declare them yourself when you want to set titles, ranges, or a secondary axis.
6.2.2 chart_data — a data series¶
Each series is a chart_data child. Properties (see src/Element/ChartData.php):
| Property | Purpose |
|---|---|
#title |
Series name (shown in the legend). |
#data |
The values. Simple types take a flat array ([257, 235, 325]); coordinate types take arrays of points ([[x, y], …] for scatter, [[x, y, size], …] for bubble). |
#color |
Series color. |
#chart_type |
Per-series type override — this is how combo charts work (a line series inside a column chart). |
#target_axis |
The key of the y-axis this series binds to, for dual-axis charts. |
#show_in_legend, #show_labels |
Legend and inline-label visibility. |
#line_width, #marker_radius |
Line/marker styling (line charts). |
#decimal_count, #date_format, #prefix, #suffix |
Value formatting. |
#raw_options |
Per-series library-native options. |
6.2.3 chart_xaxis / chart_yaxis — the axes¶
Both extend a shared base (src/Element/ChartAxisBase.php) and accept the same properties:
| Property | Purpose |
|---|---|
#title |
Axis title. |
#labels |
Category labels (typically on the x-axis). |
#axis_type |
linear, logarithmic, datetime, or labels. |
#min, #max |
Axis bounds. |
#opposite |
Render the axis on the opposite side — used for a secondary y-axis. |
#labels_rotation |
Rotate labels by a degree value (e.g. -45). |
#title_*, #labels_*, #grid_line_color, #base_line_color |
Font and grid styling. |
#raw_options |
Library-native axis options. |
6.2.4 chart_data_item — a single point¶
For per-point control, a series' data can contain chart_data_item elements instead of bare values, each with its own #data, #color, #title (often the tooltip content), and #raw_options.
6.3 Worked examples¶
These are drawn from the example builder and render on /charts/example/display.
Multi-series column with axes:
$chart = [
'#type' => 'chart',
'#chart_type' => 'column',
'#title' => $this->t('Drupal Installs'),
'series_one' => [
'#type' => 'chart_data',
'#title' => $this->t('5.0.x'),
'#data' => [257, 235, 325, 340],
'#color' => '#1d84c3',
],
'series_two' => [
'#type' => 'chart_data',
'#title' => $this->t('8.x-3.x'),
'#data' => [4330, 4413, 4212, 4431],
'#color' => '#77b259',
],
'x_axis' => [
'#type' => 'chart_xaxis',
'#title' => $this->t('Months'),
'#labels' => [
$this->t('January 2021'), $this->t('February 2021'),
$this->t('March 2021'), $this->t('April 2021'),
],
],
'y_axis' => [
'#type' => 'chart_yaxis',
'#title' => $this->t('Number of Installs'),
],
];
Combo chart with a secondary y-axis — override the second series' type to line, point it at a secondary axis, and declare that axis with #opposite:
$chart['series_two'] = [
'#type' => 'chart_data',
'#chart_type' => 'line',
'#title' => $this->t('8.x-3.x'),
'#data' => [4330, 4413, 4212, 4431],
'#color' => '#77b259',
'#target_axis' => 'y_axis_secondary',
];
$chart['y_axis_secondary'] = [
'#type' => 'chart_yaxis',
'#title' => $this->t('Secondary Y-Axis'),
'#opposite' => TRUE,
];
Stacked chart — add #stacking => TRUE to the chart alongside multiple series.
Gauge — set #chart_type => 'gauge', supply a single-value series ('#data' => [65]) and a #gauge threshold array.
Scatter / bubble — set the matching type, enable #data_markers, and provide point arrays: [[162.2, 51.8], …] for scatter, [[162.2, 51.8, 10], …] for bubble (the third value is the bubble size).
Data from a CSV file — read the file yourself and feed the columns into #data and #labels. The example builder reads a fixture CSV, maps columns to series, and builds a stacked area chart from them; the pattern is just ordinary PHP file reading producing arrays that you drop into chart_data elements.
6.4 The example-builder pattern¶
ChartExampleBuilder::build($library) is worth studying because it shows the idiomatic way to write library-agnostic chart code. Rather than branching on which library is active, it asks the library plugin what it supports:
$plugin = $this->chartManager->createInstance($library);
foreach (['area', 'bar', 'column', 'line', 'spline', 'pie', 'donut'] as $type) {
if (!$plugin->isSupportedChartType($type)) {
continue;
}
// …build an example for $type…
}
This means the same code produces the right set of examples for Highcharts (which adds dumbbell, solidgauge) or Chart.js (which supports radar via the polar setting) without any if ($library === …) checks. When you write code that should work across libraries, follow the same approach: build the generic render array and let the library plugin and the alter hooks handle the differences.
Note also that some "types" are really display settings. A radar chart, for instance, is a cartesian line chart with the polar coordinate setting (#polar => TRUE) enabled — Highcharts renders it as a polar line, Chart.js as a radar — which is why the builder exposes it as a separate opt-in helper rather than a distinct type.
6.5 Raw options — the escape hatch¶
Every chart, series, axis, and data item accepts #raw_options: an array merged directly into the final library definition, letting you reach features the module doesn't expose. Internally, ApplyRawOptionsTrait::applyRawOptions() deep-merges your #raw_options over the generated definition:
'#raw_options' => [
// Highcharts-specific example.
'plotOptions' => [
'series' => ['animation' => ['duration' => 2000]],
],
],
Portability caveat: the shape of #raw_options is library-specific. Options written for Highcharts won't mean anything to Google Charts. If you switch libraries, your raw options may silently stop applying, so isolate them and document which library they target.
For cases where you need to alter the entire generated definition (not just merge into one element), use the alter hooks instead — see Section 8 (hook_chart_definition_alter()), which runs after the module has produced the library-specific definition.
6.6 Programmatic library and type selection¶
- Resolving the default library. If you pass
#chart_library => 'site_default'(or leave it empty), the module resolves the configured site default for you. In code,\Drupal::service('plugin.manager.charts')(theChartManager) does this: callingcreateInstance('site_default', …)transparently swaps in the configured default library plugin. If the requested library isn't available, it falls back to the first available library rather than failing. - Checking type support. Call
$plugin->isSupportedChartType($type)before offering or building a type, as shown above.$plugin->getSupportedChartTypes()returns the full list (and fires thehook_charts_plugin_supported_chart_types_alter()alter so other modules can extend it). - The chart type registry. Chart types themselves (their labels, axis mode, whether they stack or use coordinates) come from
*.charts_types.ymlfiles, managed by theplugin.manager.charts_typeservice (TypeManager). Query it when you need type metadata rather than hard-coding a list.
6.7 The accessible-table fallback¶
Every chart can render an accompanying HTML data table built from the same series and labels, so the underlying numbers are available to screen readers and to users without JavaScript. It's controlled by the #accessible_table property on the chart element:
disabled— no table.collapsible— the table is placed in a details/disclosure element beneath the chart (you can set the disclosure button text and class via#accessible_table_button_text/#accessible_table_button_class).
The table is produced by the charts.table_builder service (ChartTableBuilder), which the chart element invokes during rendering — you don't call it directly; you just choose the mode. For accessibility, prefer collapsible over disabled on public-facing charts.
Debugging tip¶
Turn on Enable Charts Debug on the Advanced settings page (or set it per site) to render the exact JSON definition the module hands to the charting library, in a code block beneath the chart. That JSON can usually be pasted straight into the charting library's own online playground, which makes it easy to see whether a problem is in your render array, in the module's translation of it, or in the library itself.
7. Provider Reference¶
In brief — each provider (Highcharts, Google Charts, Chart.js, C3, Billboard.js) supports a different set of chart types and carries different licensing; the support matrix shows which. Nine types — area, bar, bubble, column, donut, line, pie, scatter, spline — work in every bundled provider, so charts built from those are fully portable. Highcharts has the widest type support but is free for non-commercial use only.
This section is the lookup companion to the rest of the manual: what each charting library can draw, what it costs, where its JavaScript comes from, and where its sharp edges are. Use the comparison matrix to shop for a provider, then the per-provider entries for the details.
7.1 How provider capabilities work¶
Two things determine whether a chart type is available:
- The type must exist in the module's registry — either in the core
charts.charts_types.ymlor added by a provider (or your own module). The core registry defines area, area range, bar, boxplot, bubble, candlestick, column, donut, gauge, heatmap, line, pie, scatter, and spline; individual providers add a few more (Highcharts adds solid gauge and dumbbell, Google adds table, Chart.js adds polar area). - The selected provider must declare support for it. Each provider lists its supported types, and the UI only offers those. If a provider doesn't list a type, it won't appear as an option for that provider — and attempting it via the API raises an error.
A consequence worth remembering: "radar" is not a type. It's a cartesian chart rendered with the polar coordinate setting (#polar) enabled — Highcharts draws it as a polar line, Chart.js draws it as a radar. Providers that don't honor the polar setting simply won't produce it. (Chart.js additionally offers a distinct polar area type, which is a different thing.)
7.2 Chart type support matrix¶
Support across the five bundled providers (accurate for 5.2.x). ✓ = the provider declares support for that type.
| Chart type | Highcharts | Chart.js | C3 | Billboard.js | |
|---|---|---|---|---|---|
| Area | ✓ | ✓ | ✓ | ✓ | ✓ |
| Area range | ✓ | — | — | — | ✓ |
| Bar | ✓ | ✓ | ✓ | ✓ | ✓ |
| Boxplot | ✓ | ✓ | — | — | — |
| Bubble | ✓ | ✓ | ✓ | ✓ | ✓ |
| Candlestick | — | ✓ | — | — | ✓ |
| Column | ✓ | ✓ | ✓ | ✓ | ✓ |
| Donut | ✓ | ✓ | ✓ | ✓ | ✓ |
| Dumbbell | ✓ | — | — | — | — |
| Gauge | ✓ | ✓ | — | ✓ | ✓ |
| Heatmap | ✓ | — | — | — | — |
| Line | ✓ | ✓ | ✓ | ✓ | ✓ |
| Pie | ✓ | ✓ | ✓ | ✓ | ✓ |
| Polar area | — | — | ✓ | — | — |
| Scatter | ✓ | ✓ | ✓ | ✓ | ✓ |
| Solid gauge | ✓ | — | — | — | — |
| Spline | ✓ | ✓ | ✓ | ✓ | ✓ |
| Table* | — | ✓ | — | — | — |
Radar (via #polar) |
✓ | — | ✓ | — | — |
* Google's "table" renders the data as an HTML table through the Google visualization API rather than as a graphical chart.
Reading the matrix: Highcharts is the most capable by type count; Google and Billboard are close behind and are the two that offer candlestick; Chart.js and C3 cover the mainstream types. If you need boxplot/heatmap/solid gauge/dumbbell, Highcharts is effectively required; candlestick means Google or Billboard.
7.3 The portable core¶
Nine types are supported by every bundled provider:
area, bar, bubble, column, donut, line, pie, scatter, spline.
If you build charts using only these, you can switch providers freely — which is the module's central promise. Straying outside this set ties a chart to the subset of providers that support the type (and, if you use #raw_options or #chart_definition, potentially to a single provider). When portability matters, design against the core nine.
7.4 Per-provider reference¶
Highcharts (charts_highcharts)¶
- Supported types: area, area range, bar, boxplot, bubble, column, donut, dumbbell, gauge, heatmap, line, pie, scatter, solid gauge, spline — plus radar via
#polar. The widest coverage of any bundled provider. - Library / source: Highcharts 12.5.0 in 5.2.x, loaded from jsDelivr in CDN mode.
- Licensing: ⚠️ Free for non-commercial use only; a commercial license is required for commercial sites. This is the single biggest decision factor — Highcharts is the most capable option but the only bundled provider that isn't free for commercial use.
- Optional modules: ships a large set of add-on files you can install as needed —
more(for range/bubble/polar),3d,exporting/export-data,accessibility,annotations,boost,coloraxis,data,dumbbell,solid-gauge,pattern-fill,heatmap,no-data-to-display, and themes such ashigh-contrast-light. Enable the ones your charts require via the provider's configuration. - Gotchas: the Status report raises an error if your Highcharts copy contains an
exporting-serverdirectory (unsafe sample files — delete it). Because it has the most native features, it's also the provider people most often extend via#raw_options/#chart_definition. - Best for: the richest visualizations and the broadest type support, when licensing is acceptable.
Google Charts (charts_google)¶
- Supported types: area, bar, boxplot, bubble, candlestick, column, donut, gauge, line, pie, scatter, spline, plus a table rendering. One of two providers with candlestick.
- Library / source: loaded via Google's
loader.js(version 45 in 5.2.x), served fromgstatic.com. - Licensing: free to use under Google's terms (the loader is labeled Apache 2.0).
- Gotchas: what you host locally is only Google's small loader — the actual chart code is fetched from Google at runtime, so Google Charts is never fully self-hosted, which has privacy/offline implications. If you hit an SSL error installing the loader via Composer, apply the
http://+"secure-http": falseworkaround from the provider README. Its Status-report severity when missing is a warning (not an error), reflecting the loader-based model. - Best for: quick, no-license-cost interactive charts when an external dependency on Google is acceptable.
Chart.js (charts_chartjs)¶
- Supported types: area, bar, bubble, column, donut, line, pie, polar area, scatter, spline — plus radar via
#polar. No gauge, boxplot, or candlestick. - Library / source: Chart.js 4.4.x in 5.2.x, installed from npm (via asset-packagist), CDN-able from unpkg.
- Licensing: open source (MIT); free for commercial use.
- Companions: needs two helper packages —
chartjs-adapter-date-fns(for time/date axes) andchartjs-plugin-datalabels(for inline data labels). - Gotchas: the npm package brings many files you don't need to serve; use the README's
clean-chartjs.shcleanup script and Composer post-install hooks. Uniquely offers the polar area type. - Best for: a modern, permissively licensed, widely known library for mainstream chart types.
C3 (charts_c3)¶
- Supported types: area, bar, bubble, column, donut, gauge, line, pie, scatter, spline.
- Library / source: C3 0.7.20 in 5.2.x, from cdnjs; requires d3 (7.9.0) plus a bundled d3-c3 shim.
- Licensing: open source (MIT); d3 is BSD.
- Gotchas: C3 hasn't been updated since ~2019–2020 and originally depended on an old, insecure d3; the module ships a shim so a modern d3 works with it. The maintainers recommend Billboard.js instead — it's the actively maintained fork. Has an optional
clean-c3js.shcleanup script. - Best for: existing sites already standardized on C3; new projects should prefer Billboard.
Billboard.js (charts_billboard)¶
- Supported types: area, area range, bar, bubble, candlestick, column, donut, gauge, line, pie, scatter, spline. One of two providers with candlestick, and it also does area range.
- Library / source: Billboard.js 3.10.3 in 5.2.x (npm dependency tracks ^3.16), served from the Billboard release CDN; requires d3 (7.9.0) and ships a CSS file.
- Licensing: open source (MIT); d3 is BSD.
- Gotchas: remember the accompanying stylesheet (it's part of the library definition). Has an optional
clean-billboardjs.shcleanup script. - Best for: a maintained, permissively licensed D3-based library — the recommended choice over C3, and a good candlestick/area-range option without Highcharts' licensing.
7.5 Additional ecosystem providers¶
Beyond the bundled five, these providers are available as separate contributed modules and behave the same way (enable, set default, install library, build):
- Charts ECharts (
charts_echarts) — Apache ECharts; Apache-2.0 licensed. - ApexCharts (
charts_apexcharts) — ApexCharts.js; MIT, free for commercial use. - Charts Kendo UI (
charts_kendo_ui) — Kendo UI charts; a commercially licensed library. - Charts Plotly (
charts_plotly) — Plotly.js; MIT, free for commercial use.
Their supported-type lists and library-install specifics live in each project's own README. Several install their JavaScript via the asset-packagist Composer approach, since their libraries are npm-published.
7.6 Choosing a provider¶
A short decision guide:
- Need the broadest type support (heatmap, solid gauge, dumbbell, boxplot)? → Highcharts — if you can meet its commercial-licensing terms.
- Need candlestick without Highcharts' license? → Google or Billboard.js.
- Want permissive licensing and a mainstream, well-known library? → Chart.js.
- Want a maintained D3-based library? → Billboard.js (not C3).
- Fine depending on Google and don't need self-hosting? → Google Charts.
- Maximum portability across providers? → build with the core nine types and any provider works.
Whatever you choose, you can change your mind later: as long as your charts stay within a new provider's supported types, switching is a matter of installing the new library and updating your default at /admin/config/content/charts.
8. Extending the Module¶
In brief — extension mechanisms run along a portability spectrum, from most portable to most library-locked: add a chart type (*.charts_types.yml + supported-types alter), alter the render array (hook_chart_alter), merge native options (#raw_options), alter the generated definition (hook_chart_definition_alter), or replace it entirely with #chart_definition — the escape hatch that bypasses the module's chart-type limits to render any library-native type.
The module deliberately standardizes on the chart types and options common across libraries, so the UI stays clean and charts stay portable. But you will sometimes need more — a library-native chart type the module doesn't model, an option it doesn't expose, or a programmatic tweak to many charts at once. This section covers the ways to do that, from the most portable to the most library-specific.
Note on scope: writing an entirely new provider submodule is possible but rarely necessary now — the bundled providers plus the ecosystem modules (ECharts, ApexCharts, Kendo UI, Plotly) cover essentially all widely used libraries. This section therefore focuses on extending behavior; adding a provider is summarized briefly at the end.
8.1 The extension spectrum¶
Pick the lightest tool that solves your problem. The options, from most portable to most library-locked:
| Mechanism | What it does | Portable across libraries? | Reach |
|---|---|---|---|
Add a chart type (*.charts_types.yml + alter) |
Makes a new type a first-class option in the UI and API | Partially — you add it per provider | All charts of that type |
hook_chart_alter() |
Alters the render array before conversion | Yes — operates on module properties | Targeted charts |
#raw_options |
Merges native options into the generated definition | No — options are library-specific | Per chart/series/axis/point |
hook_chart_definition_alter() |
Alters the generated definition after conversion | No — definition shape is library-specific | Targeted charts |
#chart_definition |
Replaces the generated definition with your own native config | No — fully library-specific | A single chart, no limits |
The rule of thumb: stay as far up this list as you can. Reach for #chart_definition (the bottom) only when you genuinely need a capability the module can't reach any other way, and accept that such a chart is tied to one library.
8.2 Altering the render array — hook_chart_alter()¶
The gentlest hook. It fires on the chart render array, before the module converts it into a library definition, so you're working with the module's own #properties and it stays library-agnostic.
/**
* Implements hook_chart_alter().
*/
function mymodule_chart_alter(array &$element, $chart_id) {
if ($chart_id === 'view_name__display_name') {
$element['#title_font_size'] = 20;
}
}
There's also a per-chart variant, hook_chart_CHART_ID_alter(), which puts the chart ID in the function name instead of a parameter — handy when you only care about one chart. (Charts only have an ID if #chart_id is set.)
Use this when the change can be expressed in the module's own vocabulary. Because it runs pre-conversion, it works no matter which library ends up rendering.
8.3 Adding native options — #raw_options¶
Every chart, series, axis, and data item accepts a #raw_options array. Its contents are deep-merged into the generated definition for that element, letting you set library-native options the module doesn't model — without replacing anything the module produced.
$chart['series_one'] = [
'#type' => 'chart_data',
'#title' => $this->t('Revenue'),
'#data' => [10, 20, 30],
// Highcharts-native options merged into this series.
'#raw_options' => [
'dashStyle' => 'ShortDash',
'zoneAxis' => 'x',
],
];
This is the right tool when the module gets you 90% of the way and you just need to switch on one or two extra options. The trade-off: #raw_options shapes are library-specific, so options written for one provider are ignored by another. Keep them isolated and document which library they target.
8.4 Altering the generated definition — hook_chart_definition_alter()¶
When you need to change the result of conversion — the actual library object — use this hook. It fires after the module has built the definition, receives it by reference, and (because the definition's structure differs per library) is inherently library-specific.
/**
* Implements hook_chart_definition_alter().
*/
function mymodule_chart_definition_alter(array &$definition, array $element, $chart_id) {
if ($element['#chart_library'] === 'highcharts') {
$definition['title']['style']['fontSize'] = 20;
// Pull a caption from a field on the entity holding the chart.
$node = $element['#entity'] ?? NULL;
if ($node instanceof \Drupal\node\NodeInterface && $node->hasField('field_caption')) {
$definition['caption']['text'] = $node->field_caption->value;
}
}
}
A per-chart variant, hook_chart_definition_CHART_ID_alter(), exists as well. Note the guidance in the module's own hook docs: this hook is powerful but fragile — because it manipulates the raw, library-specific structure, switching libraries can break code that relies on it. Branch on $element['#chart_library'] and treat it as library-coupled.
8.5 Replacing the definition entirely — the #chart_definition escape hatch¶
This is the ultimate extension point, and the one that lets you escape the chart-type limits the module imposes.
Normally you describe a chart with the module's elements (#chart_type, series, axes), and the provider plugin builds the library definition from them. But if you set the #chart_definition property to a complete, library-native configuration array, the provider plugin detects it and uses it as-is, skipping its own populate-from-series logic entirely. The provider's code comment states the intent plainly: "This allows bypassing the Drupal Charts abstraction layer."
Why it escapes the type gate¶
During pre-render the module enforces type support like this: if #chart_type is set to a type the selected library doesn't declare, it throws a LogicException. Crucially, that check only looks at #chart_type — it never inspects the contents of #chart_definition. So to render a library-native type the module doesn't model (a Highcharts streamgraph, networkgraph, sankey, wordcloud, etc.), you:
- Set
#chart_libraryso the correct provider plugin and its JavaScript load. - Omit
#chart_type(or set it to a type the library does support) so the type gate doesn't fire. - Put your full native config — including the real, library-native chart type — in
#chart_definition.
$chart = [
'#type' => 'chart',
'#chart_library' => 'highcharts',
// No #chart_type — we're supplying the definition directly.
'#chart_definition' => [
'chart' => ['type' => 'streamgraph'], // a native type the module doesn't model
'title' => ['text' => $this->t('Traffic by channel')],
'xAxis' => ['categories' => ['Jan', 'Feb', 'Mar']],
'series' => [
['name' => 'Organic', 'data' => [3, 4, 3]],
['name' => 'Paid', 'data' => [1, 2, 5]],
],
],
];
The module JSON-encodes this into the data-chart attribute that the library reads, and the chart renders — using a type the module's UI would never have offered.
Notes and caveats¶
- Accepts an array or a JSON string. You can pass a PHP array (as above) or a JSON string; the module decodes a string automatically. Pasting a config straight from the library's online playground works well.
hook_chart_definition_alter()still runs on a definition you supply this way, so other modules can still adjust it.- You give up portability and the module's conveniences. A
#chart_definitionchart is bound to one library, and the module no longer manages its options, colors, or accessibility table from your element properties — because it's not building the definition. This is the deliberate trade for unlimited access. - Prefer lighter tools when they suffice. If you only need to tweak an option, use
#raw_options(§8.3) or a definition-alter hook (§8.4). Reserve#chart_definitionfor when you need a whole chart the module can't otherwise express.
This escape hatch is what makes the module's standardization safe: the common types stay clean and portable, and nothing stops a developer who needs the full power of a specific library from using it.
8.6 Adding a chart type to the module¶
If a type is worth making a first-class, reusable option (appearing in the UI, passing the type gate, usable without #chart_definition), register it properly rather than hand-rolling each chart.
Step 1 — declare the type. Add a <module>.charts_types.yml file to your module. Each entry carries the type's metadata:
candlestick:
label: 'Candlestick'
axis: 'xy' # 'xy' (dual-axis) or 'y_only' (single-axis)
axis_inverted: false
stacking: false
coordinate: false # true for coordinate types like scatter/bubble/heatmap
The TypeManager discovers these YAML files across all modules, so simply providing the file adds the type to the registry (with label required).
Step 2 — advertise it on the providers that can draw it. A type in the registry still needs each provider to declare support. Implement the supported-types alter hook:
/**
* Implements hook_charts_plugin_supported_chart_types_alter().
*/
function mymodule_charts_plugin_supported_chart_types_alter(array &$types, string $chart_plugin_id) {
if ($chart_plugin_id === 'highcharts') {
$types[] = 'candlestick';
}
}
Now candlestick passes isSupportedChartType() for Highcharts, appears where types are chosen, and can be rendered normally — you'll typically also add a definition-alter hook or #raw_options to shape the library-native specifics of the new type.
Runtime alteration via events. The type registry can also be altered programmatically. The TypeManager dispatches a TypesInfoEvent (ChartsEvents::TYPE_INFO) whenever it assembles the type definitions, so an event subscriber can add, remove, or modify types dynamically rather than through static YAML.
8.7 Registering a new provider (brief)¶
Adding a whole new charting library is the heaviest extension and, as noted, seldom needed today. In outline, a provider submodule supplies:
- a Library plugin extending
ChartBase(implementingChartInterface), annotated with the#[Chart]attribute that declares itsid,name, and thetypesit supports; - a
*.libraries.ymldeclaring the JavaScript (with CDN fallback); - a small JavaScript file that listens for the module's initialization event, reads the
data-chartconfig, and renders it; - an optional
*.installrequirements check and config schema.
The bundled providers (charts_chartjs and charts_c3 are the most compact) are the reference implementations. If you're considering integrating a library that isn't already covered, the maintainers ask that you reach out first (see the project page) so effort isn't duplicated.
8.8 Choosing the right mechanism¶
A quick decision guide:
- Change a property the module already understands? →
hook_chart_alter()(portable). - Flip on a library option the module doesn't expose, on top of what it generates? →
#raw_options(per element). - Adjust the finished library object for specific charts? →
hook_chart_definition_alter()(library-specific). - Render a whole chart or type the module can't model? →
#chart_definition(library-locked, unlimited). - Make a new type a permanent, reusable option? →
*.charts_types.yml+ supported-types alter (and possibly aTypesInfoEventsubscriber). - Integrate a library nothing supports yet? → a new provider submodule (rarely necessary; contact the maintainers).
Whenever a technique is library-specific, isolate it and branch on #chart_library so a future library switch fails loudly and locally rather than silently producing a broken chart.
9. Configuration & Data Model Reference¶
In brief — configuration lives in three places: the site-wide charts.settings object, per-chart config (embedded in Views/block config), and serialized data in chart_config entity fields. This section documents the config schema and its reusable data types, the field storage, how library selection becomes a calculated module dependency, and how config is kept consistent on import and protected on uninstall.
Charts stores information in three distinct places, and it helps to keep them separate in your mind:
- Site-wide defaults — one config object,
charts.settings. - Per-chart configuration — stored wherever the chart lives: in a View's config, in a block's config, or in a
chart_configfield on an entity. - Chart field data — the actual series/labels for a field-based chart, stored in the entity's field tables.
This section documents each, plus the schema that validates them, how library selection turns into module dependencies, and how the module protects configuration during import and uninstall.
9.1 The charts.settings config object¶
The single site-wide config object. Its top-level structure:
| Key | Type | Purpose |
|---|---|---|
charts_default_settings |
charts_config |
The default library, type, display, axis, and color settings that new charts inherit. |
advanced.debug |
boolean | Whether to render the debug JSON object beneath each chart. |
advanced.requirements.cdn |
boolean | Whether to load libraries from a CDN when not present locally (and whether to warn about missing libraries). |
dependencies |
config dependencies | Calculated module dependencies (see 9.5). |
Two aspects of charts_default_settings are worth calling out:
- It reuses the
charts_configschema type — the same structure used by Views chart settings and by the chart field. This is why the settings you see on the config page, in a View's Format settings, and on a chart field are consistent: they're all the same shape. - Per-library settings are stored alongside the defaults. When you configure library-specific options and then switch libraries, the module preserves each library's settings under a
library_configsstore (keyed by library ID) rather than discarding them, so switching back and forth doesn't lose your work.
9.2 Config schema and reusable data types¶
The module defines its schema across three files, plus one per provider:
charts.settings.schema.yml— the top-levelcharts.settingsobject described above.charts.views.schema.yml— schema for the Views integration: thechartstyle (views.style.chart, whosechart_settingsis acharts_config), thechart_extensiondisplay, and the specialized Views fields (field_exposed_chart_type,field_charts_fields_bubblewith x/y/z axis fields,field_charts_fields_scatterwith x/y, andfield_charts_numeric_array).charts.data_types.schema.yml— the reusable building blocks that the others compose from.charts_<provider>.schema.yml— each provider adds a small schema for its library-specific settings (for examplecharts_highcharts.schema.yml).
The reusable data types in charts.data_types.schema.yml are the vocabulary of a chart's configuration:
| Data type | Describes |
|---|---|
charts_config |
A complete chart configuration: library, type, fields (label, stacking, data providers, entity grouping), and the display/axis blocks below. |
charts_display |
Title/subtitle, legend, tooltips, data labels/markers, background, colors, 3D/polar, dimensions, gauge, color changer, and accessibility options (caption, summary, accessible-table mode). |
charts_dimensions |
Width/height with unit strings. |
charts_gauge |
Gauge min/max and the green/yellow/red from–to thresholds. |
charts_xaxis / charts_yaxis |
Axis title, min/max, prefix/suffix, decimal count, label rotation. |
charts_views_field_data_provider |
A single Views data-provider field: enabled, color, weight. |
Understanding these types is useful when you hand-edit exported YAML or write config programmatically — they tell you exactly which keys are valid and what type each expects.
9.3 The chart_config field (per-entity storage)¶
The Chart field (chart_config field type) is how a chart attaches to an individual entity. Its storage has three columns:
| Column | Type | Contents |
|---|---|---|
config |
serialized blob (big) |
The full chart configuration array — this is the field's main property and holds everything: library, type, display, series, and data. |
library |
varchar_ascii(255) |
The chosen library, denormalized out of the config for querying. |
type |
varchar_ascii(255) |
The chosen chart type, denormalized for querying. |
The field exposes three typed-data properties — config (the main property, a chart_config data type), library, and type. When a value is set, the field automatically copies library and type out of the config array into their own columns, so you can query or filter entities by chart library/type without unserializing the blob. The chart_config data type itself is a lightweight typed-data wrapper (ChartConfigData) representing the configuration object.
This is the key structural difference from Views/block charts: a chart field stores both the configuration and the data together, serialized, on the entity. Views charts, by contrast, store only configuration and pull their data dynamically from the query at render time.
9.4 Config translation¶
charts.settings is translatable. The charts.config_translation.yml file registers the settings object with the Configuration Translation module (mapped to the charts.settings route), so multilingual sites can translate the human-facing default strings (titles, labels, and other label/text-typed values in the schema). Translatable values are marked as label or text types in the schema; purely structural values (library IDs, booleans, colors) are not translated.
9.5 How library selection becomes a dependency¶
When you save chart settings, the module calculates module dependencies from your chosen library and type and writes them into charts.settings's dependencies key (via the shared dependency-calculator logic). In plain terms: choosing Highcharts as your default records a dependency on charts_highcharts.
This calculated dependency is the connective tissue behind the next two subsections — it's what lets the module recalculate correctly on config import and refuse an unsafe uninstall. It's also why Views that use a chart style are re-saved during certain upgrades: so their calculated library dependencies are recorded correctly.
9.6 Config import safety¶
When configuration is imported (for example during a deployment with drush config:import), the module ensures the dependencies on charts.settings are correct for the incoming library/type rather than trusting whatever was in the imported file. The ConfigImportSubscriber hooks two events:
STORAGE_TRANSFORM_IMPORT— as the import storage is transformed, it recomputesdependenciesfrom the incomingcharts_default_settingsand rewritescharts.settings.IMPORT(priority 50, before core's importer at 40) — it reads the sourcecharts.settings, recalculates dependencies from its library/type, and writes them to the target storage.
The practical effect: you don't have to hand-maintain the dependencies key in exported config — the module keeps it consistent so imports don't fail or drift because of a stale or hand-edited dependency list.
9.7 Uninstall protection¶
The module prevents you from uninstalling a provider (or chart-type) module that is currently configured as the default. PluginsUninstallValidator checks the calculated dependencies.module on charts.settings; if the module you're trying to uninstall is listed there, it blocks the uninstall with a message directing you to update the chart configuration first (with a link to the settings page that returns you to the uninstall screen).
This guards against the failure mode where removing, say, charts_highcharts while it's the site default would leave every chart pointing at a library plugin that no longer exists. To uninstall such a provider, first change the default library at /admin/config/content/charts (or reset the settings), then uninstall.
9.8 Updates and upgrades¶
Two mechanisms keep configuration current across module versions:
Post-update hooks (charts.post_update.php, and per-provider files such as charts_highcharts.post_update.php) run automatically via drush updatedb / update.php. In the 5.2.x line these handle, among others: initializing the CDN and debug advanced settings, expanding the default color palette from 10 to 25 colors, migrating library defaults into the per-library library_config store, moving the color changer from the fields section to the display section, recalculating view/library dependencies, and modernizing older charts to the current element/settings structure.
The ConfigUpdater service carries the heavier migration logic, most notably transforming version-3-era settings into the current architecture — for both the main config and existing Views that use the chart style (transformVersion3SettingsToNew(), updateExistingViewsVersion3ToNewSettings()), including normalizing legacy boolean-string values and remapping old data-provider structures. If you're upgrading a site that has been on Charts since a 3.x release, these are what reshape your existing configuration; run database updates after updating the module code so they execute.
Deployment tip: because dependencies are recalculated on import (9.6) and legacy structures are migrated by update hooks, the safe upgrade order is: update the code, run
drush updatedb, then export the resulting config. Exporting before running updates can capture a half-migrated state.
10. Theming & Front-End Integration¶
In brief — the module emits a data-chart JSON attribute that Drupal.Charts.Contents parses and hands to the charting library after firing a drupalChartsConfigsInitialization event. Theme charts via the charts-chart.html.twig template and CSS classes (charts-figure, chart-visual); customize in JavaScript by listening for that event — the only way to pass library-native callbacks such as tooltip formatter functions.
Charts render as HTML plus a JavaScript hand-off. Understanding that seam is what lets you restyle a chart, target it with CSS, or inject behavior the PHP API can't express (like a JavaScript tooltip formatter).
10.1 The template¶
The module ships a single, deliberately minimal template, charts-chart.html.twig, registered as the charts_chart theme hook. It outputs four regions:
{{ content_prefix }}
{{ content }}
{{ debug }}
{{ content_suffix }}
Most of the markup is assembled in template_preprocess_charts_chart() rather than the template itself, so overriding the Twig file is only necessary for structural changes. The preprocess builds an accessible structure around every chart:
- A figure wrapper (
role="figure", anaria-labelfrom the chart title,tabindex="0", classcharts-figure). - The visual chart container — the
<div>the library draws into — markedaria-hidden="true"with classeschartandchart-visual, because the graphic itself isn't meaningful to screen readers. - An optional visually-hidden summary (
#chart_summary), referenced viaaria-describedby, giving assistive tech a text description. - An optional caption (
#figure_caption), run throughXss::filterAdmin(), and the accessible data table (see Section 6). - An outer wrapper with class
charts-wrapper.
Caption and summary support token replacement — if the chart is attached to an entity (#entity), tokens resolve against it, so a chart field can pull a caption from another field on the same node.
10.2 CSS hooks¶
For styling and JavaScript targeting, the reliable class names are: charts-wrapper (outer), charts-figure (the figure), chart / chart-visual (the visual container), and provider-specific render classes such as charts-highchart. The accessible-table disclosure uses charts-accordion-trigger. Target these rather than generated IDs, which are unique per chart.
10.3 The data-chart attribute contract¶
The bridge between PHP and the library is a single HTML attribute. The module JSON-encodes the finished chart definition into a data-chart attribute on the chart element. On the client, Drupal.Charts.Contents scans for every [data-chart] element, parses the JSON, and registers it in Drupal.Charts.Configs, keyed by the element's id. It also stashes a reference to the DOM element on the config so providers can find it again.
This is the whole contract: produce an element with a data-chart attribute and a unique id, and the front end takes over. It's also why #chart_definition (see the Extending section) works — it sets the contents of that attribute directly.
10.4 The JavaScript API¶
Drupal.Charts.Contents exposes a small API:
new Drupal.Charts.Contents()— constructs the registry from all[data-chart]elements on the page.getData(id)— returns the config for a chart and, importantly, fires its initialization event first (see below).Drupal.Charts.Contents.initialize(id)— dispatches adrupalChartsConfigsInitializationcustom event on the chart's DOM element, with the config asevent.detail.Drupal.Charts.Contents.update(id, data)— replaces the stored config for a chart, so mutations made during initialization are picked up before rendering.
A provider's own JavaScript is the consumer: it constructs a Contents instance, calls getData(id) for each chart, and renders. Here's the shape (Highcharts):
const contents = new Drupal.Charts.Contents();
once('charts-highchart', '.charts-highchart', context).forEach((element) => {
const config = contents.getData(element.id); // also fires the init event
config.chart.renderTo = element.id;
Drupal.highchartsCharts.instances[element.id] = new Highcharts.Chart(config);
});
Providers also clean up on detach (destroying the chart instance on unload), and read optional global library settings from drupalSettings (e.g. drupalSettings.charts.highcharts.global_options passed to Highcharts.setOptions()).
10.5 Overriding a chart in JavaScript¶
The initialization event is the supported extension point for front-end tweaks — especially things that can't cross the PHP→JSON boundary, like JavaScript function callbacks. Attach a behavior, listen for drupalChartsConfigsInitialization on the chart element, mutate event.detail, and call update():
Drupal.behaviors.myChartTweaks = {
attach() {
const el = document.getElementById('my-chart-id');
el.addEventListener('drupalChartsConfigsInitialization', (e) => {
const data = e.detail;
data.chart.backgroundColor = 'green';
// A JS formatter — impossible to express in the PHP render array.
data.tooltip.formatter = function () {
return `The value for <b>${this.x}</b> is <b>${this.y}</b>`;
};
Drupal.Charts.Contents.update(data.drupalChartDivId, data);
});
},
};
This *_js_override.js pattern (there are examples in the charts_api_example and charts_highcharts_api_example submodules) is the right tool when you need library-native JavaScript behavior on specific charts. Note that the config shape is the library's shape, so an override is provider-specific.
10.6 The debug output¶
When the debug object is enabled, each chart is followed by an element carrying data-charts-debug-container. The provider JS writes JSON.stringify(config, null, ' ') into its <code> element, so the rendered JSON reflects exactly what the library received — including any JavaScript-side overrides applied during initialization. This is the front-end counterpart to the PHP-side definition and the fastest way to confirm what the library is actually being handed.
10.7 CDN rewriting¶
One front-end-adjacent behavior worth knowing: hook_library_info_alter() (in ChartsHooks::libraryInfoAlter()) rewrites provider library definitions to their CDN URLs when CDN mode is enabled and no local copy is present. This is the mechanism behind the local/CDN fallback described in Section 3 — it operates on any library whose machine name starts with charts_.
11. Permissions & Security¶
In brief — the configuration forms require the core administer site configuration permission; the module's own access all charts permission is currently a placeholder. Chart config is JSON-encoded and captions are filtered, but #raw_options, #chart_definition, HTML tooltips, and editor-supplied CSV/JSON are trust boundaries — never build them from untrusted input. Prefer locally hosted libraries over a CDN for privacy and integrity.
11.1 Permissions¶
- Administration — the configuration routes (
/admin/config/content/chartsand its Advanced tab) require the coreadminister site configurationpermission. Only trusted administrators should hold it, since chart defaults and the CDN/debug toggles live there. access all charts— the module defines this permission (markedrestrict access: true). Note that in the current release its description states it "needs to be fleshed out" — treat it as a placeholder that may gain a defined role in a later version rather than a fully wired-up access gate. Charts themselves are generally visible wherever the entity, View, or block that contains them is visible, governed by those components' own access controls.
Chart blocks and fields inherit the standard Drupal access model: block visibility/placement permissions and entity/field view access respectively.
11.2 How chart data is sanitized¶
Because a chart's configuration is serialized into an HTML attribute and then handed to a JavaScript library, the module applies output handling at the seams:
- The chart definition is written with
Json::encode()into thedata-chartattribute (proper JSON/HTML-attribute encoding). - The visual container is
aria-hidden, and the graphic is drawn by the library into a container the module controls. - Captions are filtered with
Xss::filterAdmin(); screen-reader summaries are reduced to plain text withstrip_tags(). - IDs are generated with Drupal's HTML utilities to be unique and safe.
11.3 Security considerations for developers¶
The escape hatches that make the module powerful also bypass its normal handling, so treat them carefully:
#raw_optionsand#chart_definitionare merged/emitted into the definition largely as-is. Never build them from untrusted input without sanitizing — a malicious value can reach the charting library and, depending on the library, produce script execution.tooltips_use_html/ HTML in tooltips lets markup through to the library's renderer; enable it only with trusted content.- JavaScript overrides (§10.5) run arbitrary code you write; keep formatter/callback logic free of unsanitized data.
- Fields and blocks that accept CSV/JSON from editors should be restricted to trusted roles, since that data becomes chart configuration.
11.4 Third-party assets (CDN)¶
Loading libraries from a CDN means a third-party request on every charted page. Two implications:
- Privacy / availability: the CDN sees your visitors' requests, and your charts depend on its uptime. Google Charts is a special case — even a "local" install only hosts Google's loader; the chart code is always fetched from Google, so it can't be fully self-hosted.
- Integrity: the bundled CDN definitions don't pin subresource-integrity hashes, so for high-assurance sites prefer local installation (Section 3), which serves audited files from your own domain and sidesteps the Ajax/BigPipe caveats of CDN loading.
12. Testing & Quality¶
In brief — the module ships Unit, Kernel, Functional, and Functional-JavaScript PHPUnit suites (declared in phpunit.xml.dist); the JavaScript suite requires Chromedriver. CI (.gitlab-ci.yml) runs PHPUnit plus PHP_CodeSniffer, PHPStan, ESLint, Stylelint, and cspell via Drupal's shared GitLab CI template.
12.1 Test suites¶
The module is covered by the four standard Drupal PHPUnit suites, declared in phpunit.xml.dist:
| Suite | What it covers here |
|---|---|
| Unit | Isolated logic — the data-collector table, the config form, theme/preprocess/views-data hooks, and each provider's plugin (HighchartsTest, GoogleTest, ChartjsTest, C3Test, BillboardTest). |
| Kernel | Integration with a booted kernel — chart element rendering, the chart_config field, library config, chart-type support, dimensions, raw-options handling (Highcharts/Chart.js), the Canvas block, and the example builder. |
| Functional | Browser-level behavior — e.g. the Views chart style (StyleChartsTest). |
| Functional JavaScript | Real-browser interaction via WebDriver/Chromedriver — the AJAX data-collector table and config overrides. |
The JavaScript suite needs a Chromedriver endpoint; phpunit.xml.dist configures headless Chrome through MINK_DRIVER_ARGS_WEBDRIVER.
12.2 Running the tests¶
Run a suite against the module with PHPUnit from your Drupal root, for example:
# All unit tests for the module
vendor/bin/phpunit --group charts --testsuite unit
# A single test
vendor/bin/phpunit web/modules/contrib/charts/tests/src/Kernel/ChartElementTest.php
Functional and Functional-JavaScript suites require a working test database and (for JS) a running Chromedriver; consult core's testing documentation for environment setup. The suite directories in phpunit.xml.dist are written for the CI layout (web/modules/custom/*), so adjust paths to match where your checkout lives.
12.3 Static analysis and coding standards¶
Quality gates run in CI via .gitlab-ci.yml, which pulls in Drupal's shared GitLab CI template. That template provides the standard jobs — PHPUnit (with a coverage variant), plus PHP_CodeSniffer (Drupal coding standards), PHPStan (static analysis), ESLint, Stylelint, and spell-checking. The project keeps a custom dictionary at .cspell-project-words.txt for domain terms (library names, API words) so the spell-check job doesn't flag them.
12.4 Contributing tests¶
The maintainers explicitly welcome help with tests. When adding a provider or a feature, mirror the existing structure: a Unit test for the plugin/logic, a Kernel test for rendering/integration, and — where behavior is interactive — a Functional-JavaScript test. Keeping new code within the existing suites means it runs automatically in CI.
13. Troubleshooting & FAQ¶
In brief — most first-chart problems are one of: the library isn't loaded (check the Status report — enable CDN or install locally), the chart type isn't supported by the selected provider, a Views chart needs aggregation, or a library-specific escape hatch broke after a provider switch. Enabling the debug object to inspect the generated JSON resolves the majority of cases.
The chart area is blank.
Open the browser console. A "library is not defined" (or similar) error means the JavaScript library isn't loading. Check the Status report (/admin/reports/status): if it says the library is "Not Installed" and CDN is off, either install it locally (Section 3) or re-enable CDN on the Advanced settings tab. If the console is clean but nothing draws, turn on the debug object and inspect the JSON — a malformed definition points at the render array or a bad #raw_options/#chart_definition.
A chart type I want isn't in the dropdown. The selected library doesn't support it. Type availability is per-provider (Section 7) — for example Chart.js has no gauge, and only Highcharts has heatmap. Switch to a library that supports the type, or check the support matrix before designing the chart.
My Views chart is empty or wrong.
Usually a data/label mix-up or missing aggregation. Confirm the label field and the data field(s) are assigned correctly in the chart Settings, and if you're charting a count rather than an existing numeric field, enable aggregation and apply the count function. Building the View as a Table first (Section 5) makes these mistakes obvious.
I switched libraries and a chart broke.
You were likely relying on library-specific behavior — #raw_options, a hook_chart_definition_alter() implementation, a #chart_definition, or a JS override — all of which are provider-specific. Stay within the portable core of chart types and avoid library-native escape hatches if cross-library portability matters, or branch on #chart_library so a switch fails loudly.
Charts don't render inside Layout Builder / Drupal Canvas.
Use the Chart (Canvas) block (charts_canvas_block) rather than the standard block — it avoids the AJAX data-collector table that doesn't play well in the Canvas component form, taking CSV/JSON in a textarea instead (Section 5).
Charts work in dev but break with Ajax or BigPipe. This is the known CDN caveat. Install the library locally instead of relying on the CDN.
The Status report warns the library is missing, but I installed it.
Detection looks for a specific sentinel file under /libraries/<name> in one of three searched locations. Confirm the path and filename match what the provider expects (Section 3.4.0).
Highcharts shows a security error about exporting-server.
Delete that directory from the Highcharts library folder — it contains unsafe sample files, and the module refuses to run until it's gone.
Can I use a chart type or option the module doesn't expose?
Yes — via #raw_options (to add native options) or #chart_definition (to supply a whole library-native chart). See the Extending section.
How do I see the exact configuration sent to the library? Enable Enable Charts Debug on the Advanced settings tab. The JSON appears beneath each chart and can be pasted into the library's own online playground.
Where do I report a bug or ask for help? The Drupal.org issue queue: https://www.drupal.org/project/issues/charts. Each provider's README also has current, library-specific installation commands.
14. Upgrade & Migration Guide¶
In brief — the safe upgrade order is: update code → drush updatedb (runs post-update hooks and the ConfigUpdater migration of v3-era settings) → export config. Several constructor signatures deprecated in charts 5.1.x become required in 6.0.0, and providers use the #[Chart] attribute (the legacy annotation still works).
14.1 Deprecations and backward-compatibility notes¶
The 5.x line carries several deprecations that become required in 6.0.0, so address them before that release:
- Constructor arguments.
ChartBase::__construct()now expects the module handler (deprecated without it since 5.1.6) and the form builder (since 5.1.11).ChartsConfigForm::__construct()similarly expects its injected services (since 5.1.6). Custom subclasses/forms that call these without the new arguments emit deprecation notices today and will break in 6.0.0 — inject the services viacreate(). - Plugin definition style. Providers are defined with the PHP
#[Chart]attribute (Drupal\charts\Attribute\Chart). The legacy annotation (Drupal\charts\Annotation\Chart) is still recognized for backward compatibility, but new providers should use the attribute. - Procedural hooks. Module hooks have moved to OOP
#[Hook]classes (e.g.ChartsHooks), with the procedural wrappers incharts.modulemarked#[LegacyHook]. If you copied procedural implementations, prefer the class-based approach going forward.
14.2 Settings migration¶
Two mechanisms bring older configuration up to date automatically when you run database updates:
- Post-update hooks (
charts.post_update.php, and provider files likecharts_highcharts.post_update.php) handle incremental config changes: initializing the CDN and debug settings, expanding the default palette from 10 to 25 colors, migrating library defaults into the per-librarylibrary_configstore, moving the color changer from fields to display, recalculating view/library dependencies, and modernizing charts to the current element structure. - The
ConfigUpdaterservice performs the larger structural migration from version-3-era settings to the current architecture — for both the main config and existing Views that use the chart style — normalizing legacy boolean-string values and remapping old data-provider structures along the way.
14.3 Recommended upgrade procedure¶
- Update the module code (
composer update drupal/charts). - Run database updates (
drush updatedbor/update.php) so the post-update hooks andConfigUpdatermigrations execute. - If you host libraries locally, check whether the provider bumped its library version (see the provider README /
*.libraries.yml) and update the local files or your Composer/npm pins accordingly. - Export configuration (
drush config:export) after updates run — never before, or you'll capture a half-migrated state. Dependencies are recalculated on import, so you don't hand-maintain thedependencieskey. - Clear caches and verify with the example gallery (
/charts/example/display) and the Status report.
14.4 If you're coming from a very old version¶
Sites that have been on Charts since a 3.x release rely on the ConfigUpdater transformations in step 2 to reshape both configuration and chart-style Views. Run updates on a copy first, then spot-check a few existing charts (library, type, colors, and data all intact) before deploying. Because the module supports Drupal core 10.3+/11/12, also confirm your core version is in range before upgrading the module.
15. Tutorials & Case Studies¶
In brief — a cookbook of self-contained, worked tutorials and case studies, each with a fixed structure (Goal / When to use / Prerequisites / Steps / Result / Related), covering the common Views, field, block, ecosystem-module, API, and extension scenarios. It complements and maps to the canonical HowTo library on Drupal.org.
15.0 How to use this section¶
Every tutorial below follows the same fixed structure so it can be read in isolation:
- Goal — one sentence stating what you will produce.
- When to use — the condition under which this is the right approach.
- Prerequisites — modules/state assumed before step 1.
- Steps — numbered, imperative, with exact identifiers (routes, machine names, property keys).
- Result — what you should observe.
- Related — cross-references to manual sections and canonical HowTos.
These tutorials are a curated subset. The Charts project maintains an authoritative, continually updated set of HowTo guides on Drupal.org that covers additional scenarios (external data via Views JSON Source, drilldown, attachments-as-tabs, word clouds, GeoCharts, and more). The canonical index is https://www.drupal.org/docs/8/modules/charts/charts-howtos-0; specific guides are linked in the HowTo map at the end of this section. When a full step-by-step already exists on Drupal.org, the tutorial here summarizes it and links out rather than duplicating it.
15.1 A Views chart of content over time¶
Goal. Produce a column chart of a count of content per time period (e.g. articles published per month).
When to use. The data is already stored as Drupal content, and you want an aggregate (a count or sum) rather than a value stored on each item. Contrast with plotting an existing numeric field, which needs no aggregation (see §5.2.1).
Prerequisites. charts + one provider enabled and set as the default library.
Steps.
1. Create a View at /admin/structure/views/add of your content type; set the display format to Chart.
2. Under Advanced → Other → Use aggregation, set Aggregate to Yes.
3. Add the time field (e.g. Authored on, formatted to month granularity) as the label field.
4. Add a field to count (e.g. the content ID); set its Aggregation type to Count.
5. In Format → Settings, set library, chart type = column, the label field, and the counted field as the data provider.
6. Save.
Result. One column per month, height = number of items authored that month.
15.2 Multiple series by grouping (a line per category)¶
Goal. Plot one measure over a shared axis with a separate series per category value — e.g. a rate over time with one line per geographic region.
When to use. Your series come from one data column split by a categorical field, all sharing one chart type and one axis. If instead the extra series needs a different chart type or axis, use a Chart attachment (§15.3); if the series are separate numeric columns, add multiple data fields.
Prerequisites. charts + a provider. Content with a time/label field, a numeric measure, and a category field (e.g. a region taxonomy reference).
Steps.
1. Create a Chart-format View. Set the time field as the label.
2. Add the numeric measure as the single data provider field.
3. In Format → Settings, set the Grouping field to the category field (e.g. region).
4. Choose chart type = line. Leave stacking off (stacking is independent of grouping — see §5.2.2).
5. (Optional) If grouping by a taxonomy/entity-reference field, configure the entity grouping color settings to color each line from the referenced entity.
6. Save.
Result. One line per distinct region, all plotted against the shared time axis.
Related. §5.2.2. Canonical HowTo: Using entity grouping settings to create multiple series from one field in Views (see HowTo map).
15.3 A combo chart in Views¶
Goal. Draw two series of different chart types in one chart (e.g. columns with a trend line over them), optionally on a secondary y-axis.
When to use. A series must have its own chart type or axis. This is the defining reason to use a Chart attachment; grouping and multiple data fields cannot mix chart types.
Prerequisites. A working Views chart (the parent) from §15.1 or similar.
Steps.
1. In the View, add a display of type Chart attachment (chart_extension).
2. In the attachment, set Attach to = the parent chart display (required).
3. Configure inheritance of exposed/contextual filters if both series should respond to the same filtering.
4. To place the second series on its own scale, set the attachment to use a secondary y-axis.
5. In the attachment's Format → Settings, choose its own chart type (e.g. line).
6. Add the attachment's data field(s). Save.
Result. A single rendered chart combining the parent's columns and the attachment's line.
Related. §5.3.
15.4 Small multiples with Views Field View¶
Goal. Render a grid of small charts — one miniature chart per category (a "small multiple" / trellis), e.g. one small line chart per region shown side by side.
When to use. You want to compare many categories at a glance by giving each its own chart, rather than overlaying all series in one chart (grouping) or combining types (attachment).
Prerequisites. charts + a provider, plus the contributed Views Field View module (views_field_view), which lets one View embed another View inside a field.
Steps.
1. Build the child chart View. Create a Chart-format View (a block display works well) that charts the measure for a single category. Add a contextual filter (argument) on the category field (e.g. region), so the View renders the chart for whichever category is passed in.
2. Build the parent list View. Create a second View that returns one row per category (e.g. one row per region term).
3. Embed the child in the parent. In the parent View, add a Global: View field (provided by Views Field View). Configure it to render the child View and pass the current row's category value as the child's contextual filter argument.
4. Arrange/style the parent output as a grid (CSS or an unformatted/grid row style).
5. Save both Views.
Result. The parent renders one small chart per category — a small-multiples layout driven entirely by configuration.
Related. Full canonical walkthrough: Creating "small multiple" charts with Charts and Views Field View.
15.5 Let visitors switch the chart type¶
Goal. Add a control that lets site visitors change the rendered chart type (e.g. toggle line ↔ column) on a Views chart.
When to use. A dashboard where different viewers prefer different representations of the same data.
Prerequisites. A working Views chart.
Steps.
1. In the View's Fields, add the Exposed chart type field (field_exposed_chart_type).
2. Configure which chart types to offer and the selection widget.
3. Save. The exposed control overrides the configured chart type at render time using the visitor's selection.
Result. A front-end selector; changing it re-renders the chart as the chosen type.
Related. §5.4.
15.6 A per-entity chart with the Chart field (CSV import)¶
Goal. Attach a static chart to a single entity (e.g. one node), entering its data by importing a CSV.
When to use. The chart belongs to one entity and its data does not need to be queried dynamically.
Prerequisites. charts + a provider.
Steps.
1. On the target bundle, Manage fields → Add field → Chart (chart_config field type).
2. In the field's widget settings, decide whether editors may change the charting library ("Allow users to change the default charting library").
3. When editing an entity, in the Chart field use the data-collector table: click Import Data from CSV, choose the separator, upload a .csv, and Upload CSV. (Import overwrites existing table data.)
4. Set library, chart type, and display options in the same field.
5. Save the entity.
Result. The chart renders wherever the entity is displayed, using the imported data.
15.7 A chart in Layout Builder or a block¶
Goal. Place a reusable static chart via Block layout or Layout Builder.
When to use. The chart is not tied to one entity and its data is entered by hand.
Prerequisites. The charts_blocks submodule enabled.
Steps.
1. Enable charts_blocks.
2. Place a Charts block (charts_block) via Block layout or add it in Layout Builder. Enter data in its data-collector table (manual or CSV).
3. In Drupal Canvas or where the AJAX table is awkward, use the Chart (Canvas) block (charts_canvas_block) instead: choose data format CSV or JSON and paste data into the textarea (CSV: header row with the label in the first cell, series names after; JSON: { "categories": [...], "series": [ { "name", "data", "color?", "target_axis?" } ] }).
4. Configure library/type/display; save.
Result. A placed, reusable chart block.
Related. §5.7.
15.8 Embed a chart inside body content (Charts Text Filter)¶
Goal. Let content editors insert a chart inline within a rich-text/CKEditor 5 field via a toolbar button.
When to use. A chart needs to sit within an article's body copy rather than in a View, block, or dedicated field.
Prerequisites. The contributed Charts Text Filter (charts_text_filter, 2.x) module, which requires Charts and CKEditor 5 (in core). Charts default settings must be saved.
Steps.
1. Enable charts and charts_text_filter at /admin/config/content/extend.
2. Confirm Charts defaults at /admin/config/content/charts.
3. Edit the target text format at /admin/config/content/formats:
- Drag the Chart icon into the CKEditor 5 Active toolbar.
- Enable the Charts text filter (filter_charts_text_filter) checkbox.
- Filter order matters: place the Charts text filter after any filters that correct/restrict HTML, or the placeholder may be stripped.
4. In a body field using that format, click the Chart toolbar button, configure the chart in the dialog, and insert it.
5. Save the content.
Result. CKEditor inserts a <chart data-chart-config="{…JSON…}"> placeholder; on render, the filter decodes the JSON and builds the chart with Chart::buildElement(), so it displays inline through the normal Charts pipeline (library plugins, alter hooks, accessible table).
Note. Older versions required hand-editing WYSIWYG source; that approach is now discouraged in favor of the toolbar button.
Related. §5.10 (ecosystem creation methods), §9.3 (Chart::buildElement). Project: charts_text_filter.
15.9 Render a chart in a Twig template (Charts Twig)¶
Goal. Output a chart directly from a Twig template using the Charts API.
When to use. A themer needs a chart in a specific template region without building a View or block.
Prerequisites. The contributed Charts Twig (charts_twig, 2.x) module (requires Charts ≥5). A provider must be enabled and Charts default settings saved.
Steps.
1. Enable charts_twig.
2. In a template, build an options array and pass it to the chart() function. The array keys map to chart render-element properties: id, chart_type, title, series (each with title, data, and optionally color, chart_type, target_axis), axes (each keyed entry needs type: 'chart_xaxis' or 'chart_yaxis', plus title, labels, opposite), and raw_options.
{% set my_chart = {
id: 'my_twig_chart',
chart_type: 'column',
title: 'The Chart Title'|t,
series: [
{ title: 'First series'|t, data: [10, 20, 30], color: 'purple' },
{ title: 'Second series'|t, data: [8, 14, 22] }
],
axes: {
xaxis: { type: 'chart_xaxis', title: 'X-Axis Label'|t, labels: ['a', 'b', 'c'] }
}
} %}
{{ chart(my_chart) }}
chart_type and target_axis, and define the matching yaxis entry with opposite: true.
4. Use the |t filter on user-facing strings for translation. Clear caches and view the template.
Result. A chart rendered from template code, using the same providers and pipeline as UI-built charts.
Caveat. When placing Twig charts inside a View, the required charting-library JavaScript may be stripped; the chart won't display unless that JS is already present on the page. A legacy positional syntax — chart(id, chart_type, title, series, xaxis, yaxis, options) — is also still supported for backward compatibility.
Related. §5.10 (ecosystem creation methods), §6.2 (element properties the array keys map to). Project: charts_twig.
15.10 A first chart in code (the API)¶
Goal. Render a chart from a controller or block by returning a render array.
When to use. You are a developer building charts programmatically.
Prerequisites. charts + a provider.
Steps.
1. Build a chart render element with a chart_data series and an axis:
$build['chart'] = [
'#type' => 'chart',
'#chart_type' => 'column',
'#title' => $this->t('Responses'),
'series' => [
'#type' => 'chart_data',
'#title' => $this->t('Responses'),
'#data' => [60, 40],
],
'xaxis' => [
'#type' => 'chart_xaxis',
'#labels' => [$this->t('Yes'), $this->t('No')],
],
];
$build from a controller/block build(); Drupal renders it. Omit #chart_library to use the site default.
Result. A rendered column chart.
Related. §6 (The Charts API), and the live gallery at /charts/example/display (charts_api_example).
15.11 Case study: escape the chart-type limits with #chart_definition¶
Goal. Render a library-native chart type the module does not model (e.g. a Highcharts streamgraph).
When to use. No lighter mechanism suffices because you need a whole chart type outside the module's type registry. Accept that the result is bound to one library.
Prerequisites. The target provider enabled with its library installed.
Steps.
1. Set #chart_library so the correct provider JS loads.
2. Omit #chart_type so the module's type-support gate (a LogicException on unsupported types) does not fire.
3. Supply a complete, library-native configuration in #chart_definition, including the native type:
$build['chart'] = [
'#type' => 'chart',
'#chart_library' => 'highcharts',
'#chart_definition' => [
'chart' => ['type' => 'streamgraph'],
'title' => ['text' => $this->t('Traffic by channel')],
'xAxis' => ['categories' => ['Jan', 'Feb', 'Mar']],
'series' => [
['name' => 'Organic', 'data' => [3, 4, 3]],
['name' => 'Paid', 'data' => [1, 2, 5]],
],
],
];
Result. The provider renders the native type; the module JSON-encodes your definition into data-chart and hands it to the library unchanged. hook_chart_definition_alter() still runs.
Related. Extending the Module (the #chart_definition escape hatch). Canonical HowTo: Extend the Charts module to make a word cloud.
15.12 Case study: add a first-class chart type¶
Goal. Register a new chart type so it appears in the UI, passes the type gate, and works without #chart_definition.
When to use. A type is worth making reusable across charts rather than hand-defining per chart.
Steps.
1. Add <mymodule>.charts_types.yml declaring the type (label, axis = xy or y_only, axis_inverted, stacking, coordinate). The TypeManager discovers it automatically.
2. Advertise support on the providers that can draw it:
function mymodule_charts_plugin_supported_chart_types_alter(array &$types, string $chart_plugin_id) {
if ($chart_plugin_id === 'highcharts') {
$types[] = 'candlestick';
}
}
#raw_options to shape the type's library-native specifics.
Result. The new type is selectable and renders through the normal pipeline.
Related. Extending the Module (chart type plugins).
15.13 Case study: theme and alter one chart¶
Goal. Change a specific chart's markup and definition together — e.g. add a caption from an entity field and tweak a library option.
Steps.
1. Use hook_chart_alter() / hook_chart_CHART_ID_alter() for render-array-level, library-agnostic changes (title size, adding #chart_id, etc.).
2. Use hook_chart_definition_alter() (branch on $element['#chart_library']) for library-specific definition changes.
3. For markup/accessibility structure, override the charts-chart.html.twig template or the CSS classes (charts-figure, chart-visual) — see §10.
4. For JavaScript-only behavior (e.g. a tooltip formatter function), attach a drupalChartsConfigsInitialization listener and call Drupal.Charts.Contents.update() — see §10.5.
Result. A single chart customized across the render, definition, template, and JS layers as needed.
Related. §10 (Theming), Extending the Module (hooks). Canonical HowTos: Customizing a chart made via Views using the Charts API; Alter Charts Configuration using JavaScript.
15.14 Case study: switch providers without rebuilding¶
Goal. Move a set of charts from one library to another with no per-chart rework.
When to use. Licensing, features, or aesthetics motivate a library change.
Steps.
1. Confirm your charts use only types supported by the target provider (see the Provider Reference support matrix); staying within the portable core of types guarantees compatibility.
2. Install the target library (Section 3) and enable its submodule.
3. Change the default library at /admin/config/content/charts (and update any per-chart library overrides).
4. Audit any library-specific code — #raw_options, #chart_definition, hook_chart_definition_alter(), JS overrides — which do not carry across libraries.
5. Clear caches; verify with the example gallery and spot-check charts.
Result. The same charts render through the new provider; only library-specific escapes need attention.
Related. §7 (Provider Reference), §9.5.
15.15 Canonical HowTo map¶
The Charts project's HowTo guides on Drupal.org are the authoritative, continually updated recipes. Index: https://www.drupal.org/docs/8/modules/charts/charts-howtos-0. Key guides and the manual sections they extend:
| HowTo (Drupal.org) | Extends |
|---|---|
| Creating "small multiple" charts with Charts and Views Field View | §15.4 |
| Using entity grouping settings to create multiple series from one field in Views | §15.2 |
| Creating Scatter & Bubble Charts using Views | §5.5 |
| Creating charts from external data using Charts and Views JSON Source | §5.2 |
| Customizing a chart made via Views using the Charts API | §15.13 |
| Alter Charts Configuration using JavaScript | §10.5 |
| How to use the Charts Debug feature | §10.6 |
| How to use the Charts Highcharts Color Changer | §5.9 |
| How to use the Charts Highcharts Drilldown module with Charts | Provider Reference (ecosystem) |
| Using Charts with Views Attachments as Tabs | §15.3 |
| Extend the Charts module to make a word cloud | §15.11 |
Contributing a tutorial. The maintainers welcome additional real-world recipes. New use cases are a good fit either as a HowTo on Drupal.org or as an entry in this section; follow the fixed tutorial structure in §15.0 so entries stay consistent and individually retrievable.
16. Reference Appendices¶
In brief — consolidated lookup tables: the full chart-type catalog, every render-element property with defaults, the hook and event index, a service/class index, routes and permissions, the architecture and data-flow map, a glossary, and external links.
16.1 Chart type catalog¶
Every chart type in the registry, with its metadata. Base types come from charts.charts_types.yml; the last four are added by provider submodules via their own *.charts_types.yml. Metadata keys: axis (xy = dual-axis, y_only = single-axis), inverted (axes swapped), stacking (supports stacked series), coordinate (data points are [x,y]/[x,y,z] rather than scalars).
| Type ID | Label | axis | inverted | stacking | coordinate | Added by |
|---|---|---|---|---|---|---|
area |
Area | xy | no | yes | no | core |
arearange |
Area Range | xy | no | yes | no | core |
bar |
Bar | xy | yes | yes | no | core |
boxplot |
Boxplot | xy | no | no | no | core |
bubble |
Bubble | xy | no | no | yes | core |
candlestick |
Candlestick | xy | no | no | no | core |
column |
Column | xy | no | yes | no | core |
donut |
Donut | y_only | no | no | no | core |
gauge |
Gauge | y_only | no | no | no | core |
heatmap |
Heatmap | xy | no | no | yes | core |
line |
Line | xy | no | no | no | core |
pie |
Pie | y_only | no | no | no | core |
scatter |
Scatter | xy | no | no | yes | core |
spline |
Spline | xy | no | no | no | core |
solidgauge |
Solid Gauge | y_only | no | no | no | charts_highcharts |
dumbbell |
Dumbbell | xy | yes | no | no | charts_highcharts |
table |
Table | xy | no | no | no | charts_google |
polarArea |
Polar Area | y_only | no | no | no | charts_chartjs |
Radar is not a type — it is any cartesian type rendered with the #polar setting enabled (see §7.1). For which provider supports which type, see the Provider Reference support matrix.
16.2 Element property reference¶
The five render elements and their properties, with defaults. Source: the elements' getInfo() in src/Element/.
chart (container)¶
| Property | Default | Property | Default |
|---|---|---|---|
#chart_type |
NULL | #tooltips |
TRUE |
#chart_library |
NULL | #tooltips_use_html |
FALSE |
#chart_id |
NULL | #data_labels |
FALSE |
#title |
NULL | #data_markers |
FALSE |
#title_color |
#000 |
#connect_nulls |
FALSE |
#title_font_weight |
normal | #legend |
TRUE |
#title_font_style |
normal | #legend_title |
'' |
#title_font_size |
14 | #legend_title_font_weight |
bold |
#title_position |
out | #legend_title_font_style |
normal |
#subtitle |
NULL | #legend_title_font_size |
'' |
#figure_caption |
'' | #legend_position |
right |
#chart_summary |
'' | #legend_font_weight |
normal |
#accessible_table |
disabled | #legend_font_style |
normal |
#accessible_table_button_text |
'' | #legend_font_size |
NULL |
#accessible_table_button_class |
button | #width |
NULL |
#colors |
default palette | #height |
NULL |
#font |
Arial | #attributes |
[] |
#font_size |
12 | #chart_definition |
[] |
#gauge |
[] | #raw_options |
[] |
#background |
transparent | #content_prefix |
[] |
#stacking |
NULL | #content_suffix |
[] |
#color_changer |
FALSE | #library_type_options |
[] |
Additional properties honored by provider plugins / preprocess but not in getInfo(): #polar, #three_dimensional, #width_units, #height_units, and #entity (the entity a chart is attached to, used for token replacement in caption/summary).
chart_data (series)¶
| Property | Default | Meaning |
|---|---|---|
#title |
NULL | Series name (legend). |
#data |
[] | Values: scalars, or [x,y]/[x,y,z] for coordinate types. |
#labels |
NULL | Optional per-series labels. |
#color |
NULL | Series color. |
#show_in_legend |
TRUE | Legend visibility. |
#show_labels |
FALSE | Inline value labels. |
#chart_type |
NULL | Per-series type override (combo charts). |
#line_width |
1 | Line charts. |
#marker_radius |
3 | Marker size (px). |
#target_axis |
NULL | Key of the y-axis to bind to. |
#decimal_count |
NULL | Value formatting. |
#date_format |
NULL | e.g. %Y-%m-%d. |
#prefix / #suffix |
NULL | Value affixes. |
#raw_options |
[] | Library-native series options. |
chart_xaxis / chart_yaxis (axes — shared base)¶
| Property | Default | Property | Default |
|---|---|---|---|
#axis_type |
'' (linear/logarithmic/datetime/labels) | #labels_rotation |
NULL |
#title |
'' | #grid_line_color |
#ccc |
#title_color |
#000 |
#base_line_color |
#ccc |
#title_font_weight |
normal | #minor_grid_line_color |
#e0e0e0 |
#title_font_style |
normal | #max |
NULL |
#title_font_size |
12 | #min |
NULL |
#labels |
NULL | #opposite |
FALSE (secondary axis when TRUE) |
#labels_color |
#000 |
#raw_options |
[] |
#labels_font_weight / #labels_font_style |
normal | #labels_font_size |
NULL |
chart_data_item (single point)¶
| Property | Default |
|---|---|
#data |
NULL |
#color |
NULL |
#title |
NULL (often tooltip content) |
#raw_options |
[] |
16.3 Hook & event index¶
Documented in charts.api.php unless noted.
| Hook / event | Signature | Purpose |
|---|---|---|
hook_chart_alter |
(&$element, $chart_id) |
Alter the render array before conversion (library-agnostic). |
hook_chart_CHART_ID_alter |
(&$element) |
Same, targeted by #chart_id. |
hook_chart_definition_alter |
(&$definition, $element, $chart_id) |
Alter the library-specific definition after conversion. |
hook_chart_definition_CHART_ID_alter |
(&$definition, $element, $chart_id) |
Same, targeted by #chart_id. |
hook_charts_plugin_supported_chart_types_alter |
(&$types, $chart_plugin_id) |
Add/remove a provider's supported types. |
charts_chart_library (alter info) |
plugin definitions | Alter discovered chart library plugins (ChartManager). |
charts_type_info (alter info) |
type definitions | Alter discovered chart type definitions (TypeManager). |
TypesInfoEvent (ChartsEvents::TYPE_INFO) |
event | Programmatically alter the chart-type list at runtime. |
hook_library_info_alter |
(&$libraries, $extension) |
Module implements this to rewrite charts_* libraries to CDN URLs when CDN mode is on. |
16.4 Service & class index¶
| Service ID / class | Role |
|---|---|
plugin.manager.charts — ChartManager |
Discovers/instantiates library (provider) plugins; resolves site_default. |
plugin.manager.charts_type — TypeManager |
Discovers chart types from *.charts_types.yml; dispatches TypesInfoEvent. |
charts.table_builder — ChartTableBuilder |
Builds the accessible HTML data table from a chart element. |
charts.hooks — ChartsHooks |
OOP hook implementations (theme, preprocess, views data, library alter). |
charts.config_import_subscriber — ConfigImportSubscriber |
Recalculates charts.settings dependencies on config import. |
charts.plugins_uninstall_validator — PluginsUninstallValidator |
Blocks uninstalling a module whose plugin is the configured default. |
ConfigUpdater |
Migrates legacy (v3-era) settings to the current structure. |
Render elements (src/Element/) |
Chart, ChartData, ChartXaxis, ChartYaxis, ChartDataItem; form elements BaseSettings (charts_settings) and ChartDataCollectorTable (chart_data_collector_table). |
| Plugin contracts | ChartInterface + ChartBase (library plugins); TypeInterface + Type (type plugins); Attribute\Chart (current), Annotation\Chart (legacy). |
| Field API | ChartConfigItem (field type), ChartConfigItemDefaultWidget, ChartConfigItemDefaultFormatter, ChartConfigData (data type). |
| Views | ChartsPluginStyleChart (style chart), ChartsPluginDisplayChart (display chart_extension), fields NumericArrayField, ScatterField, BubbleField, ExposedChartType. |
| Traits | ApplyRawOptionsTrait, ColorHelperTrait, DependenciesCalculatorTrait, LibraryRetrieverTrait, ElementFormStateTrait, DataCollectorTableHasDataTrait. |
16.5 Route & permission index¶
Routes
| Route | Path | Handler | Requirement |
|---|---|---|---|
charts.settings |
/admin/config/content/charts |
ChartsConfigForm |
administer site configuration |
charts.settings.advanced |
/admin/config/content/charts/advanced |
ChartsConfigAdvancedForm |
administer site configuration |
Permissions
| Permission | Notes |
|---|---|
administer site configuration (core) |
Gates the configuration forms above. |
access all charts |
Defined by the module, restrict access: true; currently a placeholder ("needs to be fleshed out") rather than a fully wired gate. |
Chart visibility otherwise follows the access of the container (entity/field, View, or block). Ecosystem modules add their own routes/permissions (e.g. charts_text_filter.dialog at /charts-text-filter/dialog).
16.6 Architecture & data-flow map¶
Data flow (the module's central pipeline):
Data + settings (UI: Views/field/block, or API render array)
│
▼
charts core: assemble a `chart` render element → preRender
│ (hook_chart_alter runs here; type-support gate enforced)
▼
Provider plugin builds the library-specific definition
│ (or uses a supplied #chart_definition verbatim)
▼
hook_chart_definition_alter runs → definition JSON-encoded
│
▼
<div data-chart='{…}'> emitted (+ accessible table, optional debug)
│
▼
Drupal.Charts.Contents reads data-chart → drupalChartsConfigsInitialization event
│ (JS overrides may mutate here)
▼
Provider JS renders with the library (Highcharts/Google/Chart.js/C3/Billboard/…)
Directory map (top level):
| Path | Contains |
|---|---|
charts.*.yml, charts.module, charts.api.php |
Module metadata, services, routing, hook docs. |
src/Element/ |
Render + form elements. |
src/Plugin/chart/Library/ |
ChartInterface, ChartBase. |
src/Plugin/chart/Type/ |
Type plugin contract. |
src/Plugin/views/, src/Plugin/Field/, src/Plugin/DataType/ |
Views, field, and data-type integration. |
src/{ChartManager,TypeManager,ConfigUpdater}.php, src/Service/, src/EventSubscriber/, src/Hook/ |
Managers, services, subscribers, hooks. |
config/ |
Install config + schema. |
js/, templates/ |
Client API (charts.js) and the charts-chart.html.twig template. |
modules/charts_* |
Provider submodules and example/blocks submodules. |
16.7 Glossary¶
- Provider / library — the JavaScript charting engine (a
chartplugin) that renders the chart. - Chart type — the kind of chart (line, column, pie…), defined in
*.charts_types.yml; support is per-provider. - Render element — the Drupal array building blocks:
chart,chart_data,chart_xaxis,chart_yaxis,chart_data_item. - Series — one set of related values (
chart_data), shown as one line/bar-group/etc. - Axis — a reference scale (
chart_xaxis/chart_yaxis);#oppositemakes a secondary axis. - Data item — a single data point (
chart_data_item) with its own color/tooltip. - Single-axis (
y_only) vs. dual-axis (xy) — whether a type uses one dimension (pie/gauge) or two. - Coordinate type — a type whose points are
[x,y]/[x,y,z](scatter/bubble/heatmap). - Chart definition — the library-specific object produced from the render array and JSON-encoded into
data-chart. #raw_options— library-native options merged into the definition (per element).#chart_definition— a full, library-native definition supplied directly, bypassing the module's model and type gate.- Grouping — splitting one data column into a series per distinct value of a category field (one display).
- Chart attachment (
chart_extension) — a second Views display attached to a parent to make combo charts (mixed types / secondary axis). - Stacking — an independent display option that stacks existing series on stackable types.
- Debug object — the generated definition rendered as JSON beneath a chart when debug is enabled.
- CDN vs. local — loading a library from a third-party URL vs. from
/libraries/<name>. - Portable core — the nine types every bundled provider supports (area, bar, bubble, column, donut, line, pie, scatter, spline).
16.8 External links¶
- Project: https://www.drupal.org/project/charts
- Documentation (guide): https://www.drupal.org/docs/contributed-modules/charts
- HowTo index: https://www.drupal.org/docs/8/modules/charts/charts-howtos-0
- Issue queue: https://www.drupal.org/project/issues/charts
- Source: https://git.drupalcode.org/project/charts
- Ecosystem provider modules: Charts ECharts (
charts_echarts), ApexCharts (charts_apexcharts), Charts Kendo UI (charts_kendo_ui), Charts Plotly (charts_plotly). - Ecosystem feature modules: Charts Twig (
charts_twig), Charts Text Filter (charts_text_filter), and others listed on the project's ecosystem page. - Upstream libraries: Highcharts (highcharts.com), Google Charts (developers.google.com/chart), Chart.js (chartjs.org), C3 (c3js.org), Billboard.js (naver.github.io/billboard.js).