# Getting started

You can create and develop Pages in two ways:

1. **Web editor** — Build pages directly in the Ubidots UI, as described below.
2. **CLI** — Build pages locally with hot reload, then push to Ubidots when ready. Developing locally unlocks the power of your preferred IDE and AI-powered tools like Claude Code.

```bash
ubidots pages dev add --name my-page
ubidots pages dev start
```

{% hint style="info" %}
See the full [CLI reference for Pages](/sdks/cli/pages.md) for local development workflows, including push, pull, and dev server management.
{% endhint %}

## Using the Web editor

### Accessing the Pages module

Find the **Pages** module in the **Dev Center** section of the navbar:

<figure><img src="/files/hYDyfUGVWWMA5t6zcv9a" alt="" width="317"><figcaption></figcaption></figure>

## Creating a new Page

Click the **Create a new page** button.

<figure><img src="/files/cTiFq5lr9UQpa4wDUGu7" alt=""><figcaption></figcaption></figure>

## Page editor

The page editor interface includes:

1. **File Explorer**: Shows the list of files available for editing or for creating new ones.
2. **Editor Panel**: The main area for editing the selected file.
3. **Tab Bar**: Shows open tabs for files being edited and lets you switch between them.
4. **Page Name**: Shows the **Page** name.
5. **Page URL**: Shows the **Page** URL and includes a button to copy it.
6. **Preview Toggle**: Enables or disables the live preview of the **Page**.
7. **Dark Mode Toggle**: Switches the interface between light and dark themes.
8. **Save Button**: A button to save the changes.

<figure><img src="/files/Q84uPTJXWK64cFDwHuww" alt=""><figcaption></figcaption></figure>

## Quick example

Create a folder called `static` using the **+** button in the **File Explorer**:

| <img src="/files/5bphsEzY1PimXCfhB8rn" alt="" data-size="original"> | <img src="/files/2fbukrUFf6anmcUCuhkM" alt="" data-size="original"> |
| ------------------------------------------------------------------- | ------------------------------------------------------------------- |

Click the three-dot menu on the `static` folder, then create a file inside it called `data.js`.

| <img src="/files/NbvuYskf0YJHMPoCsBRY" alt="" data-size="original"> | <img src="/files/3YKQrSyAsV6lRUREDsGT" alt="" data-size="original"> |
| ------------------------------------------------------------------- | ------------------------------------------------------------------- |

Paste the following content into each corresponding file:

<details>

<summary>data.js</summary>

```javascript
const fullData = {
   "categories":[
      "Oct 24",
      "Nov 24",
      "Dec 24",
      "Jan 25"
   ],
   "series":[
      {
         "stack":"total",
         "name":"AD1",
         "data":[
            null,
            null,
            37.332,
            null
         ]
      },
      {
         "stack":"total",
         "name":"AD2",
         "data":[
            9.419,
            117.572,
            76.749,
            167.505
         ]
      },
      {
         "stack":"total",
         "name":"AD5",
         "data":[
            null,
            7.659000000000001,
            32.679,
            2.929
         ]
      },
      {
         "stack":"total",
         "name":"AD7",
         "data":[
            null,
            153.884,
            110.641,
            46.781
         ]
      },
      {
         "stack":"total",
         "name":"AV2",
         "data":[
            null,
            null,
            7.651,
            null
         ]
      },
      {
         "stack":"total",
         "name":"EN1",
         "data":[
            0.821,
            null,
            null,
            null
         ]
      },
      {
         "stack":"total",
         "name":"EP5",
         "data":[
            null,
            17.772,
            0.496,
            null
         ]
      },
      {
         "stack":"total",
         "name":"PP1",
         "data":[
            1.049,
            2.021,
            111.76,
            3.8449999999999998
         ]
      },
      {
         "stack":"total",
         "name":"PP3",
         "data":[
            1.229,
            null,
            7.91,
            null
         ]
      },
      {
         "stack":"total",
         "name":"PP4",
         "data":[
            null,
            24.323,
            32.785,
            75.607
         ]
      },
      {
         "stack":"total",
         "name":"PP5",
         "data":[
            null,
            12.861,
            null,
            null
         ]
      },
      {
         "stack":"total",
         "name":"PR1",
         "data":[
            10.114,
            5.126,
            null,
            null
         ]
      },
      {
         "stack":"total",
         "name":"PR2",
         "data":[
            null,
            8.873,
            null,
            8.853
         ]
      }
   ]
};

export default fullData ;
```

</details>

<details>

<summary>body.html</summary>

```html
<div id="app" class="container">
    <!-- First Chart Description -->
    <div id="chart1-info" class="content">
        <h2>Factory Production Summary</h2>
        <p>
            This chart shows the cumulative production output by each department over the last 24 hours. The data represents various production lines: AD2, AD5, AD7, PP1, PP4, and PR2.
        </p>
    </div>
    <div id="main" class="report-div"></div> <!-- First Chart -->

    <!-- Second Chart Description -->
    <div id="chart2-info" class="content">
        <h2>Factory Temperature and Environmental Monitoring</h2>
        <p>
            This chart displays temperature variations throughout the day across different monitoring points in the factory. Color bands represent specific ranges of temperatures critical for product safety.
        </p>
    </div>
    <div id="main2" class="report-div"></div> <!-- Second Chart -->
</div>
```

</details>

<details>

<summary>style.css</summary>

```css
* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

body {
    overflow: hidden; /* Prevent body from scrolling */
}

/* Overall container for charts and descriptions */
.container {
    overflow-y: auto; /* Allow vertical scrolling only on the #app container */
    height: 100vh; /* Ensure the container fills the entire viewport */
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-family: 'Nunito', sans-serif;
    font-size: 1rem;
    color: rgb(94, 94, 94); /* Text color */
}

/* Styling for description blocks */
.content {
    width: 100%;
    text-align: center; /* Center-align text */
    padding: 0; /* Add padding for spacing */
    background-color: white; /* Light background */
    border-bottom: none; /* Remove the subtle line */
}

/* Heading styling */
.content h2 {
    font-size: 20px; /* Larger font size for headings */
    font-weight: 700; /* Bold headings */
    margin: 0; /* Space below the heading */
    color: #2c3e50; /* Darker heading color */
    padding-top: 20px; /* Add padding for spacing */
    
}

/* Paragraph styling */
.content p {
    font-size: 14px; /* Slightly smaller text */
    font-weight: 400; /* Regular weight */
    margin: 0;
    line-height: 1.6; /* Improve readability */
    color: rgb(148, 148, 148); /* Light gray text */
    padding: 0; /* Add padding for spacing */
}

/* Styling for the chart containers */
.report-div {
    width: 100%; /* Full width */
    height: 50vh; /* Half of the viewport height for each chart */
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: white; /* White background for charts */
    border-bottom: none; /* Remove the subtle line */
}
```

</details>

<details>

<summary>script.js</summary>

```javascript
import fullData from './static/data.js';


// First chart setup
var chartDom1 = document.getElementById('main');
var myChart1 = echarts.init(chartDom1, null, {
  renderer: 'canvas',
  useDirtyRect: false
});

// Second chart setup
var chartDom2 = document.getElementById('main2');
var myChart2 = echarts.init(chartDom2, null, {
  renderer: 'canvas',
  useDirtyRect: false
});

const ubidots = new Ubidots();
const COLUMN_PREFIX = ""; 

ubidots.on("ready", () => {
    render();

    ubidots.on('dashboardRefreshed', function () {
        render();
    });
});

function render() {
    // First Chart (Original Data)
    let series1 = buildEchartsSeries(fullData.series);
    let options1 = setOptionsEcharts(series1, fullData.categories);
    myChart1.setOption(options1);

    // Second Chart (Desired Configuration)
    let series2 = buildEchartsSeriesForSecondChart();
    let options2 = setDesiredChartOptions(series2);
    myChart2.setOption(options2);
}

function buildEchartsSeries(series) {
    series.forEach(element => {
        element["type"] = "bar";
        if (COLUMN_PREFIX !== "") {
            element.stack = `${COLUMN_PREFIX} ${element.stack}`;
        }
        element["emphasis"] = {
            focus: "self"
        };
        let dataLength = element.data.length;
        let newData = Array(dataLength);
        for (let i = 0; i < dataLength; i++) {
            if (element.data[i] != null) {
                newData[i] = element.data[i];
            }
        }
        element.data = newData;
    });
    return series;
}

function buildEchartsSeriesForSecondChart() {
    const dummyData = generateDummyData();

    return [
        {
            name: "Sample Series",
            type: "line",
            data: dummyData,
            areaStyle: {},
            itemStyle: {
                color: "#5470C6",
                opacity: 0.8,
                borderColor: "#5470C6"
            },
            lineStyle: {
                width: 2
            },
            showSymbol: false,
            symbolSize: 8,
            connectNulls: true
        }
    ];
}

function setOptionsEcharts(series, categories) {
    return {
        tooltip: {
            trigger: 'axis',
            textStyle: {},
            axisPointer: {
                type: 'shadow' // 'shadow' as default; can also be 'line' or 'shadow'
            },
            formatter: function (params) {
                var tooltip = params[0].name + '<br>'; // Category name
                params.forEach(function (item) {
                    if (item.data != null) {
                        tooltip += item.marker + item.seriesName + ':   ' + `<b>${formatHours(item.data)}</b>` + '<br>';
                    }
                });
                return tooltip; // Return the customized tooltip content
            }
        },
        legend: {},
        grid: {
            left: '3%',
            right: '4%',
            bottom: '3%',
            containLabel: true
        },
        xAxis: {
            type: 'category',
            data: categories
        },
        yAxis: {
            type: 'value'
        },
        series: series
    };
}

function setDesiredChartOptions(series) {
    return {
        yAxis: [
            {
                type: "value",
                axisLine: {
                    lineStyle: {
                        color: "#5e5e5e40"
                    }
                },
                axisLabel: {
                    color: "#5E5E5E",
                    fontSize: 11,
                    show: false
                },
                splitLine: {
                    lineStyle: {
                        color: ["#5e5e5e"],
                        opacity: 0.1
                    }
                },
                min: "dataMin",
                max: "dataMax",
                name: "",
                nameLocation: "center",
                nameGap: 70,
                nameTextStyle: {
                    color: "#5E5E5E"
                },
                position: "left",
                show: true,
                offset: 0,
                lineStyle: {
                    color: "#5470C6",
                    width: 3
                }
            }
        ],
        xAxis: [
            {
                type: "time",
                axisLine: {
                    lineStyle: {
                        color: "#5e5e5e40"
                    }
                },
                axisLabel: {
                    color: "#5E5E5E",
                    fontSize: 11,
                    show: false
                },
                splitLine: {
                    lineStyle: {
                        color: ["#5e5e5e"],
                        opacity: 0.1
                    }
                }
            }
        ],
        dataZoom: [
            {
                show: true,
                filterMode: "none",
                xAxisIndex: 0,
                realtime: false,
                textStyle: {
                    color: "#5E5E5E",
                    fontSize: 11
                },
                dataBackground: {
                    areaStyle: {
                        color: "#5E5E5E"
                    }
                },
                right: 45,
                left: 45
            }
        ],
        series: series,
        tooltip: {
            confine: true,
            trigger: "axis"
        },
        visualMap: {
            show: false,
            type: "piecewise",
            pieces: [
                { gt: 0, lt: 18, color: "#1e13e8" },
                { gt: 18, lt: 22, color: "#e8dd13" },
                { gt: 22, lt: 26, color: "#e85a13" },
                { gt: 26, lt: 28, color: "#ed2005" },
                { gt: 28, color: "#ed2005" }
            ],
            dimension: 1,
            seriesIndex: 0
        }
    };
}

function generateDummyData() {
    const now = new Date();
    const data = [];
    for (let i = 0; i < 50; i++) {
        const timestamp = new Date(now.getTime() - i * 3600000); // Subtract i hours
        const value = Math.random() * 30; // Random values
        data.push([timestamp, value]); // Push [timestamp, value]
    }
    return data.reverse(); // Ensure chronological order
}

function formatHours(hours) {
    if (hours < 1) {
        return Math.floor(hours * 60) + 'm';
    }
    var duration = moment.duration(hours, 'hours');
    var months = Math.floor(duration.asMonths());
    duration.subtract(months, 'months');
    var days = Math.floor(duration.asDays());
    duration.subtract(days, 'days');
    var remainingHours = Math.floor(duration.asHours());
    duration.subtract(hours, 'hours');
    var minutes = Math.floor(duration.asMinutes());
    var result = '';
    if (months > 0) result += months + 'M ';
    if (days > 0) result += days + 'd ';
    if (remainingHours > 0) result += remainingHours + 'h ';
    if (minutes > 0) result += minutes + 'm';
    return result.trim();
}

window.addEventListener('resize', function () {
    myChart1.resize();
    myChart2.resize();
});
```

</details>

<details>

<summary>manifest.toml</summary>

```toml
[page]
keywords = "test,validation"
description = "Page for testing purposes."
static_paths = ["static"]
js_libraries = [
    {src="script.js", type="module", defer="", crossorigin="", referrerpolicy="no-referrer"}
    {src="static/data.js", type="module", defer="", crossorigin="", referrerpolicy="no-referrer"}
]
js_thirdparty_libraries = [
    {src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"},
    {src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"}
]

css_libraries = [{href="style.css", crossorigin="", referrerpolicy="no-referrer"}]
css_thirdparty_libraries = [
    {href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap", type="font", crossorigin=""}
]
link_thirdparty_libraries = [
    {href="https://fonts.googleapis.com/css?family=Roboto:310i,410&display=swap", type="font", crossorigin=""}
]
```

</details>

Then click **Save**. You will be prompted for a name:

<figure><img src="/files/WGTb8DNJuZgrnqMgVpbR" alt=""><figcaption></figcaption></figure>

After you name the page, you will be able to copy its URL:

<figure><img src="/files/1VMjfwSqz7TpWVRcvxbp" alt=""><figcaption></figcaption></figure>

Now go to the [Dashboards](https://industrial.ubidots.com/app/dashboards/) module and create a new dashboard to display the **Page** you just created. Once you create the dashboard, open its settings, change **Dashboard type** to **Default**, and paste the copied URL into the **Custom Page URL** field:

<figure><img src="/files/z8S6NNVDJR9CDxc78lek" alt="" width="563"><figcaption></figcaption></figure>

When you reload the dashboard, the page appears:

<figure><img src="/files/gFwWdK3Q0a78ETOA8Fps" alt=""><figcaption></figcaption></figure>

That’s it. You have developed your first Page. Head to the next section to learn more about Page development in Ubidots.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dev.ubidots.com/apps/pages/getting-started.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
