---
title: Learn data exploration and visualization with Kibana
description: Learn Kibana's core analyst features, including Discover, ES|QL, Lens, and Dashboards, by exploring data, building visualizations, and sharing a dashboard.
url: https://www.elastic.co/docs/explore-analyze/kibana-data-exploration-learning-tutorial
products:
  - Kibana
applies_to:
  - Elastic Cloud Serverless: Generally available
  - Elastic Stack: Generally available
---

# Learn data exploration and visualization with Kibana
Kibana provides powerful tools for exploring and visualizing data stored in Elasticsearch. **Discover** lets you search and filter documents with Elasticsearch Query Language (ES|QL), **Lens** transforms query results into charts, and **Dashboards** combine visualizations into shareable, interactive views. This tutorial teaches you how these core features work together by walking through a complete workflow, from querying data to sharing a finished dashboard.
You'll use Kibana's built-in sample web logs dataset so you can focus on learning the tools without needing to set up data ingestion. Basic familiarity with Elasticsearch concepts (indices, documents, fields) is helpful but not required.
These features are available across all Elastic solutions and project types, so what you learn here applies regardless of your use case.
By the end of this tutorial, you'll know how to:
- Search, filter, and aggregate data in **Discover** using ES|QL
- Create visualizations with **Lens**
- Combine panels into a **Dashboard** and customize the layout
- Navigate between **Discover**, **Lens**, and **Dashboards** to iterate on your analysis
- Share a dashboard with your team


## Before you begin

- An Elastic Stack deployment or Elastic Cloud Serverless project with Elasticsearch and Kibana. Don't have one yet? [Start a free trial](https://cloud.elastic.co/registration?page=docs&placement=docs-body).
- The required privileges to complete the tutorial. Specifically:
  - **Kibana privileges**: `All` on **Discover** and **Dashboard** (to explore data and create dashboards).
- **Elasticsearch index privileges**: `read` and `view_index_metadata` on the `kibana_sample_data_logs` index (to query the sample data in Discover).
  <note>
  If you created a trial account, you are the admin of your deployment and already have all the required privileges.
  </note>


## Step 1: Load sample data

Before you can explore and visualize, you need data in Elasticsearch. In this tutorial you use Kibana's built-in sample web logs dataset, which you can load in a few clicks. No agents or integrations required.
1. Open the **Integrations** page from the navigation menu or using the [global search field](https://www.elastic.co/docs/explore-analyze/find-and-organize/find-apps-and-objects).
2. In the list of integrations, select **Sample Data**.
3. On the page that opens, select **Other sample data sets**.
4. On the **Sample web logs** card, select **Add data**.

The sample data is loaded into the `kibana_sample_data_logs` index. It includes web server access logs with fields like `@timestamp`, `clientip`, `response`, `bytes`, `url`, `extension`, and `geo.src`.
<tip>
  When you're ready to explore your own data, refer to [Ingest data](https://www.elastic.co/docs/manage-data/ingest) for an overview of ingestion options, including Elastic Agent, Beats, and direct API uploads. Many [integrations](https://www.elastic.co/docs/reference/fleet/manage-integrations) also ship with pre-built dashboards, visualizations, anomaly detection jobs, and alerting rules, so you can start analyzing your data as soon as it's ingested.
</tip>


## Step 2: Explore data in Discover with ES|QL

**Discover** is the starting point for data exploration. You can search, filter, and visualize your data interactively.
Discover supports two exploration modes. This tutorial uses **ES|QL** (Elasticsearch Query Language), a piped query language that lets you chain operations like filtering, aggregating, and sorting in a single query. Unlike the default classic, KQL-based mode, ES|QL doesn't require you to set up a data view first: you query indices directly by name, so you can start exploring right away.
<stepper>
  <step title="Open Discover and switch to {{esql}}">
    1. From the navigation menu, go to **Discover**.
    2. Switch to ES|QL mode. You can do this from:
       - `code` **ES|QL** or **Try ES|QL** in the application menu.
    - <applies-to>Elastic Stack: Generally available since 9.4</applies-to> <applies-to>Elastic Cloud Serverless: Generally available</applies-to> **Switch to ES|QL** in the contextual menu (`boxes_vertical`) of the active Discover tab. This affects only that tab.
    **Result:** The query bar changes to an ES|QL editor where you can write piped queries.
  </step>

  <step title="Run your first query">
    Enter the following query, then select `playFilled` **Run** or **Search**. If you choose to type your own query, the editor helps you with relevant autocomplete suggestions for commands, fields, and values.
    ```esql
    FROM kibana_sample_data_logs 
    | KEEP @timestamp, clientip, response, message 
    | SORT @timestamp DESC 
    ```
    You can add more ES|QL commands and functions to control the results of the query. For example, a `| LIMIT` command to cap the number of rows returned (the default is 1,000). Refer to the [ES|QL reference](https://www.elastic.co/docs/reference/query-languages/esql/esql-syntax-reference) for the full list of commands.**Result:** The results table displays the most recent web log entries with only the fields you selected. To discover which fields are available, browse the field list in the sidebar.
    <tip>
      **No results?** The time range filter defaults to the last 15 minutes. Sample data timestamps are relative to when you loaded the dataset, so you may need to select a wider range, such as **Last 90 days**, or more, to see results.
    </tip>

    ![Discover showing an ES|QL query with results table and histogram](https://www.elastic.co/docs/explore-analyze/images/kibana-learning-tutorial-esql-first-query.png)
  </step>

  <step title="Inspect individual results and documents">
    The results table gives you an overview, but sometimes you need the full details of a single event. To inspect a document:
    1. Select the expand icon (`expand`) on any row in the results table. A flyout opens.
    2. The flyout shows the fields returned by your query in a detailed view. Use the **Table** tab to see field names and values, or the **JSON** tab to see the raw document.
    3. Use the navigation arrows at the top of the flyout to move between documents without closing it. This is useful when you need to compare consecutive events or trace a sequence.

    ![Document viewer flyout showing all fields for a single web log entry](https://www.elastic.co/docs/explore-analyze/images/kibana-learning-tutorial-document-viewer.png)
  </step>

  <step title="Filter and aggregate">
    Browsing individual events is useful, but you can also summarize data directly in ES|QL. In this step, you check which HTTP response codes appear in the logs and how frequently. Use `WHERE` to filter out rows with missing values and `STATS` to count events per response code:
    ```esql
    FROM kibana_sample_data_logs
    | WHERE response IS NOT NULL 
    | STATS event_count = COUNT(*) BY response 
    ```
    **Result:** The table shows the HTTP response codes ranked by frequency. A chart appears above the table to visualize the aggregation, so you can see at a glance how traffic breaks down by status (200, 404, 503, and so on). A three-line query turned thousands of raw log entries into a ranked breakdown with a chart. Notice that the field list in the sidebar now only shows the fields produced by the query (`event_count` and `response`), reflecting the narrower result set.
    ![Discover showing a STATS aggregation with HTTP response codes ranked by event count](https://www.elastic.co/docs/explore-analyze/images/kibana-learning-tutorial-esql-aggregation.png)
  </step>

  <step title="Save the visualization to a dashboard">
    The aggregation query produced a chart showing event counts by response code. You can save this chart directly to a dashboard:
    1. Select `app_dashboard` **Save visualization to dashboard** (or `save` **Save visualization** in earlier versions) next to the chart. You can also select `pencil` **Edit visualization** to open the Lens editor inline and customize the chart before saving it.
    2. Enter a title, for example `Events by response code`.
    3. Under **Add to dashboard**, select **New**.
    4. Select **Save and go to dashboard**.

    ![Save visualization dialog with "Add to dashboard" set to New](https://www.elastic.co/docs/explore-analyze/images/kibana-learning-tutorial-save-to-dashboard.png)
    **Result:** Kibana opens a new, unsaved dashboard with your response code chart already on it.
    <tip>
      Want to show the results table on a dashboard instead of the chart? Save your Discover session (select **Save** in the toolbar), then from your dashboard, import it from the library as a new panel. This embeds the table view, including the query and any filters you applied.
    </tip>
  </step>
</stepper>

You've queried, filtered, aggregated, and inspected data, all within Discover using ES|QL. You also saved a visualization to a new dashboard, which is where you're headed next. When you work with specific types of data, Discover adapts its interface accordingly. For example, it provides [specialized log exploration tools](https://www.elastic.co/docs/solutions/observability/logs/explore-logs) with built-in parsing and categorization when it detects log data. To learn more about Discover, refer to [Discover](https://www.elastic.co/docs/explore-analyze/discover). For the full ES|QL language reference, refer to [ES|QL](https://www.elastic.co/docs/reference/query-languages/esql/esql-syntax-reference).

## Step 3: Build your dashboard

Now that you have a dashboard with your first panel, add more visualizations to tell a complete story about your web traffic.
<stepper>
  <step title="Save the dashboard">
    Before adding more panels, save your dashboard so you don't lose your work:
    1. In the toolbar, select **Save**.
    2. Enter a title, for example `Web logs overview`.
    3. Select **Save**.
  </step>

  <step title="Add a metric panel for median response size">
    1. Create the visualization:
       - <applies-to>Elastic Cloud Serverless: Generally available</applies-to> <applies-to>Elastic Stack: Generally available since 9.2</applies-to> Select **Add** > **Visualization** in the toolbar.
    - <applies-to>Elastic Stack: Generally available from 9.0 to 9.1</applies-to> Select **Create visualization**.
    2. Once in the Lens editor, switch the visualization type to **Metric**.
    3. From the **Available fields** list on the left, drag **bytes** to the **Primary metric** area. Lens selects the **Median** aggregation automatically.
    4. Select the "Median of bytes" **Primary metric** that we just added, then go to the **Appearance** section and configure the following:
       - **Name**: `Median response size`
    - **Value format**: `Bytes (1024)`
    - **Background chart** (or **Supporting visualization**): `Line`. A sparkline appears behind the number, showing how the median changes over the selected time range.
    - **Color by value** (or **Color mode**): `Dynamic`. Set three color stops: green at `0`, yellow at `6000`, and red at `10000`. With these thresholds, the panel color reflects whether the median response size is small (under 6 KB), moderate, or large (over 10 KB).

    ![Lens editor showing a metric panel with median response size, background sparkline, and dynamic coloring](https://www.elastic.co/docs/explore-analyze/images/kibana-learning-tutorial-metric-lens.png)

    1. Select **Close**, then select **Save and return**.
    **Result:** A metric panel appears on the dashboard showing the median response size in a human-readable format (for example, 5.6 KB instead of 5,748), with a background sparkline for context.
    <dropdown title="Optional: add more metrics to build a row">
      A dashboard often starts with a row of metrics for key numbers at a glance. Using the same steps, you can create additional metrics such as:
      - **Unique visitors**: drag `clientip`. Lens picks **Unique count** for IP fields.
      - **Total requests**: drag `Records`. Lens creates a simple count. To add a trend indicator, drag `Records` again to the **Secondary metric** area and set its **Time shift** to `1d`. Then set **Color mode** to `Dynamic` and **Compare to** to `Primary metric` to show whether traffic is trending up or down. For details, refer to [Show trends in Metric charts](/docs/explore-analyze/visualize/charts/metric-charts#metric-trends).
      - **Unique URLs**: drag `request.keyword`. Lens picks **Unique count**, showing how many distinct pages were requested.
    </dropdown>
  </step>

  <step title="Add a line chart of log volume over time">
    1. Create the visualization:
       - <applies-to>Elastic Cloud Serverless: Generally available</applies-to> <applies-to>Elastic Stack: Generally available since 9.2</applies-to> Select **Add** > **Visualization** in the toolbar.
    - <applies-to>Elastic Stack: Generally available from 9.0 to 9.1</applies-to> Select **Create visualization**.
    2. Once in the Lens editor, switch the visualization type to **Line**.
    3. From the **Available fields** list, drag **Records** to the workspace.
       Because the data contains a time field, Lens places **@timestamp** on the horizontal axis and **Count of Records** on the vertical axis automatically.
    4. From the **Available fields** list, drag **host.keyword** to the **Breakdown** area. Lens draws one line per host, each in a different color, so you can compare traffic patterns across servers.
    5. Add a reference line to give the chart visual context:
       1. Select the **Add layer** icon `plus_in_square`, then select **Reference lines**.
    2. Select the reference line value and enter `80`. This marks a "high traffic" threshold on the chart.
    3. Set the color to red, then under **Text decoration**, enter a label such as `High traffic` and select **Fill below** to shade the area under the line.

    ![Lens editor showing a line chart of count of records over time with a reference line](https://www.elastic.co/docs/explore-analyze/images/kibana-learning-tutorial-line-chart-lens.png)

    1. Select **Save and return**.
    Add a panel title:
    1. Hover over the panel and select `gear` **Settings**.
    2. In the **Title** field, enter `Log volume over time per host`, then select **Apply**.

    <dropdown title="Optional: add more time series">
      To compare trends, create additional time series. For example, create a **Response size over time per host** panel: drag `bytes` to a new Line panel, then drag `host.keyword` to the **Breakdown** area to see how response sizes vary per host.
    </dropdown>
  </step>

  <step title="Add a bar chart of requests by file extension">
    1. Create the visualization:
       - <applies-to>Elastic Cloud Serverless: Generally available</applies-to> <applies-to>Elastic Stack: Generally available since 9.2</applies-to> Select **Add** > **Visualization** in the toolbar.
    - <applies-to>Elastic Stack: Generally available from 9.0 to 9.1</applies-to> Select **Create visualization**.
    2. Make sure the correct data view is selected (for example, `kibana_sample_data_logs`).
    3. From the **Available fields** list, drag **extension.keyword** to the workspace.
       Lens detects that this is a categorical field and creates a bar chart of its top values by count. It picks the chart type and axis configuration automatically.

    ![Lens editor showing a bar chart of top values of extension.keyword by count of records](https://www.elastic.co/docs/explore-analyze/images/kibana-learning-tutorial-bar-chart-lens.png)

    1. Select **Save and return**.
    Add a panel title:
    1. Hover over the panel and select `gear` **Settings**. The **Settings** flyout appears.
    2. In the **Title** field, enter `Requests by file extension`, then select **Apply**.
    **Result:** A bar chart appears on the dashboard showing the most common file extensions by request count.
  </step>

  <step title="Add a table of recent events with {{esql}}">
    You can also add panels powered by ES|QL queries directly from the dashboard. This is useful when you want to display raw events or run a specific query without going through Discover first.
    1. Add a new panel:
       - <applies-to>Elastic Cloud Serverless: Generally available</applies-to> <applies-to>Elastic Stack: Generally available since 9.2</applies-to> Select **Add** > **New panel** in the toolbar, then select **ES|QL** under **Visualizations**.
    - <applies-to>Elastic Stack: Generally available from 9.0 to 9.1</applies-to> Select **Add panel** in the toolbar, then select **ES|QL** under **Visualizations**.
    2. Enter the following query and run it:
       ```esql
       FROM kibana_sample_data_logs
       | KEEP @timestamp, request, response, bytes 
       | SORT @timestamp DESC 
       | LIMIT 100 
       ```
    3. In the visualization type dropdown, select **Table**.
    4. In the styling options, enable **Paginate table** so the panel stays compact on the dashboard while still giving access to all rows.

    ![ES|QL visualization editor showing a table of recent log events with the query and table configuration](https://www.elastic.co/docs/explore-analyze/images/kibana-learning-tutorial-esql-table.png)

    1. Select **Apply and close**.
    **Result:** Your dashboard now has at least five panels: the response code chart from Discover, the metric, the line chart, the bar chart, and the ES|QL table, plus any additional panels you may have created from the optional suggestions.
  </step>

  <step title="Expand your dashboard">
    Lens supports many visualization types beyond metrics, lines, bars, and tables. To keep building your dashboard, you can add panels such as:
    - A [**pie chart**](https://www.elastic.co/docs/explore-analyze/visualize/charts/pie-charts) of traffic distribution by operating system (`machine.os.keyword`).
    - A [**treemap**](https://www.elastic.co/docs/explore-analyze/visualize/charts/treemap-charts) breaking down requests by geography (`geo.dest`).
    Each one follows the same workflow you have used so far: create a visualization, pick a type, drag fields, and save.
  </step>

  <step title="Customize a panel with inline editing">
    You can fine-tune any Lens panel without leaving the dashboard. Try it on the **Requests by file extension** panel:
    1. Hover over the panel and select `pencil` **Edit visualization configuration**. A **Configuration** flyout opens on the right side of the panel.
    2. In the flyout, select the **Horizontal axis** configuration.
    3. Expand **Advanced**, then in the **Include values** field, enter `.+` and select **Use regular expression**. This regular expression matches any non-empty string, which filters out documents where the extension field is blank. The panel updates immediately to reflect the change.
    4. Select **Back**, then **Apply and close**.

    ![Dashboard panel with the inline Configuration flyout open on the right](https://www.elastic.co/docs/explore-analyze/images/kibana-learning-tutorial-inline-editing.png)

    <tip>
      For more advanced editing, select **Edit in Lens** in the inline editing flyout to open the full Lens editor. When you are done, select **Save and return** to go back to the dashboard.
    </tip>
  </step>

  <step title="Try interactive filtering">
    Dashboard panels are interactive. Try selecting the `404` bar in the **Events by response code** chart. Kibana adds a filter for that value, and the other panels update to show only the matching log events. The metric, bar chart, and line chart now reflect only the 404 traffic.To remove the filter, select the `cross` next to it in the dashboard's filter bar.
    <tip>
      If you know which dimensions your viewers will want to filter by, you can add [controls](https://www.elastic.co/docs/explore-analyze/dashboards/add-controls) (dropdown menus, range sliders) directly to the dashboard so they don't have to build those filters themselves.
    </tip>
  </step>

  <step title="Arrange and save">
    Drag panels by their header to reposition them, and drag the corner handles to resize them. A well-organized layout helps readers find what matters quickly. Aim for a compact, dense layout so the most important information is visible without scrolling:
    - **Top row:** place metric panels side by side for key numbers at a glance. Keep them short, about 5 grid rows, so they don't dominate the page.
    - **Middle rows:** arrange time series charts (line charts) and bar charts below the metrics. A moderate height (roughly 10–12 grid rows) gives charts enough room to be readable without wasting space.
    - **Bottom row:** use wider panels for tables that benefit from more horizontal space and can afford a taller height.
    To reduce clutter, consider hiding redundant axis titles. For example, on a bar chart the x-axis title may not add value when the panel title already describes the data. To hide it, edit the panel in Lens, open the `brush` **Style** panel, then under **Bottom axis**, set **Axis title** to **None**.
    ![A polished dashboard with metrics at the top, time series charts in the middle, and a bar chart and table at the bottom](https://www.elastic.co/docs/explore-analyze/images/kibana-learning-tutorial-dashboard-polished.png)

    <tip>
      For larger dashboards, you can also group panels into [collapsible sections](https://www.elastic.co/docs/explore-analyze/dashboards/arrange-panels) to keep things organized.
    </tip>
    When you are happy with the layout, select **Save** in the toolbar.
  </step>
</stepper>

Your dashboard now combines multiple panel types built with Lens, and you've seen how inline editing and interactive filtering make the dashboard both customizable and interactive. To learn more, refer to [Dashboards](https://www.elastic.co/docs/explore-analyze/dashboards), [Lens](https://www.elastic.co/docs/explore-analyze/visualize/lens), and [Panels and visualizations](https://www.elastic.co/docs/explore-analyze/visualize).

## Step 4: Share the dashboard

Once your dashboard is ready, share it with your team:
1. In the toolbar, select `share` **Share**.
2. [Copy the link](/docs/explore-analyze/report-and-share#share-a-direct-link) and share it with your team.

Users who receive the link need to authenticate and have the appropriate privileges to access the underlying data.
<applies-to>Elastic Cloud Serverless: Generally available</applies-to> <applies-to>Elastic Stack: Generally available since 9.3</applies-to> From the same **Share** menu, you can also set whether other users in the space can edit or only view the dashboard. Users with view-only access can still duplicate it to create their own version.
For more details on sharing options, access control, and managing dashboard ownership, refer to [Sharing dashboards](https://www.elastic.co/docs/explore-analyze/dashboards/sharing).

## Recreate the dashboard with the API

<applies-to>
  - Elastic Cloud Serverless: Preview
  - Elastic Stack: Preview since 9.4
</applies-to>

Everything you built in this tutorial can also be reproduced in a single API call. The [Dashboards API](https://www.elastic.co/docs/explore-analyze/dashboards/create-dashboards-programmatically) accepts a JSON payload that encodes the complete dashboard, including panel types, data sources, layout, and display options, making it straightforward to version-control dashboards or provision consistent environments programmatically.
<dropdown title="Recreate this dashboard with one API call">
  The following request creates the same dashboard as the one you built in this tutorial, including the optional panels suggestions.
  <tab-set>
    <tab-item title="Console">
      ```json

      {
        "title": "Web logs overview",
        "description": "Recreates the dashboard built in the Kibana data exploration tutorial.",
        "time_range": { "from": "now-90d", "to": "now" },
        "panels": [
          {
            "grid": { "x": 0, "y": 0, "w": 12, "h": 5 },
            "type": "vis",
            "config": {
              "type": "metric",
              "data_source": {
                "type": "data_view_spec",
                "index_pattern": "kibana_sample_data_logs",
                "time_field": "timestamp"
              },
              "metrics": [
                {
                  "type": "primary",
                  "operation": "median",
                  "field": "bytes",
                  "label": "Median response size", <1>
                  "format": { "type": "bytes", "decimals": 2 },
                  "background_chart": { "type": "trend" },
                  "color": {
                    "type": "dynamic",
                    "range": "absolute",
                    "steps": [
                      { "lt": 6000, "color": "#24c292" },
                      { "gte": 6000, "lt": 10000, "color": "#fcd883" },
                      { "gte": 10000, "color": "#f6726a" }
                    ]
                  },
                  "apply_color_to": "background"
                }
              ]
            }
          },
          {
            "grid": { "x": 12, "y": 0, "w": 12, "h": 5 },
            "type": "vis",
            "config": {
              "type": "metric",
              "data_source": {
                "type": "data_view_spec",
                "index_pattern": "kibana_sample_data_logs",
                "time_field": "timestamp"
              },
              "metrics": [
                {
                  "type": "primary",
                  "operation": "unique_count",
                  "field": "clientip",
                  "label": "Unique visitors" <2>
                }
              ]
            }
          },
          {
            "grid": { "x": 24, "y": 0, "w": 12, "h": 5 },
            "type": "vis",
            "config": {
              "type": "metric",
              "data_source": {
                "type": "data_view_spec",
                "index_pattern": "kibana_sample_data_logs",
                "time_field": "timestamp"
              },
              "metrics": [
                {
                  "type": "primary",
                  "operation": "count",
                  "label": "Total requests" <3>
                },
                {
                  "type": "secondary",
                  "operation": "count",
                  "label": "Week to week",
                  "time_shift": "1w",
                  "compare": { "to": "primary", "palette": "compare_to", "icon": true, "value": true }
                }
              ]
            }
          },
          {
            "grid": { "x": 36, "y": 0, "w": 12, "h": 5 },
            "type": "vis",
            "config": {
              "type": "metric",
              "data_source": {
                "type": "data_view_spec",
                "index_pattern": "kibana_sample_data_logs",
                "time_field": "timestamp"
              },
              "metrics": [
                {
                  "type": "primary",
                  "operation": "unique_count",
                  "field": "request.keyword",
                  "label": "Unique URLs" <4>
                }
              ]
            }
          },
          {
            "grid": { "x": 0, "y": 5, "w": 24, "h": 10 },
            "type": "vis",
            "config": {
              "type": "xy",
              "title": "Response size over time, per host", <5>
              "layers": [
                {
                  "type": "line",
                  "data_source": {
                    "type": "data_view_spec",
                    "index_pattern": "kibana_sample_data_logs",
                    "time_field": "timestamp"
                  },
                  "x": { "operation": "date_histogram", "field": "timestamp" },
                  "y": [{ "operation": "median", "field": "bytes" }],
                  "breakdown_by": {
                    "operation": "terms",
                    "fields": ["host.keyword"],
                    "limit": 9
                  }
                }
              ]
            }
          },
          {
            "grid": { "x": 24, "y": 5, "w": 24, "h": 10 },
            "type": "vis",
            "config": {
              "type": "xy",
              "title": "Log volume over time, per host", <6>
              "layers": [
                {
                  "type": "line",
                  "data_source": {
                    "type": "data_view_spec",
                    "index_pattern": "kibana_sample_data_logs",
                    "time_field": "timestamp"
                  },
                  "x": { "operation": "date_histogram", "field": "timestamp" },
                  "y": [{ "operation": "count" }],
                  "breakdown_by": {
                    "operation": "terms",
                    "fields": ["host.keyword"],
                    "limit": 9
                  }
                },
                {
                  "type": "reference_lines",
                  "data_source": {
                    "type": "data_view_spec",
                    "index_pattern": "kibana_sample_data_logs",
                    "time_field": "timestamp"
                  },
                  "thresholds": [
                    {
                      "operation": "static_value",
                      "value": 80,
                      "label": "High traffic",
                      "color": { "type": "static", "color": "#f6726a" },
                      "icon": "alert"
                    }
                  ]
                }
              ]
            }
          },
          {
            "grid": { "x": 0, "y": 15, "w": 24, "h": 10 },
            "type": "vis",
            "config": {
              "type": "xy",
              "title": "Events by response code", <7>
              "layers": [
                {
                  "type": "bar_stacked",
                  "data_source": {
                    "type": "esql",
                    "query": "FROM kibana_sample_data_logs | WHERE response IS NOT NULL | STATS event_count = COUNT(*) BY response | SORT event_count DESC | LIMIT 50"
                  },
                  "x": { "column": "response" },
                  "y": [{ "column": "event_count" }]
                }
              ]
            }
          },
          {
            "grid": { "x": 24, "y": 15, "w": 24, "h": 10 },
            "type": "vis",
            "config": {
              "type": "xy",
              "title": "Requests by file extension", <8>
              "layers": [
                {
                  "type": "bar_stacked",
                  "data_source": {
                    "type": "data_view_spec",
                    "index_pattern": "kibana_sample_data_logs",
                    "time_field": "timestamp"
                  },
                  "x": {
                    "operation": "terms",
                    "fields": ["extension.keyword"],
                    "limit": 9,
                    "includes": { "as_regex": true, "values": [".+"] }
                  },
                  "y": [{ "operation": "count" }]
                }
              ]
            }
          },
          {
            "grid": { "x": 0, "y": 25, "w": 24, "h": 10 },
            "type": "vis",
            "config": {
              "type": "pie",
              "title": "Traffic distribution by operating system", <9>
              "data_source": {
                "type": "data_view_spec",
                "index_pattern": "kibana_sample_data_logs",
                "time_field": "timestamp"
              },
              "metrics": [{ "operation": "count" }],
              "group_by": [
                {
                  "operation": "terms",
                  "fields": ["machine.os.keyword"],
                  "limit": 9
                }
              ]
            }
          },
          {
            "grid": { "x": 24, "y": 25, "w": 24, "h": 10 },
            "type": "vis",
            "config": {
              "type": "treemap",
              "title": "Requests by geography", <10>
              "data_source": {
                "type": "data_view_spec",
                "index_pattern": "kibana_sample_data_logs",
                "time_field": "timestamp"
              },
              "metrics": [{ "operation": "count" }],
              "group_by": [
                {
                  "operation": "terms",
                  "fields": ["geo.dest"],
                  "limit": 9
                }
              ]
            }
          },
          {
            "grid": { "x": 0, "y": 35, "w": 48, "h": 17 },
            "type": "vis",
            "config": {
              "type": "data_table",
              "title": "Last 100 events", <11>
              "data_source": {
                "type": "esql",
                "query": "FROM kibana_sample_data_logs | KEEP @timestamp, request, response, bytes | SORT @timestamp DESC | LIMIT 100"
              },
              "rows": [
                { "column": "@timestamp" },
                { "column": "request" },
                { "column": "response" }
              ],
              "metrics": [
                { "column": "bytes" }
              ]
            }
          }
        ]
      }
      ```
    </tab-item>

    <tab-item title="curl">
      ```bash
      curl -X POST "${KIBANA_URL}/api/dashboards" \
        -H "Authorization: ApiKey ${API_KEY}" \
        -H "kbn-xsrf: true" \
        -H "Content-Type: application/json" \
        -d '{
        "title": "Web logs overview",
        "description": "Recreates the dashboard built in the Kibana data exploration tutorial.",
        "time_range": { "from": "now-90d", "to": "now" },
        "panels": [
          {
            "grid": { "x": 0, "y": 0, "w": 12, "h": 5 },
            "type": "vis",
            "config": {
              "type": "metric",
              "data_source": {
                "type": "data_view_spec",
                "index_pattern": "kibana_sample_data_logs",
                "time_field": "timestamp"
              },
              "metrics": [
                {
                  "type": "primary",
                  "operation": "median",
                  "field": "bytes",
                  "label": "Median response size", 
                  "format": { "type": "bytes", "decimals": 2 },
                  "background_chart": { "type": "trend" },
                  "color": {
                    "type": "dynamic",
                    "range": "absolute",
                    "steps": [
                      { "lt": 6000, "color": "#24c292" },
                      { "gte": 6000, "lt": 10000, "color": "#fcd883" },
                      { "gte": 10000, "color": "#f6726a" }
                    ]
                  },
                  "apply_color_to": "background"
                }
              ]
            }
          },
          {
            "grid": { "x": 12, "y": 0, "w": 12, "h": 5 },
            "type": "vis",
            "config": {
              "type": "metric",
              "data_source": {
                "type": "data_view_spec",
                "index_pattern": "kibana_sample_data_logs",
                "time_field": "timestamp"
              },
              "metrics": [
                {
                  "type": "primary",
                  "operation": "unique_count",
                  "field": "clientip",
                  "label": "Unique visitors" 
                }
              ]
            }
          },
          {
            "grid": { "x": 24, "y": 0, "w": 12, "h": 5 },
            "type": "vis",
            "config": {
              "type": "metric",
              "data_source": {
                "type": "data_view_spec",
                "index_pattern": "kibana_sample_data_logs",
                "time_field": "timestamp"
              },
              "metrics": [
                {
                  "type": "primary",
                  "operation": "count",
                  "label": "Total requests" 
                },
                {
                  "type": "secondary",
                  "operation": "count",
                  "label": "Week to week",
                  "time_shift": "1w",
                  "compare": { "to": "primary", "palette": "compare_to", "icon": true, "value": true }
                }
              ]
            }
          },
          {
            "grid": { "x": 36, "y": 0, "w": 12, "h": 5 },
            "type": "vis",
            "config": {
              "type": "metric",
              "data_source": {
                "type": "data_view_spec",
                "index_pattern": "kibana_sample_data_logs",
                "time_field": "timestamp"
              },
              "metrics": [
                {
                  "type": "primary",
                  "operation": "unique_count",
                  "field": "request.keyword",
                  "label": "Unique URLs" 
                }
              ]
            }
          },
          {
            "grid": { "x": 0, "y": 5, "w": 24, "h": 10 },
            "type": "vis",
            "config": {
              "type": "xy",
              "title": "Response size over time, per host", 
              "layers": [
                {
                  "type": "line",
                  "data_source": {
                    "type": "data_view_spec",
                    "index_pattern": "kibana_sample_data_logs",
                    "time_field": "timestamp"
                  },
                  "x": { "operation": "date_histogram", "field": "timestamp" },
                  "y": [{ "operation": "median", "field": "bytes" }],
                  "breakdown_by": {
                    "operation": "terms",
                    "fields": ["host.keyword"],
                    "limit": 9
                  }
                }
              ]
            }
          },
          {
            "grid": { "x": 24, "y": 5, "w": 24, "h": 10 },
            "type": "vis",
            "config": {
              "type": "xy",
              "title": "Log volume over time, per host", 
              "layers": [
                {
                  "type": "line",
                  "data_source": {
                    "type": "data_view_spec",
                    "index_pattern": "kibana_sample_data_logs",
                    "time_field": "timestamp"
                  },
                  "x": { "operation": "date_histogram", "field": "timestamp" },
                  "y": [{ "operation": "count" }],
                  "breakdown_by": {
                    "operation": "terms",
                    "fields": ["host.keyword"],
                    "limit": 9
                  }
                },
                {
                  "type": "reference_lines",
                  "data_source": {
                    "type": "data_view_spec",
                    "index_pattern": "kibana_sample_data_logs",
                    "time_field": "timestamp"
                  },
                  "thresholds": [
                    {
                      "operation": "static_value",
                      "value": 80,
                      "label": "High traffic",
                      "color": { "type": "static", "color": "#f6726a" },
                      "icon": "alert"
                    }
                  ]
                }
              ]
            }
          },
          {
            "grid": { "x": 0, "y": 15, "w": 24, "h": 10 },
            "type": "vis",
            "config": {
              "type": "xy",
              "title": "Events by response code", 
              "layers": [
                {
                  "type": "bar_stacked",
                  "data_source": {
                    "type": "esql",
                    "query": "FROM kibana_sample_data_logs | WHERE response IS NOT NULL | STATS event_count = COUNT(*) BY response | SORT event_count DESC | LIMIT 50"
                  },
                  "x": { "column": "response" },
                  "y": [{ "column": "event_count" }]
                }
              ]
            }
          },
          {
            "grid": { "x": 24, "y": 15, "w": 24, "h": 10 },
            "type": "vis",
            "config": {
              "type": "xy",
              "title": "Requests by file extension", 
              "layers": [
                {
                  "type": "bar_stacked",
                  "data_source": {
                    "type": "data_view_spec",
                    "index_pattern": "kibana_sample_data_logs",
                    "time_field": "timestamp"
                  },
                  "x": {
                    "operation": "terms",
                    "fields": ["extension.keyword"],
                    "limit": 9,
                    "includes": { "as_regex": true, "values": [".+"] }
                  },
                  "y": [{ "operation": "count" }]
                }
              ]
            }
          },
          {
            "grid": { "x": 0, "y": 25, "w": 24, "h": 10 },
            "type": "vis",
            "config": {
              "type": "pie",
              "title": "Traffic distribution by operating system", 
              "data_source": {
                "type": "data_view_spec",
                "index_pattern": "kibana_sample_data_logs",
                "time_field": "timestamp"
              },
              "metrics": [{ "operation": "count" }],
              "group_by": [
                {
                  "operation": "terms",
                  "fields": ["machine.os.keyword"],
                  "limit": 9
                }
              ]
            }
          },
          {
            "grid": { "x": 24, "y": 25, "w": 24, "h": 10 },
            "type": "vis",
            "config": {
              "type": "treemap",
              "title": "Requests by geography", 
              "data_source": {
                "type": "data_view_spec",
                "index_pattern": "kibana_sample_data_logs",
                "time_field": "timestamp"
              },
              "metrics": [{ "operation": "count" }],
              "group_by": [
                {
                  "operation": "terms",
                  "fields": ["geo.dest"],
                  "limit": 9
                }
              ]
            }
          },
          {
            "grid": { "x": 0, "y": 35, "w": 48, "h": 17 },
            "type": "vis",
            "config": {
              "type": "data_table",
              "title": "Last 100 events", 
              "data_source": {
                "type": "esql",
                "query": "FROM kibana_sample_data_logs | KEEP @timestamp, request, response, bytes | SORT @timestamp DESC | LIMIT 100"
              },
              "rows": [
                { "column": "@timestamp" },
                { "column": "request" },
                { "column": "response" }
              ],
              "metrics": [
                { "column": "bytes" }
              ]
            }
          }
        ]
      }'
      ```
    </tab-item>
  </tab-set>
  For the full request schema, including sections, filter controls, and library-linked panels, refer to the [Dashboards API reference](https://elastic.github.io/dashboards-api-spec/dashboards#tag/Dashboards/operation/post-dashboards).
</dropdown>


## Navigate between Discover and dashboards

One of Kibana's strengths is how you can move between exploring raw data and visualizing it. Here are the key navigation paths:
<definitions>
  <definition term="From Discover to a dashboard">
    When a classic search or an ES|QL aggregation produces a chart in Discover, select `app_dashboard` **Save visualization to dashboard** (or `save` **Save visualization** in earlier versions) next to the chart, then choose **Add to dashboard** to send it to an existing or new dashboard. You can also save the current view of the table to a dashboard from the Discover table toolbar, or save the entire Discover session to the library and add it as a panel from a dashboard.
  </definition>
  <definition term="From a dashboard panel back to Discover">
    Open the context menu on any Lens panel and select **Explore in Discover**. Kibana opens Discover with the panel's query and filters already applied, so you can drill into the underlying data.
  </definition>
  <definition term="Inline and full Lens editing from a dashboard">
    Select `pencil` on any panel to open the inline **Configuration** flyout. For deeper changes, select **Edit in Lens** in the flyout to switch to the full editor, then **Save and return** to go back to the dashboard.
  </definition>
  <definition term="Add a new visualization directly from a dashboard">
    From a dashboard, select **Add** > **Visualization** to open the Lens editor, or **Add** > **New panel** and then **ES|QL** under **Visualizations** to create a chart from an ES|QL query without going through Discover first.
  </definition>
</definitions>

<tip>
  This back-and-forth workflow is especially useful when investigating anomalies: spot something unusual on a dashboard, jump to Discover to examine the raw events, refine your query, then save an updated visualization back to the dashboard.
</tip>


## Next steps

You've completed the core workflow, from sample data to a shareable dashboard. Here are some directions to explore next:
**Bring in your own data**
: The same workflow applies to any data in Elasticsearch. Use [Elastic Agent](https://www.elastic.co/docs/reference/fleet/install-elastic-agents) to ingest your own logs, metrics, or traces. Refer to [Ingest data](https://www.elastic.co/docs/manage-data/ingest) for an overview of all ingestion options.
**Deepen your ES|QL knowledge**
: ES|QL supports advanced operations like `ENRICH`, `LOOKUP JOIN`, `DISSECT`, and `GROK`, and more to transform your data on the fly. Refer to the [ES|QL reference](https://www.elastic.co/docs/reference/query-languages/esql/esql-syntax-reference) and [Use ES|QL in Kibana](https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql-kibana).
**Explore different types of data**
: Depending on what you monitor, you can use specialized tools:
- **Logs**: [Explore logs in Discover](https://www.elastic.co/docs/solutions/observability/logs/explore-logs) with field-level filtering and log parsing.
- **Metrics**: [Get started with system metrics](https://www.elastic.co/docs/solutions/observability/infra-and-hosts/get-started-with-system-metrics) and the Infrastructure app.
- **Traces**: [Get started with APM](https://www.elastic.co/docs/solutions/observability/apm/get-started) to trace requests across distributed services.

**Try more visualization techniques**
: Build richer dashboards with the following step-by-step tutorials:
- [Dashboard with web server data](https://www.elastic.co/docs/explore-analyze/dashboards/create-dashboard-of-panels-with-web-server-data)
- [Dashboard with time series eCommerce data](https://www.elastic.co/docs/explore-analyze/dashboards/create-dashboard-of-panels-with-ecommerce-data)

**Add geographic context**
: The sample web logs data includes `geo.src` and `geo.dest` fields. [Maps](https://www.elastic.co/docs/explore-analyze/visualize/maps) lets you visualize this data on interactive maps and add them to dashboards.
**Set up alerts**
: Don't wait for problems to show up on a dashboard. [Create alerting rules](https://www.elastic.co/docs/explore-analyze/alerting/alerts/alerting-getting-started) to get notified when your data crosses a threshold.
**Try machine learning**
: Use machine learning to [detect anomalies](https://www.elastic.co/docs/explore-analyze/machine-learning/anomaly-detection/ml-getting-started) in time-series data, forecast trends, or categorize log messages. The sample data sets include pre-configured anomaly detection jobs you can experiment with.

## Related pages

- [Discover](https://www.elastic.co/docs/explore-analyze/discover)
- [Dashboards](https://www.elastic.co/docs/explore-analyze/dashboards)
- [Panels and visualizations](https://www.elastic.co/docs/explore-analyze/visualize)
- [Lens](https://www.elastic.co/docs/explore-analyze/visualize/lens)
- [ES|QL in Kibana](https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql-kibana)
- [Sharing dashboards](https://www.elastic.co/docs/explore-analyze/dashboards/sharing)