Skip to content
Stand with Ukraine flag

IoT Solution Template Contribution Guide

Welcome to ThingsBoard IoT Hub. This guide walks you through building a solution template that end users can install on their ThingsBoard instance in a single click.

A solution template is the most powerful contribution type in IoT Hub: a ZIP archive that bundles a complete, working IoT solution — device profiles, devices (with telemetry emulators), dashboards, rule chains, asset profiles, calculated fields, and rich instructions. When a user clicks Install, the template provisions every entity in one transaction and the user sees a live dashboard with simulated data within seconds.

Solution templates are ideal for showcasing a vertical use case (smart office, fleet tracking, swimming pool SCADA, fuel monitoring) end-to-end.


Create a free Creator account on the ThingsBoard Creator Portal to start publishing your items.


When a user clicks Install on your solution template in IoT Hub:

  1. They review the description (from description.md) and preview gallery (from imageUrls in solution.json).
  2. They confirm installation — the platform atomically provisions every entity in your template:
    • Device profiles, asset profiles, rule chains, calculated fields (first, so references resolve)
    • Devices, assets, customers, dashboards (next, referencing the profiles)
    • Device emulators (last, seeded with telemetry)
  3. They are redirected to the main dashboard with live data already flowing.
  4. The post-install instructions (from instructions.md) are shown alongside the dashboard — telling them how to inspect the setup, connect real hardware, customize widgets, and so on.

Your job as a creator is to describe the complete solution declaratively in the archive so that a single installation produces a working end-to-end demo.

The fastest way to start is to grab a sample template, adapt it, and upload. Pick the one closest to the solution you want to build:

Then:

  1. Edit solution.json — update title, previewImageUrl, and imageUrls to match your solution.
  2. Replace the files in dashboards/, device_profiles/, asset_profiles/, rule_chains/, and calculated_fields/ with exports from your own ThingsBoard instance. Keep any named references (entityNameFilter, defaultRuleChainId) aligned with the entities you declare in the next step.
  3. Update the JSON arrays in entities/ to wire your exported files into concrete instances — devices, assets, customers, dashboards, rule chains, and emulators.
  4. Rewrite description.md and instructions.md for your solution. Keep the ## Solution Description heading and the ${MAIN_DASHBOARD_URL} / ${DOCKER_CONFIG} / ${DOCS_BASE_URL} placeholders.
  5. Re-zip the folder contents (not the folder itself) and upload via IoT Hub → + Add new item. The upload form asks for the target edition (CE, PE, or both), version, categories, and use cases — see Editions.

The sections below are a complete reference for every part of a solution template package.

A solution template is a ZIP archive with a fixed layout. File names matter — the platform validates the archive against this structure.

my-solution.zip
├── solution.json (required — manifest)
├── description.md (required — marketplace description, Markdown)
├── instructions.md (required — post-install instructions, Markdown)
├── edge_instructions.md (optional — post-install instructions for edge deployments)
├── entities/ (required — see "entities/" section)
│ ├── devices.json
│ ├── assets.json
│ ├── customers.json
│ ├── dashboards.json
│ ├── rule_chains.json
│ └── device_emulators.json
├── dashboards/ (required — contains one JSON per dashboard referenced in entities/dashboards.json)
│ ├── main_dashboard.json
│ └── maintenance.json
├── device_profiles/ (optional — one JSON per device profile)
│ ├── temperature_sensor.json
│ └── gateway.json
├── asset_profiles/ (optional — one JSON per asset profile)
│ └── building.json
├── rule_chains/ (optional — one JSON per rule chain)
│ └── alarm_routing.json
├── calculated_fields/ (optional — one JSON per calculated field)
│ └── average_temp.json
└── images/ (optional — custom preview/gallery images)
├── preview.png
└── gallery-1.png

The ZIP archive must contain all three of these at the root. The platform rejects the package if any are missing.

FilePurpose
solution.jsonManifest — defines title, preview image URL, gallery images, install timeout
description.mdLong-form marketplace description shown on the detail page and install screen
instructions.mdPost-install guide shown after the user’s solution is provisioned
FolderPurpose
entities/Declarations of every entity the solution creates (devices, assets, customers, dashboards, rule chains, device emulators)
dashboards/Dashboard JSON files referenced from entities/dashboards.json
FolderPurpose
device_profiles/Device profile templates referenced from entities/devices.json
asset_profiles/Asset profile templates referenced from entities/assets.json
rule_chains/Rule chain templates referenced from entities/rule_chains.json
calculated_fields/Calculated field templates referenced from related entities
images/Bundled preview and gallery images referenced from solution.json (previewImageUrl, imageUrls) by relative path, e.g. images/preview.png

ThingsBoard has two editions: Community Edition (CE) and Professional Edition (PE). When you upload a template in IoT Hub, the upload form asks which edition (or both) it targets. The selection determines where the template appears in the catalog and constrains which features your template may use.

  • CE supports: devices, assets, customers, dashboards, rule chains, device profiles, asset profiles, calculated fields, ChirpStack integration.
  • PE adds: role-based access control (RBAC), white-label branding, scheduler, converters, most integrations (TTN, LORIOT), advanced dashboard states, custom translations, reporting.

If your template uses only CE features but also offers a richer PE experience (e.g. leveraging roles), publish a CE version and a separate PE version.

solution.json at the ZIP root is the minimal manifest the platform needs to render your catalog card and unpack the installation.

{
"title": "Swimming pool SCADA system",
"installTimeoutMs": 0,
"previewImageUrl": "https://img.thingsboard.io/solutions/swimming_pool_scada_system/hp-swimming-pool-1.png",
"imageUrls": [
"https://img.thingsboard.io/solutions/swimming_pool_scada_system/hp-swimming-pool-1.png",
"https://img.thingsboard.io/solutions/swimming_pool_scada_system/tr-swimming-pool-2.png",
"https://img.thingsboard.io/solutions/swimming_pool_scada_system/hp-swimming-pool-3.png"
]
}
FieldTypeDescription
titlestringHuman-readable solution name shown on the catalog card
previewImageUrlstringMain preview image shown on browse cards and the detail page header. Accepts an external URL (e.g. https://img.thingsboard.io/...), a relative path to a file in the ZIP’s images/ folder (e.g. images/hp-swimming-pool-preview.png), or an inline data URI (data:image/png;base64,...)
FieldTypeDescription
imageUrlsstring[]Gallery images shown on the solution’s detail page. Each entry uses the same URL/path rules as previewImageUrl — e.g. https://img.thingsboard.io/... or images/hp-swimming-pool-gallery-1.png for files in the ZIP’s images/ folder
installTimeoutMsintegerUpper bound for how long the install may take (in ms). 0 means use the default server timeout. Set this only if your template creates many entities or heavy rule chains

At install time the platform resolves every image, saves it as a ThingsBoard image resource, and rewrites dashboard and profile references to point at the new resource IDs — so the solution keeps working even if the original image host goes down later.

  • Preview image — 16:9 aspect ratio, 1600×900 px, under 200 KB after compression.
  • Gallery images — 16:9 or 4:3 aspect, 800×600 px minimum, showing different screens or views of the dashboard.
  • Use PNG for screenshots with text or charts; JPG for photos.
  • Always include at least 2–3 gallery images — users browse the gallery before clicking install.
  • Crop to show dashboard content, not surrounding chrome (toolbars, navigation).

The entities/ folder declares every instance your template creates. Each file is a JSON array.

entities/
├── devices.json (devices, with optional sharedAttributes and emulator refs)
├── assets.json (assets, referencing asset profiles)
├── customers.json (customer hierarchy, often empty: [])
├── dashboards.json (dashboards, referencing files in dashboards/)
├── rule_chains.json (rule chains, referencing files in rule_chains/)
└── device_emulators.json (telemetry emulator profiles)

Each file contains a JSON array, even if empty ([]).

Lists every device the template creates. References device profiles from the device_profiles/ folder by file name.

[
{
"name": "Pool System Gateway",
"type": "default",
"additionalInfo": {
"gateway": true
},
"sharedAttributes": {
"active_connectors": ["Pool connector"],
"Pool connector": {
"mode": "basic",
"name": "Pool connector",
"type": "modbus",
"configurationJson": { ... }
}
}
},
{
"name": "Main intake valve",
"type": "Valve",
"emulator": "Valve Emulator"
}
]

Per-device fields:

FieldDescription
nameDevice name shown in ThingsBoard
typeDevice profile type (must match a profile in device_profiles/ or the default default)
additionalInfoArbitrary metadata (e.g. {"gateway": true} to mark a gateway device)
sharedAttributesShared attributes saved to the device on creation — useful for gateway connector configs
attributesServer-side attributes saved on creation (the field is named attributes, but values are stored as server-side attributes)
emulatorName of an entry from entities/device_emulators.json — binds this device to a telemetry emulator
credentialsOptional custom credentials (access token, MQTT basic, X.509)
[
{
"name": "Main Building",
"type": "Building",
"label": "HQ",
"additionalInfo": {
"description": "Head office"
}
}
]
[
{
"title": "Operations Team",
"email": "ops@example.com",
"country": "USA",
"city": "San Francisco"
}
]

Use [] if your template has no customer hierarchy.

Wires dashboard JSON files from the dashboards/ folder to actual dashboards.

[
{
"name": "Swimming Pool SCADA",
"file": "pool_scada_dashboard.json",
"main": true
},
{
"name": "Maintenance",
"file": "maintenance_dashboard.json"
}
]
FieldDescription
nameDashboard name in ThingsBoard
filePath relative to dashboards/
mainMarks this as the main dashboard. ${MAIN_DASHBOARD_URL} in instructions.md resolves to this dashboard. Exactly one dashboard should be marked main
[
{
"name": "Pool Alarm Routing",
"file": "pool_alarm_routing.json",
"jsonId": "8725c7c0-7041-11ef-a4b8-c14490e86420"
}
]
FieldDescription
nameRule chain name in ThingsBoard
filePath relative to rule_chains/
jsonIdUUID used for cross-references within the template (e.g. defaultRuleChainId in device profiles). Generate once, keep stable

Device emulators are the secret sauce that makes a solution template look alive. Instead of users seeing empty dashboards, each emulated device streams realistic synthetic telemetry the moment the template is installed.

Emulators are defined as a JSON array in entities/device_emulators.json:

[
{
"name": "Tank Emulator",
"publishPeriodInDays": 7,
"publishFrequencyInSeconds": 3600,
"publishPauseInMillis": 1,
"activityPeriodInMillis": 720000,
"telemetryProfiles": [
{
"key": "battery",
"valueStrategy": {
"type": "natural",
"precision": 1,
"minStartValue": 99,
"maxStartValue": 99,
"minLowValue": 21,
"maxLowValue": 21,
"minHighValue": 21,
"maxHighValue": 100,
"minIncrement": 100.0,
"maxIncrement": 100.0,
"minDecrement": 0.1,
"maxDecrement": 0.2,
"holidayMultiplier": 1.0,
"workHoursMultiplier": 1.0,
"nightHoursMultiplier": 1.0
}
}
]
}
]

Top-level emulator fields.

FieldDescription
nameEmulator name, referenced by devices via "emulator": "..." in entities/devices.json
publishPeriodInDaysHow many historical days of data to backfill on install
publishFrequencyInSecondsHow often each telemetry point is emitted
publishPauseInMillisDelay between individual telemetry writes
activityPeriodInMillisLength of each simulated activity burst
telemetryProfilesArray of per-key generation profiles

Telemetry profile — valueStrategy.type.

StrategyUse for
naturalSmoothly drifting numeric values (temperature, humidity, battery) with min/max bounds and increment/decrement ranges
enumCategorical values that switch randomly between a defined set
constantValues that stay fixed
stepValues that transition between levels on a schedule

Why emulators matter.

Without emulators, your user installs the template, opens the dashboard, and sees empty widgets. With emulators, the user sees a realistic live system within seconds, which is the difference between a compelling demo and a confusing one.

Each dashboard is a ThingsBoard dashboard export (JSON) with entity aliases rewritten to use named references instead of hardcoded UUIDs. The platform resolves those names to the newly-created entities at install time.

  1. Open the dashboard in ThingsBoard.
  2. Click Export dashboard (download icon).
  3. Save the JSON to dashboards/your_dashboard.json.

Downloaded dashboards have hardcoded UUIDs in every entity alias. You must replace each hardcoded UUID with a name reference that matches an entity in entities/*.json:

Before (downloaded):

{
"entityAliases": {
"d7f5...": {
"alias": "Heat pump",
"filter": {
"type": "singleEntity",
"singleEntity": {
"id": "1bc3ebc0-a607-11ed-9c79-91622243936c",
"entityType": "DEVICE"
}
}
}
}
}

After (template):

{
"entityAliases": {
"d7f5...": {
"alias": "Heat pump",
"filter": {
"type": "entityName",
"resolveMultiple": false,
"entityType": "DEVICE",
"entityNameFilter": "Heat pump"
}
}
}
}

The entityNameFilter must exactly match a device name from entities/devices.json (or an asset name from entities/assets.json, depending on the entityType).

Each profile is a single JSON file exported from ThingsBoard. File names are referenced from the type field in entities/devices.json or entities/assets.json.

device_profiles/
├── heat_pump.json
├── sand_filter.json
└── valve.json
{
"name": "Heat pump",
"type": "DEFAULT",
"transportType": "DEFAULT",
"provisionType": "DISABLED",
"defaultRuleChainId": {
"entityType": "RULE_CHAIN",
"id": "8725c7c0-7041-11ef-a4b8-c14490e86420"
},
"profileData": {
"configuration": {"type": "DEFAULT"},
"transportConfiguration": {"type": "DEFAULT"},
"alarms": [ ... ]
}
}

If you reference a rule chain via defaultRuleChainId, the id must match the jsonId of an entry in entities/rule_chains.json. The platform rewrites this reference to the actual UUID after the rule chain is created.

Rule chains and calculated fields are exported as JSON files from ThingsBoard and dropped into rule_chains/ and calculated_fields/ respectively. The exports require no post-edit — wiring happens through entities/ and device profiles.

  1. In ThingsBoard, open Rule Chains and export the rule chain.
  2. Save the JSON to rule_chains/your_rule_chain.json.
  3. Register it in entities/rule_chains.json with name, file, and a stable jsonId.
  4. If a device profile uses this rule chain as its default, set its defaultRuleChainId.id to the same jsonId. The platform rewrites both to the real UUID at install time.
  1. In ThingsBoard, go to the Calculated fields page and click the Export button (download icon) in the field’s row.
  2. Save the JSON to calculated_fields/your_calculated_field.json.
  3. Reference the file from the owning profile’s configuration — no separate entry in entities/ is required.

description.md is the long-form description shown on the solution’s detail page and inside the install dialog. Standard Markdown is supported.

The platform extracts a short description for browse cards from a specially-named section. Structure your file like this:

### 💡 Solution Description
The **Swimming Pool SCADA System** template is a production-ready accelerator for
swimming pool facility management. It bridges the gap between mechanical pool
equipment and modern IoT platforms, providing a unified SCADA interface for water
quality, temperature control, and energy management.
#### ✨ Highlights
Transform your swimming pool facility management with the **Swimming Pool SCADA
System** solution template...
#### 📦 What's inside the box?
* **🎛️ SCADA Dashboard:** A feature-rich dashboard offering real-time
visualization of water temperature, pump status, and valve positions.
* **🤖 Modbus Emulator:** A complete Docker environment simulating 14 distinct
devices...
#### 🌟 Why use this template?
...
  • The first heading that matches the pattern # Solution Description (any heading level, case-insensitive) marks the start of the short description.
  • Everything after that heading and before the next heading of the same or higher level is considered the description.
  • The first paragraph of that section becomes the short description shown on catalog browse cards.
  • If no such heading exists, the entire description.md is treated as the short description and truncated at the first paragraph.

Keep the first paragraph under 512 characters — it’s shown in a card.

  1. Solution Description section — one paragraph, one sentence, used for cards.
  2. Highlights — what the solution achieves (bullet points).
  3. What’s inside the box — every entity type the template creates.
  4. Why use this template — real benefits to the user.
  5. Key features — technical capabilities.
  6. Real-world applications — industries and use cases that match.

instructions.md is shown to the user after the solution has been installed. It should walk them through their new setup: what was created, how to view it, how to connect real hardware, how to customize.

Standard Markdown is supported, plus ${VARIABLE} placeholders that are resolved after installation.

## Solution instructions
Welcome to your new **Swimming Pool SCADA System** solution 👋
### 🐳 Step 1: Install Docker Compose
Follow the instructions in the [official Docker Compose installation guide](https://docs.docker.com/compose/install/).
### 🏊 Step 2: Launch the Modbus Pool Emulator
```bash
docker run --pull always --rm -d --name tb-modbus-pool-emulator \
-p 5021-5034:5021-5034 \
thingsboard/tb-modbus-pool-emulator:1.0-stable
```
### 🚀 Step 3: Launch the IoT Gateway
Create a `docker-compose.yml` file with the configuration:
```bash
${DOCKER_CONFIG}
```
Then run:
```bash
docker compose up
```
### Interacting with the Swimming Pool SCADA System
The [Swimming Pool SCADA system](${MAIN_DASHBOARD_URL}) dashboard is designed to
visualize and interact with the data from multiple pool components.
**💡 Tip:** For further customization, refer to the
[dashboard development guide](${DOCS_BASE_URL}/user-guide/dashboards/).

These placeholders are resolved at install time:

PlaceholderDescription
${MAIN_DASHBOARD_URL}Deep link to the solution’s main dashboard
${DOCKER_CONFIG}Full docker-compose.yml content for the gateway, pre-filled with host, port, and token. Usually wrapped in a bash code block
${DOCS_BASE_URL}Base URL of the ThingsBoard documentation — use for links that should work on both ThingsBoard Cloud and on-prem
${item-link:<item-uuid>}Renders a link card pointing at another IoT Hub item (widget, dashboard, rule chain, etc.) the user can install in one click. Use for dependencies your template relies on

Add {:copy-code} after a fenced code block to render a copy-to-clipboard button:

```bash
docker run --pull always --rm -d --name tb-modbus-pool-emulator \
-p 5021-5034:5021-5034 thingsboard/tb-modbus-pool-emulator:1.0-stable
{:copy-code}
```

A good instructions.md follows this flow:

  1. Welcome — one-line orientation.

  2. Prerequisites — what the user needs (Docker, network access, accounts). If your solution template depends on another IoT Hub item you have published (e.g. a custom widget, dashboard, or rule chain), reference it here with ${item-link:<item-uuid>}. The platform replaces the placeholder with a link card pointing at that IoT Hub item so users can install the dependency in one click. Example:

    This solution uses our custom Air Quality widget. Install it first:
    ${item-link:d93b9a1a-1c07-408c-bb86-743bbcb36832}
  3. Launch the emulator — copy-paste Docker commands.

  4. Launch the gateway — use ${DOCKER_CONFIG} to give them a ready-to-run config.

  5. Interacting with the dashboard — link to the main dashboard via ${MAIN_DASHBOARD_URL}.

  6. Historical analysis / remote control / customization — show off the dashboard’s features.

  7. Connecting real devices — bridge from demo to production.

edge_instructions.md is an optional file at the ZIP root that provides post-install instructions for ThingsBoard Edge deployments. Some solution templates work on ThingsBoard Edge instances as well as on the main server. If your template includes edge-compatible rule chains and a user installs it on an edge-enabled instance, the platform shows this edge_instructions.md file instead of instructions.md.

Use edge_instructions.md to explain edge-specific setup: how to assign the rule chains to the edge, how to relay data back to the cloud, any extra Docker configuration for edge deployments.

If you don’t provide edge_instructions.md, the platform shows the regular instructions.md.

Pick one or more categories that describe your solution. These are distinct from widget, dashboard, or rule chain categories.

Allowed values: Agriculture, Analytics, Asset Management, Device Management, Energy, Industrial, Monitoring, Smart City.

Pick one or more IoT domains the solution addresses. These are shared across the whole marketplace — a user filtering by a use case sees your solution alongside matching widgets, dashboards, and devices.

Allowed values: Air Quality Monitoring, Asset Tracking, Cold Chain, Drones, Environment Monitoring, Fleet Tracking, Health Care, Industrial Automation, Predictive Maintenance, Robotics, SCADA, Smart Building, Smart City, Smart Energy, Smart Farming, Smart Home, Smart Metering, Smart Office, Smart Retail, Solar Monitoring, Tank Level Monitoring, Waste Management.

Every upload creates a new version of your template. Bump the version in the upload form whenever you release a new revision.

  • Use semantic versioning: 1.0.0 for the initial release, 1.1.0 for new features, 1.0.1 for fixes.
  • Always write a changelog entry explaining what changed.
  • Older versions are automatically archived when a new version is published.

You provide via the upload form.

FieldPurpose
EditionCE, PE, or both — determines where your template appears
VersionSemver — bump for every content change
CategoriesBrowse filtering — from Solution template categories
Use CasesBrowse filtering — from Use cases
ChangelogVersion-specific notes
Min / max ThingsBoard versionCompatibility constraints

When you upload a new version, pair the version bump with a changelog entry — a short note that tells users exactly what changed since the previous version, so they can decide whether, and how, to upgrade.

Summarize what changed since the previous version: new features, bug fixes, breaking changes, and any migration notes users need to know.

Group your entry under these headings, and include only the ones that apply:

HeadingWhat goes here
New featuresNew capabilities, configuration options, or behavior added in this version
Bug fixesDefects corrected since the previous version — describe the symptom users saw, not the internal cause
Breaking changesAnything that changes existing behavior in a way that can disrupt an installed copy — renamed keys, removed options, changed defaults
Migration notesThe concrete steps an existing user must take to move from the previous version to this one
  • Lead with the user impact. Describe what the user can now do, or what no longer breaks — not how you implemented it.
  • Be specific. Name the exact keys, fields, settings, or outputs that changed. “Renamed the output key from temp to temperature” is actionable; “improved naming” is not.
  • One change per bullet. Keep each item to a single, scannable line.
  • Flag breaking changes loudly. If an upgrade can disrupt an installed copy, say so explicitly and pair it with a migration note.

Pair every entry with a semantic version bump — patch (1.0.1) for fixes, minor (1.1.0) for backward-compatible features, major (2.0.0) for breaking changes.

## 2.0.0
### Breaking changes
- Renamed the output key from `temp` to `temperature` to match the
ThingsBoard telemetry convention.
### Migration notes
- Update any dashboards, alarm rules, or downstream calculated fields
that read the `temp` key to read `temperature` instead.
### New features
- Added an optional `humidity` argument; when present, the formula
now also computes a `dewPoint` output.
### Bug fixes
- Fixed missing output when the input telemetry arrived as a string
instead of a number.

Once you complete the upload wizard and click Submit, your version enters the IoT Hub review queue. The ThingsBoard team checks every submission before it goes live.

To see the current status of your submission, open the Creator Portal and go to Items. Find your item in the list and click the Manage Versions icon in its row. The Versions page lists every version you have uploaded with a Status column that updates in real time.

StatusMeaning
Pending ReviewYour version is in the review queue and has not been evaluated yet
ApprovedYour version passed review and is now live on IoT Hub
RejectedYour version did not pass review — see the reviewer comment for details

Reviewers verify that the submission meets the same criteria as the Pre-Upload Checklist: the solution template installs cleanly on a fresh tenant, entity declarations and dashboard exports are correct, post-install instructions are complete and accurate, and the listing and readme give users enough context to evaluate and use the template.

The Status column will show Rejected. Open the version details to read the reviewer’s comment, which explains specifically what needs to be fixed.

To resubmit:

  1. Fix the reported issues in your local package.
  2. Return to Items → Manage Versions for your item.
  3. Click + Upload new version and complete the wizard with the corrected package.