---
title: "Overview"
framework: react
version: "2.1.2"
---

# Overview

AG Studio is an embedded analytics component that lets analysts build interactive dashboards on top of your data. Add a single component to your application, configure your data source, and get drag-and-drop report building with advanced cross-filtering, powered by a built-in data engine.

Try in the example below:

- Use the application buttons to switch pages,
- Toggle between view and edit mode,
- When in edit mode, use the AG Studio UI to drag fields onto the canvas to build your own widgets.

Open the example in CodeSandbox or Plunker to see the code, or expand to full-screen to explore the report builder UI.

#### AG Studio Demo

```tsx
"use client";

import type {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgExpressionFieldDefinition,
  AgFieldDefinition,
  AgReportState,
  AgStudioApiReadyEvent,
  AgStudioMode,
} from "ag-studio";
import type { AgStudioRef } from "ag-studio-react";
import { AgStudio } from "ag-studio-react";
import React, {
  StrictMode,
  useCallback,
  useMemo,
  useRef,
  useState,
} from "react";
import { createRoot } from "react-dom/client";

const BASE_URL = "https://www.ag-grid.com/studio/example-assets/main-demo";

async function loadJson(filename: string): Promise<any[]> {
  const response = await fetch(`${BASE_URL}/${filename}`);
  if (!response.ok) {
    console.error(`Failed to load ${filename}: ${response.status}`);
    return [];
  }
  return response.json();
}

const productsFields: AgFieldDefinition[] = [
  { id: "product_id", name: "Product ID", format: "textFormat" },
  { id: "product_name", name: "Product", format: "textFormat" },
  { id: "category", name: "Category", format: "textFormat" },
  { id: "subcategory", name: "Subcategory", format: "textFormat" },
  { id: "brand", name: "Brand", format: "textFormat" },
  { id: "launch_date", name: "Launch Date", format: "dateFormat" },
  { id: "list_price", name: "List Price", format: "currencyFormat" },
  { id: "unit_cost", name: "Unit Cost", format: "currencyFormat" },
  { id: "is_discontinued", name: "Discontinued", format: "booleanFormat" },
];

const orderItemsFields: AgFieldDefinition[] = [
  { id: "order_item_id", name: "Order Item ID", format: "textFormat" },
  { id: "order_id", name: "Order ID", format: "textFormat" },
  { id: "product_id", name: "Product ID", format: "textFormat" },
  { id: "quantity", name: "Qty", format: "integerFormat" },
  { id: "unit_price", name: "Unit Price", format: "currencyFormat" },
  { id: "discount_pct", name: "Discount", format: "percentageFormat" },
  { id: "tax_rate", name: "Tax Rate", format: "percentageFormat" },
  { id: "returned", name: "Returned", format: "booleanFormat" },
  { id: "return_reason", name: "Return Reason", format: "textFormat" },
];

const customersFields: AgFieldDefinition[] = [
  { id: "customer_id", name: "Customer ID", format: "textFormat" },
  { id: "customer_name", name: "Customer", format: "textFormat" },
  { id: "region", name: "Region", format: "textFormat" },
  { id: "segment", name: "Segment", format: "textFormat" },
  { id: "industry", name: "Industry", format: "textFormat" },
];

const ordersFields: AgFieldDefinition[] = [
  { id: "order_id", name: "Order ID", format: "textFormat" },
  { id: "customer_id", name: "Customer ID", format: "textFormat" },
  { id: "order_datetime", name: "Order Date/Time", format: "dateTimeFormat" },
  { id: "channel", name: "Channel", format: "textFormat" },
  { id: "status", name: "Status", format: "textFormat" },
  { id: "payment_method", name: "Payment Method", format: "textFormat" },
  { id: "currency", name: "Currency", format: "textFormat" },
];

const expressions: AgExpressionFieldDefinition[] = [
  {
    id: "line_gross",
    name: "Line Gross",
    isMeasure: false,
    format: "currencyFormat",
    formatOptions: { format: "$#,##0.0,K" },
    expression: {
      operator: "multiply",
      inputs: [
        { id: "order_items.quantity" },
        { id: "order_items.unit_price" },
      ],
    },
  },
  {
    id: "margin",
    name: "Margin",
    isMeasure: false,
    format: "currencyFormat",
    expression: {
      operator: "subtract",
      inputs: [{ id: "products.list_price" }, { id: "order_items.unit_price" }],
    },
  },
  {
    id: "line_net",
    name: "Line Net",
    isMeasure: false,
    format: "currencyFormat",
    expression: {
      operator: "subtract",
      inputs: [
        {
          operator: "multiply",
          inputs: [
            { id: "order_items.quantity" },
            { id: "order_items.unit_price" },
          ],
        },
        {
          operator: "multiply",
          inputs: [
            {
              operator: "multiply",
              inputs: [
                { id: "order_items.quantity" },
                { id: "order_items.unit_price" },
              ],
            },
            { id: "order_items.discount_pct" },
          ],
        },
      ],
    },
  },
];

const StudioExample = () => {
  const studioRef = useRef<AgStudioRef>(null);
  const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
  const studioStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
  const [data, setData] = useState<AgDataSourcesDefinition | AgDataEngine>();
  const [mode, setMode] = useState<AgStudioMode>("edit");
  const initialState = useMemo<AgReportState>(
    () => ({
      pages: [
        {
          id: "overview",
          widgets: {
            "kpi-gross-sales": {
              type: "value",
              dataMapping: {
                value: [{ id: "line_gross", aggregation: "sum" }],
              },
              format: { caption: { enabled: true, text: "Gross Sales" } },
            },
            "kpi-order-count": {
              type: "value",
              dataMapping: {
                value: [{ id: "orders.order_id", aggregation: "countd" }],
              },
              format: { caption: { enabled: true, text: "Order Count" } },
            },
            "kpi-avg-qty": {
              type: "value",
              dataMapping: {
                value: [{ id: "order_items.quantity", aggregation: "avg" }],
              },
              format: { caption: { enabled: true, text: "Avg Qty per Line" } },
            },
            "sales-by-category": {
              type: "bar-chart-grouped",
              dataMapping: {
                categoryKey: [{ id: "products.subcategory" }],
                valueKey: [{ id: "line_gross", aggregation: "sum" }],
              },
              sort: [
                {
                  field: { id: "line_gross", aggregation: "sum" },
                  direction: "desc",
                },
              ],
              format: {
                title: { enabled: true, text: "Gross Sales by Subcategory" },
              },
            },
            "sales-by-brand": {
              type: "bar-chart-grouped",
              dataMapping: {
                categoryKey: [{ id: "products.brand" }],
                valueKey: [{ id: "line_gross", aggregation: "sum" }],
              },
              sort: [
                {
                  field: { id: "line_gross", aggregation: "sum" },
                  direction: "desc",
                },
              ],
              format: {
                title: { enabled: true, text: "Gross Sales by Brand" },
              },
            },
            "top-products": {
              type: "grid",
              dataMapping: {
                cols: [
                  { id: "products.product_name" },
                  { id: "products.category" },
                  { id: "order_items.quantity", aggregation: "sum" },
                  { id: "line_gross", aggregation: "sum" },
                  { id: "margin", aggregation: "avg" },
                ],
              },
              sort: [
                {
                  field: { id: "line_gross", aggregation: "sum" },
                  direction: "desc",
                },
              ],
              format: { title: { enabled: true, text: "Top Products" } },
            },
          },
          widgetLayout: {
            "kpi-gross-sales": { xTrack: 0, yTrack: 0, xSpan: 8, ySpan: 6 },
            "kpi-order-count": { xTrack: 8, yTrack: 0, xSpan: 8, ySpan: 6 },
            "kpi-avg-qty": { xTrack: 16, yTrack: 0, xSpan: 8, ySpan: 6 },
            "sales-by-category": { xTrack: 0, yTrack: 6, xSpan: 12, ySpan: 16 },
            "sales-by-brand": { xTrack: 12, yTrack: 6, xSpan: 12, ySpan: 16 },
            "top-products": { xTrack: 0, yTrack: 22, xSpan: 24, ySpan: 16 },
          },
        },
        {
          id: "detail",
          widgets: {
            "subcategory-filter": {
              type: "list-filter",
              dataMapping: { value: [{ id: "products.subcategory" }] },
              format: { title: { enabled: true, text: "Subcategory" } },
            },
            "order-grid": {
              type: "grid",
              dataMapping: {
                cols: [
                  { id: "products.product_name" },
                  { id: "products.subcategory" },
                  { id: "order_items.quantity" },
                  { id: "order_items.unit_price" },
                  { id: "line_gross" },
                  { id: "line_net" },
                ],
              },
              format: { title: { enabled: true, text: "Order Details" } },
            },
          },
          widgetLayout: {
            "subcategory-filter": { xTrack: 0, yTrack: 0, xSpan: 6, ySpan: 32 },
            "order-grid": { xTrack: 6, yTrack: 0, xSpan: 18, ySpan: 32 },
          },
        },
        {
          id: "customers",
          widgets: {
            "kpi-unique-customers": {
              type: "value",
              dataMapping: {
                value: [{ id: "customers.customer_id", aggregation: "countd" }],
              },
              format: { caption: { enabled: true, text: "Unique Customers" } },
            },
            "revenue-by-region": {
              type: "bar-chart-grouped",
              dataMapping: {
                categoryKey: [{ id: "customers.region" }],
                valueKey: [{ id: "line_net", aggregation: "sum" }],
              },
              sort: [
                {
                  field: { id: "line_net", aggregation: "sum" },
                  direction: "desc",
                },
              ],
              format: { title: { enabled: true, text: "Revenue by Region" } },
            },
            "customer-grid": {
              type: "grid",
              dataMapping: {
                cols: [
                  { id: "customers.customer_name" },
                  { id: "customers.region" },
                  { id: "customers.segment" },
                  { id: "customers.industry" },
                  { id: "orders.order_id", aggregation: "countd" },
                  { id: "line_net", aggregation: "sum" },
                ],
              },
              sort: [
                {
                  field: { id: "line_net", aggregation: "sum" },
                  direction: "desc",
                },
              ],
              format: { title: { enabled: true, text: "Customer Details" } },
            },
          },
          widgetLayout: {
            "kpi-unique-customers": {
              xTrack: 0,
              yTrack: 0,
              xSpan: 24,
              ySpan: 6,
            },
            "revenue-by-region": { xTrack: 0, yTrack: 6, xSpan: 24, ySpan: 16 },
            "customer-grid": { xTrack: 0, yTrack: 22, xSpan: 24, ySpan: 16 },
          },
        },
      ],
      selectedPageId: "overview",
      panels: {
        filters: {
          collapsed: true,
        },
        edit: {
          collapsed: true,
        },
        data: {
          collapsed: true,
        },
      },
    }),
    [],
  );

  const onApiReady = useCallback((params: AgStudioApiReadyEvent) => {
    Promise.all([
      loadJson("products.json"),
      loadJson("order_items.json"),
      loadJson("customers.json"),
      loadJson("orders.json"),
    ]).then(([productData, orderItemData, customerData, orderData]) => {
      setData({
        sources: [
          {
            id: "products",
            name: "Products",
            data: productData,
            fields: productsFields,
          },
          {
            id: "order_items",
            name: "Order Items",
            data: orderItemData,
            fields: orderItemsFields,
          },
          {
            id: "customers",
            name: "Customers",
            data: customerData,
            fields: customersFields,
          },
          {
            id: "orders",
            name: "Orders",
            data: orderData,
            fields: ordersFields,
          },
        ],
        relationships: [
          {
            id: "order-item-product",
            source: { tableId: "order_items", fieldId: "product_id" },
            target: { tableId: "products", fieldId: "product_id" },
            type: "many-to-one",
            acceptFanout: true,
          },
          {
            id: "order-item-order",
            source: { tableId: "order_items", fieldId: "order_id" },
            target: { tableId: "orders", fieldId: "order_id" },
            type: "many-to-one",
          },
          {
            id: "order-customer",
            source: { tableId: "orders", fieldId: "customer_id" },
            target: { tableId: "customers", fieldId: "customer_id" },
            type: "many-to-one",
          },
        ],
        expressions,
      });
    });
  }, []);

  const toggleMode = useCallback(() => {
    setMode((currentMode) => (currentMode === "edit" ? "view" : "edit"));
  }, []);

  const selectPage = useCallback((pageId: string) => {
    const state = studioRef.current!.api.getState();
    studioRef.current!.api.setState({ ...state, selectedPageId: pageId });
  }, []);

  return (
    <div style={containerStyle}>
      <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
        <div className="example-controls">
          <div
            className="controls-row"
            style={{ display: "flex", justifyContent: "space-between" }}
          >
            <div style={{ display: "flex", gap: "8px" }}>
              <button onClick={() => selectPage("overview")}>Overview</button>
              <button onClick={() => selectPage("detail")}>Detail</button>
              <button onClick={() => selectPage("customers")}>Customers</button>
            </div>
            <button onClick={toggleMode}>Toggle Edit Mode</button>
          </div>
        </div>

        <AgStudio
          ref={studioRef}
          style={studioStyle}
          className="my-studio-container"
          data={data}
          initialState={initialState}
          mode={mode}
          onApiReady={onApiReady}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <StudioExample />
  </StrictMode>,
);
```

[Live example: AG Studio Demo](https://www.ag-grid.com/studio/examples/overview/tutorial-example/reactFunctionalTs/)

> **Note**
>
> Read the [Building a Dashboard](https://www.ag-grid.com/studio/react/tutorial/) tutorial to learn how to build this example

## How It Works

AG Studio is designed to embed into your existing applications.

Your application owns the surrounding shell: routing, authentication, data fetching, persistence, whilst AG Studio handles everything inside the component boundary: the canvas, widgets, drag-and-drop, and filtering, all powered by a built-in data engine that runs entirely in the browser.

Once configured, analysts can use the drag-and-drop UI to create self-serve reports, and end users can then interact with those reports to interrogate their data with cross-filters and page-level filters.

You can ship pre-built reports in code so users see a finished dashboard on first load, or let analysts create reports from scratch using the drag-and-drop editor. The entire dashboard state is a single JSON object, so you can save and restore it later.

## Features

AG Studio is built on top of [AG Grid](https://www.ag-grid.com/) and [AG Charts](https://www.ag-grid.com/charts/), giving you access to tables, charts, gauges, and KPI tiles as widgets.

| Feature | Description |
| --- | --- |
| [Drag & Drop Report Builder](https://www.ag-grid.com/studio/react/modes-layout/) | Analysts design dashboards using a drag-and-drop canvas with charts, grids, KPI tiles, text, and images. Build multi-page reports in edit mode, then switch to view mode to lock the layout while keeping the dashboard fully interactive. |
| [Data Engine](https://www.ag-grid.com/studio/react/data/) | Provide one or more data sources and the data engine automatically manages joins across tables via relationships, aggregation, filtering, and calculated fields through an expression language. No query layer required. |
| [Advanced Filtering](https://www.ag-grid.com/studio/react/filters/) | Page-level filters, widget-level filters, and cross-filtering between widgets are all built in. Users can filter from the filters panel, from on-canvas filter widgets (list, button, and date), or by clicking data points in charts to cross-filter the rest of the page. |
| [Theming](https://www.ag-grid.com/studio/react/theming/) | Full customisation via the Theming API. Override colours, fonts, spacing, and borders across the Studio UI, grid widgets, and chart widgets independently. Light and dark modes are supported out of the box. |
| [State Management](https://www.ag-grid.com/studio/react/state/) | The entire dashboard state (pages, widgets, layout, and filters) is captured in a single serialisable JSON object. Save and restore state to persist reports, and let users switch between saved dashboards. |

## Get Started

**[Developers](https://www.ag-grid.com/studio/react/quick-start/)**

Embed AG Studio into your app.

**[Analysts](https://www.ag-grid.com/studio/react/user-interface/)**

Learn how to build self-serve reports.
