Grafana panel plugin (for sending downlinks)

TTN version: TTNv3
Last updated: April 18, 2023

This article provides instructions for creating / importing a Grafana panel plugin (for sending downlinks via TTS). We use an open-source analytical and interactive visualization web application Grafana for data visualization, so it’s nice to have end device data as well as the ability to control the end device with downlinks without having to switch between different web pages or programs.


  • You don’t need to create your own Grafana panel plugin for sending downlinks via TTS. All you have to do is import our ready-made plugin into your Grafana. To import our plugin, follow the instructions at the very bottom of this page in the “Import LoRa@VSB’s plugin from Github to your Grafana” section.
  • Make sure your Grafana web application is served over HTTPS. Some browsers, like Safari on iOS, may block HTTP requests made from a secure context.




Fair Use Policy

The TTN documentation says about Fair Use Policy:

On The Things Network’s public community network a Fair Use Policy applies which limits the uplink airtime to 30 seconds per day (24 hours) per node and the downlink messages to 10 messages per day (24 hours) per node. If you use a private network, these limits do not apply, but you still have to be compliant with the governmental and LoRaWAN limits.

Note: Don’t send more than 10 downlinks per day per node.


Creating a Grafana panel plugin (for sending downlinks)

Preparation of the development environment

This section describes the installation of prerequisites for plugin development.

  • NodeJS >=14
  • yarn
  • Grafana Cloud account
  • GitHub account


Docker, Node.JS and yarn
  1. Open a terminal.
  2. Update and upgrade packages:
    $ sudo apt update
    $ sudo apt upgrade
  3. Install the latest Docker version:
    $ sudo apt install
  4. Install cURL and the latest LTS Node.JS version:
    $ sudo apt install -y curl 
    $ curl -sL | sudo -E bash -
    $ sudo apt install nodejs
  5. Check version of installed Node.JS and npm:
    $ node --version
    $ npm --version
  6. Install yarn and check its installed version:
    $ sudo npm install --global yarn
    $ yarn --version


Grafana Cloud account

You will need a Grafana Cloud account to generate an API key to sign your finished plugin.

  1. Create a Grafana Cloud account on Grafana if you don’t have one.


GitHub account

You will need a GitHub account to distribute your finished plugin.

  1. Create a GitHub account on GitHub if you don’t have one.


Create a new panel plugin

  1. Open a terminal in your /home/”username” directory.
  2. Create a grafana-plugins folder:
    $ mkdir grafana-plugins
  3. Move into grafana-plugins folder and create a plugin from template using create-plugin:
    $ cd grafana-plugins
    $ npx @grafana/create-plugin@latest
  4. What is going to be the name of your plugin? – Write here what you want the panel to be called.
  5. What is the organization name of your plugin? – The name of your organization from the Grafana Cloud Portal.
  6. How would you describe your plugin? – Write something here or leave it blank.
  7. What type of plugin would you like? – panel
  8. Do you want to add Github CI and Release workflows? – N
  9. Do you want to add a Github workflow for automatically checking “Grafana API compatibility” on PRs? – N
  10. Change directory to your newly created plugin:
    $ cd ./my-plugin-name
  11. Install the frontend dependencies:
    $ yarn install
  12. Build the plugin frontend code and let it run in your terminal:
    $ yarn dev


Check out the new plugin in your Grafana

  1. Open a new terminal in your /home/”username” directory.
  2. Run this command to run Grafana in a Docker container with the development mode enabled:
    $ sudo docker run --rm -it -e "GF_DEFAULT_APP_MODE=development" -p 3000:3000 -v "$(pwd)"/grafana-plugins:/var/lib/grafana/plugins --name=grafana grafana/grafana:latest
    1. Meanings:
      1. sudo docker run – Runs a new Docker container.
      2. –rm  – Removes the container when it exits.
      3. -it – Allocates a TTY for the container and keeps STDIN open.
      4. -e “GF_DEFAULT_APP_MODE=development” – Sets an environment variable, enabling development mode for Grafana.
      5. -p 3000:3000 – Maps the container’s port 3000 to the host’s port 3000.
      6. -v “$(pwd)”/grafana-plugins:/var/lib/grafana/plugins – Maps the grafana-plugins directory in the current working directory on the host to the /var/lib/grafana/plugins directory in the container.
      7. –name=grafana – Names the container “grafana”.
      8. grafana/grafana:latest – Specifies the Grafana image with the latest tag.
  3. Now you can visit your Grafana web application from a browser at http://localhost:3000.
  4. Log in with username admin and password admin.
  5. Skip changing your Grafana account password.
  6. Go to Configuration -> Plugins. Make sure that your plugin is there.


Grafana dashboard setup
  1. Click on Dashboards -> Browse -> New -> New Dashboard.
  2. Choose Add a new panel.
  3. Choose your plugin panel and save dashboard.
  4. If you now make some changes in the plugin code (and you have yarn dev running in the terminal), then when you reload the dashboard page, the new changes to the appearance and functionality of the panel will be updated immediately.
  5. You should see this:Default plugin


Plugin programming – Basic information

Focus on /src/module.ts, /src/types.ts and /src/components/SimplePanel.tsx files.



This file contains the main panel component, which is responsible for rendering the panel’s content. It is a React component that receives the panel’s options, data, and other properties.

  • Define the panel component as a functional component or class component.
  • Use the received props to access the panel options and data.
  • Write the logic for rendering the panel content, such as charts, tables, or other visualizations.
  • Handle any necessary interactivity, such as user input, panel resizing, or updates due to data changes.



This file contains type definitions for your plugin. It usually includes interfaces for the plugin options, any custom data types, and props for the main panel component.



This file is the entry point for the plugin. It’s responsible for registering the plugin with Grafana by exporting the panel component and any related configuration. Here, you import the main panel component and set various configuration options like the panel’s default options, editor configuration, and data formats. The module.ts file should export the plugin class, which extends PanelPlugin.


Useful links for programming your plugin:


Plugin programming – Panel plugin for sending downlinks

If you want, you can copy our code files to the Grafana panel plugin, which can send downlinks via TTS. Replace your files with the following files (marked in orange):

  • onOptionsChange: (options: SimplePanelOptions) => void;
    • This line adds a new property to the Props interface called onOptionsChange. This property is a function that takes a single argument options of type SimplePanelOptions and returns nothing (void).
  • export const SimplePanel: React.FC<Props> = ({ options, data, width, height, onOptionsChange }) => {};
    • a functional React component named SimplePanel is being defined. The component accepts properties (props) of the type Props, which was defined earlier. The component is written as an arrow function and is exported, so it can be imported and used in other parts of the application.
  • The first part of the React component sets the initial state and configuration of the component using functional components and hooks. This initial state will be used and updated throughout the component’s lifecycle.
    • The function checks if a given payload string is a valid hexadecimal value, returning true if valid and false otherwise.
  • const sendPostRequest = async () => {};
    • This function performs a POST request to send a downlink message via TTS. It first checks for empty fields, valid payload format, and complete hex values. Then, it constructs the target URL using the provided TTN server, encodes the payload to base64, and prepares the request header and data. Finally, it sends the POST request and sets the log message based on the response.
  • const handleSaveTTS = () => {};
    • The function is a handler for saving The Things Stack (TTS) settings. It first checks whether the API key is configured and updates the isAPIKeyConfigured state accordingly. Then, it calls onOptionsChange to update the panel options with the new settings, including the Application name, End device name, and TTN server. Finally, it sets the alert variant to ‘warning’ and displays a log message prompting the user to save the dashboard.
  • const handleAPIKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => {};
    • The function handles updates to the API key input field, synchronizing the panel options with the user’s input using the onOptionsChange callback.
  • const handleAPIkeyReset = () => {};
    • The function handles the API key reset action. It updates the panel options by setting the targetAPIkey to an empty string and sets isAPIKeyConfigured to false.
  • return ();
    • This return statement renders the component layout containing three sections: Alert, TTS settings and Downlink settings.
      • An alert at the top of the component displays messages related to validation, errors, or success.
      • The TTS settings section allows the user to input TTN server, API key, Application name, and End device name. It also includes a “Save” button to save these settings.
      • The Downlink settings section allows the user to input FPort, priority, insert mode, confirmed downlink (as a checkbox), and payload in bytes. It also includes a “Send” button to send a POST request with these settings.



The variables defined here keep their values after saving the dashboard and after reloading the page.

  • targetTTNServer – The URL of the ttn server (network cluster) is stored here
  • targetAPIkey – This is where the API key from TTS is stored
  • targetAppName – Application name is stored here
  • targetEndDeviceName – End device name is stored here



Imports dependencies and creates a Grafana panel plugin called SimplePanel. It removes default padding and sets no custom options, then exports the plugin for use within Grafana.


Packaging the plugin

  1. If you are satisfied with the resulting code and the appearance of the plugin, package your plugin using the following command in a terminal in your plugin’s root directory:
    $ yarn build


Signing the plugin

To sign a plugin, you need to decide the signature level you want to sign it under. The signature level of your plugin determines how you can distribute it. You can sign your plugin under three different signature levels.

Public plugins need to be reviewed by the Grafana team before you can sign them, but you can sign your plugin as Private plugin (Private Plugins are for use on your own Grafana. They may not be distributed to the Grafana community, and are not published in the Grafana catalog.).

For more information on signature levels, see the Plugin signature levels documentation.


Generate an API key
  1. Log into your Grafana Cloud account to access the Cloud Portal.
  2. Select the organization that you want to add an API key to, by selecting from the dropdown in top left.
  3. Click API Keys from the SECURITY section on the left.
  4. Click +Add API Key.
  5. In API Key Name, enter a name for your API key.
  6. In Role, select the PluginPublisher role.
  7. Click Create API Key.
  8. A token is created and displayed. Copy the token and store it in a safe place, because it will not be displayed again.
  9. Click Close when finished.

For more information on creating Grafana Cloud API keys, see the Create Grafana Cloud API keys documentation.


Sign a private plugin
  1. Make sure that the first part of the plugin ID matches the slug of your Grafana Cloud account. You can find the plugin ID in the plugin.json file inside your plugin directory. For example, if your account slug is acmecorp, you need to prefix the plugin ID with acmecorp-.
  2. In your plugin directory, sign the plugin with the API key you just created. Grafana Sign Plugin creates a MANIFEST.txt file in the dist directory of your plugin.
    $ npx @grafana/sign-plugin@latest --rootUrls "http://localhost:3000"

For more information on signing a private plugin, see the Sign a private plugin documentation.


Push your plugin to GitHub

  1. First, make sure you have Git installed on your Ubuntu machine. If not, install it with:
    $ sudo apt update
    $ sudo apt install git
  2. Set your GitHub username and email if you haven’t already:
    $ git config --global "Your Name"
    $ git config --global ""
  3. Change to your plugin directory (my-plugin):
    $ cd org-my-plugin-panel
  4. Create the org-my-plugin-panel folder in your local repository:
    $ mkdir org-my-plugin-panel
  5. Open the .gitignore file with the following command:
    $ nano .gitignore
  6. In the file (.gitignore), delete the following line, save and close the file.
  7. Copy the required files (dist,,, and LICENSE) to the new folder:
    $ cp -r dist org-my-plugin-panel/
    $ cp org-my-plugin-panel/
    $ cp org-my-plugin-panel/
    $ cp LICENSE org-my-plugin-panel/
  8. Initialize a Git repository in your plugin directory:
    $ git init
  9. Add the changes to the staging area:
    $ git add org-my-plugin-panel
  10. Create the release commit:
    $ git commit -m "Release v1.0.0"
  11. Go to GitHub and create a new repository (you can do this by clicking the “+” icon in the upper-right corner and selecting “New repository”). Give it a name and choose the desired visibility (public or private).
  12. Copy the URL of your new GitHub repository.
  13. Add your GitHub repository as a remote in your local Git repository (Replace with the URL you copied in step 12.):
    $ git remote add origin
  14. Push to GitHub:
    $ git branch -M main
    $ git push -u origin main


Import your plugin from Github to your Grafana

  1. If you followed the Telegraf and InfluxDB and Grafana tutorial, clone your plugin’s GitHub repository to your folder my-grafana-plugins:
    $ git clone my-grafana-plugins
  2. You need to restart the grafana container:
    $ sudo docker restart grafana
  3. Now you can visit your Grafana web application from a browser in your internal network at http://IP_AddressOfServer:3000 (for example
  4. Log in with your username and password.
  5. Click on Configuration -> Plugins. Make sure that your plugin is there.
  6. Click on Dashboards -> Browse -> Your dashboard.
  7. Choose Add a new panel.
  8. Choose your plugin panel and save dashboard.


Import LoRa@VSB’s plugin from Github to your Grafana

  1. If you followed the Telegraf and InfluxDB and Grafana tutorial, clone our plugin’s GitHub repository to your folder my-grafana-plugins:
    $ git clone my-grafana-plugins
  2. You need to restart the grafana container:
    $ sudo docker restart grafana
  3. Now you can visit your Grafana web application from a browser in your internal network at http://IP_AddressOfServer:3000 (for example
  4. Log in with your username and password.
  5. Click on Configuration -> Plugins. Make sure that your plugin is there.
  6. Click on Dashboards -> Browse -> Your dashboard.
  7. Choose Add a new panel.
  8. Choose your plugin panel and save dashboard.


Plugin User Guide

Panel – TTS settings

  1. First, enter the URL of the TTN server (TTN Network cluster).
  2. Next, enter the API key, which you create according to the instructions below – “TTS API key”.
  3. Then fill in the Application name. The Application name is in TTS -> Applications -> “YourAppName”.
  4. Then fill in the End device name. End device name is in TTS -> Applications -> YourAppName -> “YourEndDeviceName”.
  5. If you want to save these settings, press the Save button and then save the Dashboard. (If you do not save the settings in the described way, the original settings will be applied after reloading the page in the browser.).


  1. In TTS -> Applications -> YourAppName -> API keys click button +Add API key.API key
  2. Write something in the Name.
  3. Select Grant individual rights.
  4. Check Write downlink application traffic.
  5. Click on button Create API key.API key
  6. Click the Copy to clipboard button and paste the key into the plugin panel to API key entry.


Panel – Downlink

  1. Select the desired Downlink settings (FPort (1-223), Priority, Insert mode and downlink confirmation) – These settings are the same as in TTS -> Applications -> YourAppName -> YourEndDeviceName -> Messaging -> Downlink.
  2. Then fill the Payload – Bytes entry with valid hexadecimal values.
  3. Use the Send button to send the downlink.


Panel – Send test downlink

  1. Fill in the required information according to the “Panel – TTS settings” and “Panel – Downlink” sections.
  2. In the Payload – Bytes entry, write 010001 (channel 01, type 00 (digital input), value 01).
  3. Click on button Send.Sending downlink from plugin
  4. In TTS -> Applications -> YourAppName -> YourEndDeviceName -> Live data you should see this:TTS received downlink
  5. After sending the uplink and receiving the downlink by Feather, you should see the following in Serial Monitor (Downlink reception and Cayenne LPP format decoding tutorial):