This guide walks you through setting up your environment, building the example visualization, and creating your own custom vizs using the Claude Code skill.
| Tool | Version | Purpose |
|---|---|---|
| Splunk Enterprise | 10.2+ | Hosts the custom visualizations |
| Node.js | 18+ | Runs webpack to bundle the visualization JS |
| Claude Code | Latest | AI assistant with the splunk-viz skill |
git clone https://github.com/your-org/splunk-custom-visualizations.git
cd splunk-custom-visualizationsBuild the included custom_single_value visualization to verify your setup:
./build.sh custom_single_valueThis will:
- Install npm dependencies (webpack)
- Bundle
visualization_source.jsintovisualization.js - Package everything into
dist/custom_single_value-1.0.0.tar.gz
You can iterate on your visualization without deploying to Splunk using the test harness. Add your new viz to harness-manifest.json:
{
"pathTemplate": "examples/{name}/appserver/static/visualizations/{name}",
"vizs": [
"custom_single_value",
"component_status_board",
"gauge",
"your_new_viz"
]
}Serve the repo locally (any static server works):
python3 -m http.server 8000Open http://localhost:8000/test-harness.html and select your visualization from the dropdown. The harness reads harness.json and builds the sidebar controls automatically. Change values and settings to see real-time updates. Use the theme toggle to verify your viz looks correct in both light and dark mode.
See TEST-HARNESS.md for full harness documentation including harness.json schema and data modes.
Option A — Install the tarball:
$SPLUNK_HOME/bin/splunk install app dist/custom_single_value-1.0.0.tar.gz
$SPLUNK_HOME/bin/splunk restartOption B — Symlink for development (changes reflect immediately after /_bump):
ln -s $(pwd)/examples/custom_single_value $SPLUNK_HOME/etc/apps/custom_single_value
$SPLUNK_HOME/bin/splunk restart- Open any Splunk dashboard in edit mode
- Add a new panel with a search (e.g.,
| makeresults | eval value="Hello") - Click the visualization picker and select Custom Single Value
- Use the Format panel to configure colours, alignment, and labels
This is where the Claude Code skill shines. Open the repo in your editor with Claude Code enabled and describe what you want:
Using /splunk-viz, create a custom visualization called "status_board" that
shows a grid of coloured tiles. Each row should have a name, status (ok/warning/critical),
and a detail string. Colour the tile based on status.
Claude will generate the complete app in examples/status_board/ with all the scaffolding, rendering code, and configuration files.
| File | Purpose |
|---|---|
default/app.conf |
App identity and version |
default/visualizations.conf |
Registers the viz with Splunk |
default/savedsearches.conf |
Example saved search with all settings |
README/savedsearches.conf.spec |
Documents every custom setting for btool check |
metadata/default.meta |
Exports the viz to all apps |
formatter.html |
Settings UI in the dashboard Format panel |
src/visualization_source.js |
Canvas 2D rendering code |
visualization.css |
Required by Splunk (transparent background) |
webpack.config.js |
Bundles source into AMD module |
package.json |
npm scripts for build/dev |
README.md |
Documentation, SPL reference, configuration |
After the initial generation, keep refining:
The tiles are too close together — add 8px gap between them.
Make the status text bold and add a subtle glow for critical items.
Add a "columns" setting so users can choose 2, 3, or 4 columns.
Each change updates the source files in place. Rebuild with:
./build.sh status_boardIf you're building a Splunk app that bundles dashboards and multiple custom visualizations together, you can scaffold the entire app structure:
Using /splunk-viz, scaffold a Splunk Dashboard Studio app called "my_monitoring_app"
with custom visualization support.
This generates a complete app with a vizs/ build pipeline — see EMBEDDING.md for the full details. Individual vizs are then created under vizs/ using the normal /splunk-viz workflow and merged into the app automatically by the build script.
When developing, symlink your app and use /_bump to reload static assets without restarting Splunk:
- Make changes to
visualization_source.js - Run
npm run buildin the viz directory (ornpm run devfor auto-rebuild on save) - Navigate to
http://<splunk>:8000/en-US/_bumpand click "Bump version" - Hard-refresh the browser (Cmd+Shift+R / Ctrl+Shift+R)
A restart is only needed when changing config files (app.conf, visualizations.conf, savedsearches.conf).
After installing, check for config issues:
$SPLUNK_HOME/bin/splunk btool checkIf you see "Invalid key" errors, ensure every display.visualizations.custom.* setting used in savedsearches.conf is also listed in README/savedsearches.conf.spec.
To use a custom font in your visualization:
-
Convert your font to base64:
base64 -i MyFont.woff2 | tr -d '\n' > font_base64.txt
-
Create a shared font CSS file (e.g.,
shared/fonts.css):@font-face { font-family: 'MyFont'; src: url(data:font/woff2;base64,PASTE_BASE64_HERE) format('woff2'); font-weight: bold; font-style: normal; font-display: swap; }
-
Uncomment the
FONT_CSSline inbuild.shand point it to your font file:FONT_CSS="$SCRIPT_DIR/shared/fonts.css" -
The build script will automatically prepend the font CSS to each viz's
visualization.cssduring packaging, keeping the source files clean. -
In your
visualization_source.js, wait for the font to load before drawing:var fontFamily = "'MyFont', sans-serif"; // See the skill documentation for the full font-loading pattern
Splunk real-time searches typically bin at 200 ms–1 s, so a viz receives a new sample only every half-second or so. Rendering raw values directly causes anything meant to move smoothly — gauge needles, bar heights, rotations, 2D positions — to snap between samples. The jerkiness undermines the live-data feel.
The skill applies a client-side ease-out tween that decouples the render rate from the data rate:
-
updateViewstores each new sample as a target value (snappingcurrentto the first sample so the viz doesn't sweep from zero on dashboard load). -
A short
setInterval(16 ms)orrequestAnimationFrameloop lerpscurrent → targeteach frame using a frame-rate-independent formula:current += (target − current) × (1 − exp(−smoothness × dt)) -
Each tick redraws the viz using
current. The timer idle-stops when the value has settled and restarts from the nextupdateViewthat changes the target. -
A
smoothnessformatter setting (per-second follow speed) controls how quicklycurrentchasestarget. Default8closes ~95% of the gap within 500 ms — sharp transitions still look near-instant, but dwell-state jitter disappears.0disables the tween entirely and restores snap behaviour.
- ✅ Continuous numerics — metrics, percentages, rates, utilisation, temperature, pressure, rotation angles, 2D coordinates.
- ❌ Discrete / categorical / boolean values — enum status, on/off toggles, integer counts where each increment matters.
- ❌ Tables or ranked lists where rows reorder between samples — tweening reorderings reads as glitchy, not smooth.
Ask Claude to "add smoothing" when generating a continuous-numeric viz, or see the Smoothing Between SPL Samples recipe in .claude/skills/splunk-viz/SKILL.md for the full implementation (covering both single-value tweens and multi-entity tweens keyed by identifier).
- ES5 only in
visualization_source.js. Usevar,function, andforloops. Noconst,let, arrow functions, or template literals. - JS defaults must match formatter defaults. Splunk does not send formatter
values on the first render, so your JS must fall back to the same defaults
declared in
formatter.html. - Read config in
updateView, notformatData. TheformatDatamethod should only parse the data structure. All config reading belongs inupdateView. - Handle HiDPI. Scale your canvas by
window.devicePixelRatioand draw in CSS pixel coordinates afterctx.scale(dpr, dpr). - Test both themes. Use the harness theme toggle to verify your viz looks correct in light and dark mode.
splunk-custom-visualizations/
.claude/
skills/
splunk-viz/
SKILL.md The Claude Code skill definition
examples/
custom_single_value/ Example viz (ready to install)
README.md
default/
app.conf
visualizations.conf
savedsearches.conf
metadata/
default.meta
README/
savedsearches.conf.spec
appserver/
static/
visualizations/
custom_single_value/
src/
visualization_source.js
formatter.html
visualization.css
webpack.config.js
package.json
your_new_viz/ Your vizs go here
...
build.sh Build and package script
dist/ Built tarballs (gitignored)
INSTRUCTIONS.md This file
README.md Project overview
- Read TEST-HARNESS.md for the full harness documentation
including
harness.jsonschema and data modes - Read EMBEDDING.md to learn how to embed visualizations into an existing Splunk app (manual or automated build pipeline)
- Read CONTRIBUTING.md for the project rules and how to submit changes
- Browse the existing examples in
examples/to see patterns for different visualization types
When a Splunk dashboard uses real-time searches, a small blue status dot appears on each panel. These can be distracting, especially on NOC screens or presentation dashboards.
To hide them, create a browser bookmark with this URL:
javascript:(function()%7Bconst s=document.createElement('style');s.textContent='div[data-test="simple-status-icon-container"]%7Bdisplay:none%7D';document.head.appendChild(s);%7D)();
Click the bookmark on any Splunk dashboard page to inject a CSS rule that hides the status icons. The effect lasts until you reload the page.
| Issue | Fix |
|---|---|
| Viz doesn't appear in picker | Restart Splunk. Check visualizations.conf stanza name matches the directory name. |
"Invalid key" in btool check |
Add the missing setting to README/savedsearches.conf.spec |
| Canvas is blank | Check browser console for JS errors. Verify visualization.js was built (npm run build). |
| Viz doesn't update with new data | Use /_bump then hard-refresh. Check formatData returns a new object (not the raw data). |
| Font not rendering | Ensure @font-face is in visualization.css (not just the source). Check the font-loading guard in updateView. |
| Text overflows panel | Use fitText() to auto-scale. Check ctx.measureText() logic. |