# 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](https://dev.ubidots.com/sdks/cli/pages) for local development workflows including push, pull, and dev server management.
{% endhint %}

## Using the Web editor

### Accessing the Pages module

Search the **Pages** module within the **Dev Center** section in the Navbar:

<figure><img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FmQJJqB5WY70UBEnwCY5s%2Fimage.png?alt=media&#x26;token=4cf37979-b7ca-4798-bdde-dee13a32c791" alt=""><figcaption></figcaption></figure>

## Creating a new Page

Click the **create a new page** button

<figure><img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FedxJs9kPLpUtfz8UB2xE%2Fimage.png?alt=media&#x26;token=c0985ae2-2dff-448b-9ea9-5bb6f1b36c52" alt=""><figcaption></figcaption></figure>

## Page editor

The page editor interface is composed of:

1. **File Explorer**: A section displaying the list of files available for editing or creating new ones.
2. **Editor Panel**: The main area for editing the content of the selected file.
3. **Tab Bar**: Displays open tabs for multiple files being edited, allowing switching between them.
4. **Page Name**: Field displaying the **Page** name
5. **Page URL**: A field displaying the **Page**'s url featuring a button for copying it.
6. **Preview Toggle**: A toggle to enable or disable the live preview of **Page.**
7. **Dark Mode Toggle**: A toggle to switch between light and dark themes for the interface.
8. **Save Button**: A button to save the changes.

<figure><img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FcsKbn3L4uCl1S99oSd1j%2Fimage.png?alt=media&#x26;token=2d8367f5-04be-4176-ad42-e882ba2f5630" alt=""><figcaption></figcaption></figure>

## Quick example

Proceed to create a folder called `static` using the + button at the File Explorer:

| <img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2Fg1v7F08SUC2IOSaAHIx5%2Fimage.png?alt=media&#x26;token=d65fcb05-3aa2-41f0-9df8-95c85e9d8691" alt="" data-size="original"> | <img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2F9LCPtD8F3UUXPpPgTo8l%2Fimage.png?alt=media&#x26;token=c057c9ed-f796-4fff-9087-2350e157cdb0" alt="" data-size="original"> |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Click the Three-Dots menu at the `static`folder and then create a file within it called `data.js`

| <img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2F7VXFJbdM84PULw7Osw4S%2Fimage.png?alt=media&#x26;token=0feb39e3-4d7b-4ff3-964b-cd034c4521eb" alt="" data-size="original"> | <img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FGHi0eguSP0POMvsVofyw%2Fimage.png?alt=media&#x26;token=e762ea83-37b1-4eba-9437-2e895345fc2b" alt="" data-size="original"> |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Paste the following contents on each of the corresponding files:

<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 the **Save** button to save the page. You'll be prompted for a name:

<figure><img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FgMuHw7VwJbTKD0YQawVV%2Fimage.png?alt=media&#x26;token=3d822c52-84de-4575-8b90-a8e2c7406f4b" alt=""><figcaption></figcaption></figure>

Upon giving a name to your page, you'll be able to copy its URL:

<figure><img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FJOfOZFLuOsCfmnnn95ew%2Fimage.png?alt=media&#x26;token=bf10738e-dd23-4a84-872d-3d12480ef016" alt=""><figcaption></figcaption></figure>

Now, go to the [Dashboards](https://industrial.ubidots.com/app/dashboards/) module and create a new dashboard to use it for displaying the recently created **Page.** Once you've created said dashboard, go to its settings and change **Dashboard type** to **Default** and then paste the previously copied URL in the **Custom Page URL** field:

<figure><img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FvKB0vUIPBt0fMFWeUWcQ%2Fimage.png?alt=media&#x26;token=3d6092e4-aefc-4552-8b26-44a90fa83a69" alt=""><figcaption></figcaption></figure>

When you reload the dashboard, the page created will be displayed:

<figure><img src="https://884329393-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MhzNRg0B4ECiNXc093G%2Fuploads%2FRwZGvSpQy1ZhKlXLiJ3x%2Fimage.png?alt=media&#x26;token=8c7c8a43-1d95-443d-af0c-21eadbc43f0f" alt=""><figcaption></figcaption></figure>

That's it! You've just developed your first Page. Head to the next section to learn more about developing Pages in Ubidots in greater detail:
