---
title: "Overview"
framework: javascript
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

```ts
import {
  AgExpressionFieldDefinition,
  AgFieldDefinition,
  AgReportState,
  AgStudioApi,
  AgStudioProperties,
  createStudio,
} from "ag-studio";

// =============================================================================
// 1. Loading External Data
// =============================================================================
// Fetch JSON files from the server. The files contain native JSON types -
// numbers are numbers and booleans are booleans - so no extra parsing is needed.

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();
}

// =============================================================================
// 2. Field Definitions
// =============================================================================
// Field definitions tell Studio how to display and aggregate each column.
// Each field has an `id` matching a property in the row data, a display `name`,
// and a `format` that controls rendering (e.g. text, number, currency, boolean).

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" },
];

// Test Your Knowledge #1: Customer field definitions
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" },
];

// Test Your Knowledge #2: Order field definitions
const ordersFields: AgFieldDefinition[] = [
  {
    id: "order_id",
    name: "Order ID",
    format: "textFormat",
    cardinality: "high",
    notBlank: true,
  },
  { 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" },
];

// =============================================================================
// 3. Expressions (Calculated Fields)
// =============================================================================
// Expressions create derived columns computed at query time. They use operators
// like multiply, subtract, and divide, referencing fields via `sourceId.fieldId`.

const expressions: AgExpressionFieldDefinition[] = [
  // line_gross = quantity × unit_price
  {
    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" },
      ],
    },
  },
  // margin = list_price - unit_price (crosses tables via the relationship)
  {
    id: "margin",
    name: "Margin",
    isMeasure: false,
    format: "currencyFormat",
    expression: {
      operator: "subtract",
      inputs: [{ id: "products.list_price" }, { id: "order_items.unit_price" }],
    },
  },
  // Test Your Knowledge #3: line_net = (quantity × unit_price) - ((quantity × unit_price) × discount_pct)
  {
    id: "line_net",
    name: "Line Net",
    isMeasure: false,
    format: "currencyFormat",
    expression: {
      operator: "subtract",
      inputs: [
        // line_gross: quantity × unit_price
        {
          operator: "multiply",
          inputs: [
            { id: "order_items.quantity" },
            { id: "order_items.unit_price" },
          ],
        },
        // discount_amount: (quantity × unit_price) × discount_pct
        {
          operator: "multiply",
          inputs: [
            {
              operator: "multiply",
              inputs: [
                { id: "order_items.quantity" },
                { id: "order_items.unit_price" },
              ],
            },
            { id: "order_items.discount_pct" },
          ],
        },
      ],
    },
  },
];

// =============================================================================
// 4. Initial State (Pre-built Reports)
// =============================================================================
// The state is a serialisable snapshot of the entire dashboard. Define it in
// code to pre-build reports users see on load. Each page has widgets, a layout
// grid, and optional filters.

const initialState: AgReportState = {
  pages: [
    // -----------------------------------------------------------------
    // Page 1: Overview - KPIs and charts built from products + order items
    // -----------------------------------------------------------------
    {
      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" } },
        },
        // Bar chart: Gross Sales by product subcategory
        "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" },
          },
        },
        // Bar chart: Gross Sales by brand
        "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" } },
        },
        // Grid: top products by gross sales
        "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 },
      },
    },

    // -----------------------------------------------------------------
    // Page 2: Detail - subcategory filter driving a data grid
    // -----------------------------------------------------------------
    {
      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 },
      },
    },

    // -----------------------------------------------------------------
    // Test Your Knowledge #4: Customers page
    // KPI for unique customers, bar chart of revenue by region, and a
    // customer detail grid.
    // -----------------------------------------------------------------
    {
      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,
    },
  },
};

// =============================================================================
// 5. Controlling Modes & Navigating Pages
// =============================================================================
// The Studio API lets you toggle between edit and view mode at runtime, and
// navigate between pages by updating the selectedPageId in the state.

let studioApi: AgStudioApi;

// Switch the currently visible page
function selectPage(pageId: string) {
  const state = studioApi.getState();
  studioApi.setState({
    ...state,
    selectedPageId: pageId,
  });
}

// Toggle between edit (design-time) and view (presentation) mode
function toggleMode() {
  const currentMode = studioApi.getProperty("mode");
  studioApi.setProperty("mode", currentMode === "edit" ? "view" : "edit");
}

// Expose to HTML onclick handlers
(window as any).selectPage = selectPage;
(window as any).toggleMode = toggleMode;

// =============================================================================
// 6. Create the Studio
// =============================================================================

// Load all data files in parallel
const [productData, orderItemData, customerData, orderData] = await Promise.all(
  [
    loadJson("products.json"),
    loadJson("order_items.json"),
    loadJson("customers.json"),
    loadJson("orders.json"),
  ],
);

const studioProperties: AgStudioProperties = {
  mode: "edit",
  initialState,
  data: {
    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 link data sources together for cross-table queries
    relationships: [
      // Each order item refers to exactly one product. `margin` (list_price -
      // unit_price) reads from both sides, but every widget that averages it
      // groups by the full product identity (product_name + category), so the
      // aggregate is genuinely correct, not inflated.
      {
        id: "order-item-product",
        source: { tableId: "order_items", fieldId: "product_id" },
        target: { tableId: "products", fieldId: "product_id" },
        type: "many-to-one",
        acceptFanout: true,
      },
      // Test Your Knowledge #2: Link order items → orders → customers
      {
        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",
      },
    ],
    // Calculated fields available across the dashboard
    expressions,
  },
};

const studioDiv = document.querySelector<HTMLElement>("#myStudio")!;
studioApi = createStudio(studioDiv, studioProperties);

if (typeof window !== "undefined") {
  // Attach external event handlers to window so they can be called from index.html
  (<any>window).selectPage = selectPage;
  (<any>window).toggleMode = toggleMode;
}
```

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

> **Note**
>
> Read the [Building a Dashboard](https://www.ag-grid.com/studio/javascript/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/javascript/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/javascript/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/javascript/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/javascript/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/javascript/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/javascript/quick-start/)**

Embed AG Studio into your app.

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

Learn how to build self-serve reports.
