Development
Loading data

Loading Data with loadData

The loadData function, called inside your .emb.ts files, is a powerful part of the Embeddable SDK. It lets you load data for your components (charts, controls, etc.) while giving no-code users the flexibility to choose which data they want displayed. This is key to building reusable and configurable components.

A Basic Example

Below is an .emb file for a bar chart component that calls loadData inside the defineComponent function:

// src/components/BarChart/BarChart.emb.ts
 
import { defineComponent } from '@embeddable.com/react';
import { loadData } from '@embeddable.com/core';
import Component from './index';
 
export const meta = {
  name: 'BarChart',
  label: 'Bar Chart',
  inputs: [
    {
      name: 'ds',
      type: 'dataset', // Renders a dropdown with available datasets in the builder
      label: 'Dataset',
    },
    {
      name: 'myDimension',
      type: 'dimension', // Renders a dropdown with dimensions
      array: false,      // Enables selection of a single dimension
      label: 'Dimension',
      config: { dataset: 'ds' } // Limit dimensions to the chosen dataset
    },
    {
      name: 'myMeasures',
      type: 'measure',   // Renders a dropdown with measures
      label: 'Measure',
      array: true,       // Enables selection of multiple measures
      config: { dataset: 'ds' } // Limit measures to the chosen dataset
    }
  ]
} as const;
 
export default defineComponent(Component, meta, {
  props: (inputs) => {
    return {
      ...inputs,
      results: loadData({
        from: inputs.ds, // Receives the chosen dataset
        dimensions: [inputs.myDimension], // Receives the chosen dimension
        measures: inputs.myMeasures // Receives the chosen measures
      })
    };
  }
});

When users add this component in the no-code dashboard builder, they will:

  • Pick a dataset (e.g. orders or customers).
  • Select a dimension (e.g. country).
  • Choose one or more measures (e.g. count, revenue).

Then loadData retrieves information from your database or the cache, and your component receives props like:

props: {
  myDimension: {
    name: "customers.country",
    title: "Country",
    ...
  },
  myMeasures: [
    {
      name: "customers.count",
      title: "# of customers",
      ...
    }
  ],
  results: {
    isLoading: false, // false when data has loaded
    error: undefined, // error message if there's a loading issue
    data: [ // each row of data
      {
        "customers.country": "United States",
        "customers.count": "9"
      },
      {
        "customers.country": "Spain",
        "customers.count": "67"
      },
      ...
    ]
  }
}

Your React component can:

  • Check results.isLoading or results.error to show spinners or errors.
  • Read results.data to render your chart or UI.
  • Use myDimension and myMeasures to label series or axes.

This approach lets no-code users decide which data to load, while your code handles how to display it.

Parameters

Below are the main parameters relevant to loadData(...), with examples. For each type, you can prompt the user to select it by defining the appropriate inputs in your .emb.ts file.

ParamTypeRequiredDefault Value
fromDatasetYes
dimensionsDimension[]At least one dimension or measure is required[]
measuresMeasure[]At least one dimension or measure is required[]
timeDimensionsTimeDimension[]No[]
orderByOrderBy[]Nonone
limitnumberNo100
offsetnumberNo0
filtersQueryFilter[]No[]

loadData effectively builds an SQL statement under the hood, using the parameters above:

SELECT <timeDimensions>, <dimensions>, <measures>
FROM <dataset>
WHERE <filters on dimensions>
GROUP BY <timeDimensions>, <dimensions>
HAVING <filters on measures>
ORDER BY <orderBy>
LIMIT <limit>
OFFSET <offset>

Types

  • Dataset is a defined dataset in the no-code builder that's based on a data model, possibly with pre-defined filters.

  • Dimension is an array of one or more dimensions.

  • Measure is an array of one or more measures

  • TimeDimensions are time-based dimensions, often paired with a granularity (e.g. day, week, month).

    timeDimensions: [
      {
        dimension: inputs.xAxis?.name, //pass just the dimension name
        granularity: inputs.granularity
      }
    ]
  • OrderBy is an array of sorting objects containing a property (dimension or measure) and a direction (asc or desc). This is also available as a native input in the no-code builder, but you can define it if your component needs custom sorting.

    orderBy: [
      { 
        property: inputs.yAxis, 
        direction: 'desc' 
      }
    ]
  • limit is a number restricting how many rows are returned, e.g. for pagination. It defaults to 100 if not provided. This is also available as a native input in the no-code builder.

  • offset is a number specifying how many rows to skip, e.g. for pagination. Defaults to 0.

  • filters is an array of additional filters, applied on top of any filters in the dataset. Dimensions go into WHERE, measures go into HAVING.

    filters: [
      {
        property: inputs.yAxis, // Dimension | Measure | DimensionOrMeasure
        operator: 'gt', // see below for available operators
        value: 0 // string | boolean | number | Date | Time | TimeRange | Array<string | boolean | number | Date>;
      }
    ],

Available Filter Operators

Embeddable currently supports the following operators for dimension and measure filters:

  • equals: Property equals the specified value.
  • notEquals: Property does not equal the specified value.
  • contains: String property contains the specified substring.
  • notContains: String property does not contain the specified substring.
  • startsWith: String property starts with the specified substring.
  • endsWith: String property ends with the specified substring.
  • gt: Property is greater than the specified value.
  • gte: Property is greater than or equal to the specified value.
  • lt: Property is less than the specified value.
  • lte: Property is less than or equal to the specified value.
  • set: Property is not null (i.e., it has a value).
  • notSet: Property is null (no value).
  • measureFilter: Used for advanced measure-based filtering logic (e.g., when combining multiple measures).

Depending on whether a filter applies to a dimension or measure, Embeddable translates these operators into a WHERE or HAVING clause in the generated SQL.

Date/Time-specific operators (for fields of type time):

  • inDateRange: Property falls within a specified start and end date/time.

  • notInDateRange: Property is outside the specified date range.

    {
      property: inputs.timeDimension,
      operator: 'inDateRange',
      value: { // TimeRange value
        from: inputs.timeFilter.from, // string | Date
        relativeTimeString: '', // string
        to: inputs.timeFilter.to, // string | Date
      },
    }
    💡

    You can also specify a wide range of relative time strings e.g. yesterday, last week etc. Behind the scenes, Cube uses Chrono (opens in a new tab) to parse these.

  • beforeDate: Property is strictly before a specified date/time.

  • afterDate: Property is strictly after a specified date/time.

  {
    property: inputs.timeDimension, 
    operator: 'beforeData',
    value: ["2025-01-01"] // an array of one element in YYYY-MM-DD format
  }

A Full Example

Below is a comprehensive example that uses all the parameters of loadData:

import { loadData } from '@embeddable.com/core';
import { defineComponent } from '@embeddable.com/react';
 
import Component from './index';
 
export const meta = {
  name: 'GroupedLineChart',
  label: 'Grouped Line chart',
  inputs: [
    {
      name: 'ds',
      type: 'dataset',
      label: 'Dataset',
    },
    {
      name: 'xAxis',
      type: 'dimension',
      label: 'X-Axis',
      config: {
        dataset: 'ds',
        supportedTypes: ['time'] // Only let user pick time dimensions
      }
    },
    {
      name: 'granularity',
      type: 'granularity',
      label: 'Granularity'
    },
    {
      name: 'groupBy',
      type: 'dimension',
      label: 'Group by',
      config: { dataset: 'ds' }
    },
    {
      name: 'yAxis',
      type: 'measure',
      label: 'Y-Axis',
      config: { dataset: 'ds' }
    },
  ],
};
 
export default defineComponent(Component, meta, {
  props: (inputs) => {
    return {
      ...inputs,
      results: loadData({
        from: inputs.ds,
        measures: [inputs.yAxis],
        dimensions: [inputs.groupBy],
        timeDimensions: [
          {
            dimension: inputs.xAxis?.name,
            granularity: inputs.granularity
          }
        ],
        filters: [
          {
            property: inputs.yAxis,
            operator: 'gt',
            value: 0
          }
        ],
        orderBy: [{ property: inputs.yAxis, direction: 'desc' }],
        limit: 500,
        offset: 0
      })
    };
  }
});

Working With The results Object

After loadData fetches data, your React component receives a results object (along with any other inputs the user selected). Using the example above, your component might get props like this:

{
  "ds": { "datasetId": "...", "embeddableId": "..." },
  "xAxis": {
    "name": "orders.created_at",
    "title": "Created at"
  },
  "granularity": "month",
  "groupBy": {
    "name": "customers.country",
    "title": "Country of origin"
  },
  "yAxis": {
    "name": "customers.count",
    "title": "# of customers"
  },
  "results": {
    "isLoading": false, // true while data is loading
    "error": undefined, // an error message, returned as a string
    "data": [
      {
        "customers.country": "United States",
        "orders.created_at": "2023-05-01T00:00:00.000",
        "customers.count": "9"
      },
      {
        "customers.country": "United States",
        "orders.created_at": "2023-06-01T00:00:00.000",
        "customers.count": "57"
      },
      ...
    ]
  }
}
  • results.isLoading and results.error let you handle loading states or errors.
  • results.data is an array of rows, indexed by each dimension/measure name.
  • xAxis, yAxis, and groupBy objects show which dimension/measure the user chose in the builder.

Example Usage in Your Component

export default function MyLineChart(props: Props) {
  const { xAxis, groupBy, yAxis, results } = props;
  const { isLoading, error, data } = results;
 
  if (isLoading) return <Loading />;
  if (error) return <Error msg={error} />;
 
  const xValues = data.map((row) => new Date(row[xAxis.name]));
  const groups  = data.map((row) => row[groupBy.name]);
  const yValues = data.map((row) => Number(row[yAxis.name]));
 
  // Render your chart (e.g., axis labels, legends, grouping)
  return <LineChart x={xValues} y={yValues} groupBy={groups} />;
}

This pattern lets no-code users pick what data to load, while you focus on how to display it.

Making Multiple loadData Requests

Sometimes your component needs more than one data set. For example, you might display a main chart and a secondary chart, each requiring different dimensions or measures. Simply call loadData multiple times under different prop names:

export default defineComponent(Component, meta, {
  props: (inputs) => {
    return {
      ...inputs,
      // First request
      mainResults: loadData({
        from: inputs.mainDataset,
        dimensions: [inputs.mainDimension],
        measures: [inputs.mainMeasure]
      }),
      // Second request
      secondaryResults: loadData({
        from: inputs.secondaryDataset,
        measures: [inputs.secondaryMeasure]
      })
    };
  }
});

Here, mainResults and secondaryResults each return their own isLoading, error, and data properties—letting you manage multiple data sets without extra complexity.

Loading Data Dynamically

Sometimes you need to fetch new data triggered by an action or event fired from within your React component. You can achieve using Embeddable State.

1. Pagination Example

A common scenario is incrementing offset or changing limit when a user clicks “Next Page” on a table component. You can hold these values in Embeddable State, then pass them to loadData each time they change:

// MyPaginatedTable.emb.ts
 
import { defineComponent } from '@embeddable.com/react';
import { loadData } from '@embeddable.com/core';
import TableComponent from './index';
 
export const meta = {
  name: 'MyPaginatedTable',
  label: 'My Paginated Table',
  inputs: [
    {
      name: 'ds',
      type: 'dataset',
      label: 'Dataset'
    },
    {
      name: 'columns',
      type: 'dimension',
      label: 'Columns',
      array: true,
      config: {
        dataset: 'ds'
      }
    },
  ]
} as const;
 
export default defineComponent(TableComponent, meta, {
  props: (inputs, [state, setState]) => {
    // Default pagination to page=0, limit=10
    const page = state?.page ?? 0;
    const limit = 10;
 
    return {
      ...inputs,
      page,
      setPage: (newPage) => setState({ page: newPage }), // So the React component can update page
      results: loadData({
        from: inputs.ds,
        dimensions: inputs.columns,
        limit,
        offset: page * limit
      })
    };
  }
});

Inside your React component:

import { useEmbeddableState } from '@embeddable.com/react';
 
export default function TableComponent(props) {
  const { page, setPage, results } = props;
  const { isLoading, error, data } = results;
 
  if (isLoading) return <Loading />;
  if (error) return <Error msg={error} />;
 
  const handleNext = () => setPage(page + 1);
  const handlePrev = () => setPage(Math.max(0, page - 1));
 
  return (
    <div>
      <Table data={data} />
      <button onClick={handlePrev} disabled={page === 0}>Prev</button>
      <button onClick={handleNext}>Next</button>
      <p>Current Page: {page}</p>
    </div>
  );
}

Every time you click Next or Prev, Embeddable State updates page, triggers a re-render, and runs loadData with the new offset.

2. Filtering Example

For more advanced scenarios, such as applying filters dynamically:

// DynamicLoader.emb.ts
 
import { defineComponent } from '@embeddable.com/react';
import { loadData } from '@embeddable.com/core';
import Component from './index';
 
export const meta = {
  name: 'DynamicLoader',
  label: 'Dynamic Loader',
  inputs: [
    {
      name: 'ds',
      type: 'dataset',
      label: 'Dataset'
    },
    {
      name: 'columns',
      type: 'dimensionOrMeasure',
      label: 'Columns',
      array: true,
      config: {
        dataset: 'ds',
      },
      category: 'Chart data',
    }
  ]
} as const;
 
export default defineComponent(Component, meta, {
  props: (inputs, [state, setState]) => {
    const currentFilter = state?.filter;
 
    return {
      ...inputs,
      currentFilter,
      results: loadData({
        from: inputs.ds,
        dimensions: inputs.columns,
        filters: currentFilter ? [currentFilter] : undefined
      })
    };
  }
});

Then in your React component:

import { useEmbeddableState } from '@embeddable.com/react';
 
export default function DynamicLoader(props) {
  const [localState, setLocalState] = useEmbeddableState({ filter: {} });
  
  const applyFilter = (value) => {
    setLocalState((prev) => ({
      ...prev,
      filter: {
        property: { name: customers.country }, // some dimension, hard-coded or dynamically passed in via props
        operator: 'equals',
        value
      }
    }));
  };
 
  // props.results automatically updates when Embeddable State changes.
 
  return (
    <div>
      <Table data={data} />
      <button onClick={() => applyFilter('United States')}>
        Filter to US
      </button>
    </div>
  );
}

How it works:

  1. User Interaction: Clicking the button modifies Embeddable State (filter).
  2. Rerun loadData: When state changes, the .emb.ts file sees the new filter and calls loadData again.
  3. Refresh Component: Your component receives fresh results reflecting the updated filter.

This pattern lets you dynamically reload data based on user-driven state changes while still leveraging the no-code builder for broader configuration.

💡

Importantly, these code-driven techniques are useful for component-specific actions, such as pagination or custom filters controlled entirely inside the component. For broader dashboard-level filters, the no-code builder already provides a visual interface—using variables and filters to reload component data without writing extra code. This gives you the best of both worlds: a powerful no-code approach for general filtering, and full control for specialized component logic.