### Creating Platforms
DashMachine platforms are python plugins loaded by Python's `importlib` module. When they are loaded by DM, they are initialized with their `Platform` class. The official platforms for dashmachine are located at `DashMachine/dashmachine/platform` and are a great resource for learning how they work.
> **NOTE:** When you modify or add one of the files in `DashMachine/dashmachine/platform`, changes will be wiped out by updates. If you want to experiment with your own platforms, put them in `DashMachine/dashmachine/user_data/platform` to persist updates.
##### A minimal platform
Let's start by making a super simple platform. It's just going to return 'Hello World' to the card's data source container. First add a file called `hello_world.py` in `DashMachine/dashmachine/user_data/platform` with the contents:
```python
class Platform:
def docs(self):
# This is the function DM calls to get the metadata about your plaform.
# This is what DM uses to generate the documentation and options in the gui forms.
# It is very important that this information is correct.
documentation = {
"name": "hello_world",
"author": "RMountjoy",
"author_url": "https://github.com/rmountjoy92",
"version": 1.0,
"description": "return 'Hello World' to the card's data source container",
"returns": "Hello World",
"variables": [
{
"variable": "[variable_name]",
"description": "Name for the data source.",
"default": "",
"options": ".ini header",
},
{
"variable": "platform",
"description": "Name of the platform.",
"default": "hello_world",
"options": "hello_world",
},
],
}
return documentation
def __init__(self, *args, **kwargs):
# This is the initialization function.
# This should be reserved for setting up the platform's configuration.
# NOTE: this is not where you call APIs, as this code is run every time DM
# initializes your 'Platform' class (e.g. to pull documentation, etc).
# These following two lines parses the options supplied by the user in their config.ini
for key, value in kwargs.items():
self.__dict__[key] = value
def process(self):
# this is the function that is ran when the web client requests
# information from this platform (e.g. when the dashboard is loaded)
# this is where you call APIs, or whatever you're doing to get data.
return "Hello World"
```
**NOTE:** After you add a new platform file to your user_data/platforms folder, you will need to restart DashMachine for the platform to be available.
Then, we would add the following two entries to our config.ini **NOTE** All custom platforms must be prefixed with `custom_` when they are referenced in a `data_source` entry:
```ini
[hello_world_ds]
platform = custom_hello_world
[hello_world]
type = custom
data_sources = hello_world_ds
```
Now, on your dashboard, you should see this:

Ugly right!? Which leads us to the next section,
##### Rendering HTML
Let's make some changes to our `hello_world.py` file, at the top of the file we would add:
```python
from flask import render_template_string
```
then we would change our `process()` method like so:
```python
def process(self):
text_to_display = "Hello World"
html_string = """
language
{{ text_to_display }}
"""
return render_template_string(html_string, text_to_display=text_to_display)
```
Much better:

For a full reference of the html elements you have available to you, check out:
* [Materialize CSS](https://materializecss.com/)
* [Material Icons](https://material.io/resources/icons/?icon=settings&style=outline)
Remeber that your returned HTML can include `script` and `style` tags for including js/jquery/css
##### Utilizing user configuration
Okay, let's say that instead of displaying our fixed 'Hello World' message, we'll display the `text_to_display` variable from their configuration. So the entries in the .ini file would look like:
```ini
[hello_world_ds]
platform = custom_hello_world
text_to_display = Hi there.
[hello_world]
type = custom
data_sources = hello_world_ds
```
and in our `hello_world.py` file the only thing we would have to change is:
```python
return render_template_string(html_string, text_to_display=text_to_display)
```
changes to:
```python
return render_template_string(html_string, text_to_display=self.text_to_display)
```
So, now our card displays whatever `text_to_display` is set to:

**NOTE:** Now that we have added a configuration option, we need to update our `docs()` method, otherwise our new option will not show up in gui forms:
```python
def docs(self):
documentation = {
"name": "hello_world",
"author": "RMountjoy",
"author_url": "https://github.com/rmountjoy92",
"version": 1.0,
"description": "return the value of 'text_to_display' to the card's data source container",
"returns": "text_to_display as formatted html",
"variables": [
{
"variable": "[variable_name]",
"description": "Name for the data source.",
"default": "",
"options": ".ini header",
},
{
"variable": "platform",
"description": "Name of the platform.",
"default": "hello_world",
"options": "hello_world",
},
{
"variable": "text_to_display",
"description": "the text to display on the card",
"default": "Hello World",
"options": "string",
},
],
}
return documentation
```
You'll notice on the new variable we just made that there is a `default` field. We need to make sure we have defaults set up for our configuration options, so users don't have to fill out optional configurations. To do so, we would rewrite our `__init__()` method like so:
```python
def __init__(self, *args, **kwargs):
for key, value in kwargs.items():
self.__dict__[key] = value
if not hasattr(self, "text_to_display"):
self.text_to_display = "Hello World"
```
To sum it all up, here is our user configurable version of our hello world platform:
```python
from flask import render_template_string
class Platform:
def docs(self):
documentation = {
"name": "hello_world",
"author": "RMountjoy",
"author_url": "https://github.com/rmountjoy92",
"version": 1.0,
"description": "return the value of 'text_to_display' to the card's data source container",
"returns": "text_to_display as formatted html",
"variables": [
{
"variable": "[variable_name]",
"description": "Name for the data source.",
"default": "",
"options": ".ini header",
},
{
"variable": "platform",
"description": "Name of the platform.",
"default": "hello_world",
"options": "hello_world",
},
{
"variable": "text_to_display",
"description": "the text to display on the card",
"default": "Hello World",
"options": "string",
},
],
}
return documentation
def __init__(self, *args, **kwargs):
for key, value in kwargs.items():
self.__dict__[key] = value
if not hasattr(self, "text_to_display"):
self.text_to_display = "Hello World"
def process(self):
html_string = """
language
{{ text_to_display }}
"""
return render_template_string(html_string, text_to_display=self.text_to_display)
```
and in our config.ini:
```ini
[hello_world_ds]
platform = custom_hello_world
text_to_display = Hi there.
[hello_world]
type = custom
data_sources = hello_world_ds
```