Create and manage charts

Create STAT, PIE, and BAR charts on a dashboard, then rename, reposition, restyle, duplicate, and delete them.


A chart is a single card on a dashboard. This page covers the full lifecycle: create a chart with createChart, change its title, position, display, or metadata with editChart, duplicate it into the same or another dashboard with copyChart, and remove it with deleteChart. Charts are Chart objects in the API.

Every chart is either a manual chart (driven by chartSegments — formulas over project-scoped values) or an auto-generated chart (driven by metadata.query). createChart accepts either shape; the segment and value mutations apply only to manual charts. See the section overview for the full type map.

createChart

Use the createChart mutation to add a chart to a dashboard.

Request

The smallest call creates an empty STAT chart. You build out its segments afterward with createChartSegment.

mutation CreateChart {
  createChart(input: { dashboardId: "dashboard_123", title: "Open records", type: STAT }) {
    id
    title
    type
    position
    isCalculating
    needCalculation
  }
}

Parameters

CreateChartInput

ParameterTypeRequiredDescription
dashboardIdString!YesDashboard the chart belongs to. You must be its creator or hold the EDITOR role.
titleString!YesDisplay title of the chart.
typeChartType!YesSTAT, PIE, or BAR.
positionFloatNoSort position within the dashboard. Omit to append after the last chart (see Position defaults).
displayFormulaDisplayInputNoHow the chart’s value is formatted (number/currency/percentage) and the optional display rollup function.
chartSegments[ChartSegmentInput!]NoInline segments for a manual chart. Mutually exclusive with metadata. Each segment carries a formula and its values — see below.
metadataChartMetadataInputNoA query spec for an auto-generated chart. Mutually exclusive with chartSegments.

ChartType

ValueDescription
STATA single headline number (one segment value, or a formula rollup).
PIEA pie chart — either manual segments or a grouped query.
BARA bar chart — either manual segments or a grouped query.

FormulaDisplayInput

ParameterTypeRequiredDescription
typeFormulaDisplayType!YesNUMBER, CURRENCY, or PERCENTAGE.
currencyFormulaDisplayCurrencyInputNo{ code, name }, e.g. { code: "USD", name: "US Dollar" }. Use with CURRENCY.
precisionFloatNoNumber of decimal places to show.
functionChartFunctionNoDisplay rollup applied across the chart. See ChartFunction.

ChartMetadataInput

One shape for every chart. Supply query for an auto-generated chart; omit it for a manual one, whose numbers come from its chartSegments instead.

ParameterTypeRequiredDescription
queryChartQueryInputNoWhat to group by and what to measure. Omit for a manual chart.
presentationChartPresentationInputNoHow the result is dressed — stacking, target, thresholds, context band.

ChartQueryInput:

ParameterTypeRequiredDescription
dimensions[ChartDimensionInput!]!YesWhat records are grouped into. One entry today.
metrics[ChartMetricInput!]!YesWhat is measured per group. More than one plots several series over the same grouping.
breakoutChartBreakoutInputNoA second grouping that splits every bucket into slices. See stacked charts.
filtersTodoFilterInputNoNarrows the whole chart. Same filter used by list records.

ChartDimensionInput:

FieldTypeRequiredDescription
typeBarChartXAxisType!YesWhat to bucket records by: PROJECT, ASSIGNEE, TAG, CUSTOM_FIELD, TODO, TODO_LIST, TODO_STATUS, TODO_DUE_DATE, TODO_CREATED_AT, TODO_UPDATED_AT, TODO_COMPLETED_AT.
titleStringNoOptional axis label.
intervalBarChartXAxisIntervalNoBucket size for date dimensions: DAY, WEEK, MONTH, QUARTER, YEAR.
customFieldNameStringNoCustom-field name when type is CUSTOM_FIELD.
customFieldTypeCustomFieldTypeNoType of that custom field.
customFieldReferenceProjectIdStringNoFor reference custom fields, the referenced project.
orderChartOrderInputNoHow the buckets are arranged. Omit to take the order the aggregate produced. See bucket order and limit.
limitIntNoKeep only the first N buckets after ordering. 1200. Omit to draw every bucket.

Bucket order and limit

ChartOrderInput decides the order the buckets are drawn in. Ordering happens after every measure has been computed, not in the aggregate, so a chart with several metrics keeps one bucket order.

FieldTypeRequiredDescription
byChartOrderBy!YesVALUE (the measure), LABEL (the bucket’s name), or FIELD (a column of the record).
directionChartSortDirection!YesASC or DESC.
metricKeyStringNoWhich metric ranks the buckets, when by is VALUE and the chart has more than one.
fieldChartOrderFieldInputNoRequired when by is FIELD. Names the record column to sort on.

Two orders are refused with BAD_USER_INPUT:

  • FIELD unless the dimension type is TODO. Only a record axis has exactly one value per bucket to sort on.
  • LABEL on a dimension that buckets by date (TODO_DUE_DATE, TODO_CREATED_AT, TODO_UPDATED_AT, TODO_COMPLETED_AT, or a DATE custom field). Those labels are formatted dates, so an alphabetical sort would put Apr 2026 before Jan 2026.

limit needs a resolvable order to decide which buckets stay — either an explicit order, or the display type’s own default. A leaderboard ranks by VALUE DESC on its own, so it accepts a limit without an order; a funnel takes no order at all and so takes no limit. A limit with no resolvable order, or outside 1200, is refused with BAD_USER_INPUT.

The limit is a display concern: the aggregate still computes and stores every bucket, and exports still cover every record the chart counted. Read the value back from limit on the ChartDimension output type.

ChartMetricInput:

FieldTypeRequiredDescription
keyStringNoUnique within the chart. Omit and Blue assigns one from the metric’s position.
titleStringNoOptional series label.
functionChartSegmentValueFunctionsNoAggregate applied to the measured records. Omit for a plain record count.
filterTodoFilterInputNoNarrows this metric only, so two metrics can measure different slices of the same grouping.
colorStringNoExplicit series colour. Omit to take the palette colour for the metric’s position.
axisChartMetricAxisNoLEFT (default) or RIGHT, so two metrics in different units can share a chart.
customFieldNameStringNoCustom-field name to aggregate.
customFieldTypeCustomFieldTypeNoType of that custom field.
customFieldReferenceProjectIdStringNoFor reference custom fields, the referenced project.

ChartPresentationInput carries stackMode, target, direction, bands and context. None of it affects which records the chart covers.

Stacked bar charts

A BAR chart can add a second grouping dimension so each bucket is split into stacked slices — records per assignee within each workspace, for example. Set breakout on ChartQueryInput to turn this on, and presentation.stackMode to choose how the slices combine.

ParameterTypeRequiredDescription
query.breakoutChartBreakoutInputNoThe dimension each bucket is split by. Omit for a single-series chart.
presentation.stackModeChartStackModeNoHow the slices combine. Ignored when there is no breakout. Omit for a stacked chart.

ChartBreakoutInput mirrors the dimension input, minus interval — bucketing a date axis by another date is not a composition:

FieldTypeRequiredDescription
typeBarChartSeriesType!YesWhat to split each bucket by: PROJECT, ASSIGNEE, TAG, TODO_LIST, TODO_STATUS, or CUSTOM_FIELD.
titleStringNoOptional label for the breakdown.
customFieldNameStringNoCustom-field name when type is CUSTOM_FIELD.
customFieldTypeCustomFieldTypeNoType of that custom field.
customFieldReferenceProjectIdStringNoFor reference custom fields, the referenced project.

ChartStackMode is either STACKED (slices are stacked and the bar’s height is the bucket total) or PERCENT (each bucket is normalised to 100%, so only the mix is shown). A breakdown always stacks; side-by-side grouped bars are not offered.

For chartSegments (manual charts), the segment and value input fields are documented on Build chart segments and values; the example below shows the inline form.

Response

{
  "data": {
    "createChart": {
      "id": "clm4n8qwx000008l0g4oxdqn7",
      "title": "Open records",
      "type": "STAT",
      "position": 65535,
      "isCalculating": true,
      "needCalculation": true
    }
  }
}

Returns

createChart returns the new Chart.

FieldTypeDescription
idID!Unique identifier of the chart.
titleString!Chart title.
positionFloat!Sort position within the dashboard.
typeChartType!STAT, PIE, or BAR.
chartSegments[ChartSegment!]!Manual segments. Empty for an auto-generated chart.
metadataChartMetadataThe query/presentation config. query is null on a manual chart.
displayFormulaDisplayDisplay formatting (type, currency, precision, function).
isCalculatingBooleanA recompute is currently in progress.
needCalculationBooleanResults are stale or pending — a calculation is queued.
isCalculatingWithFilterBooleanSet transiently when the chart is read through charts(filter.todoFilter).
createdAtDateTime!When the chart was created.
updatedAtDateTime!When the chart was last modified.

Full example

Create an auto-generated BAR chart that counts records by workspace, formatted as a whole number.

mutation CreateBarChart {
  createChart(
    input: {
      dashboardId: "dashboard_123"
      title: "Records per workspace"
      type: BAR
      display: { type: NUMBER, precision: 0 }
      metadata: {
        query: {
          dimensions: [{ title: "Workspace", type: PROJECT }]
          metrics: [{ title: "Records" }]
        }
      }
    }
  ) {
    id
    title
    type
    displayType
    metadata {
      query {
        dimensions {
          title
          type
        }
        metrics {
          key
          title
          function
        }
      }
    }
    needCalculation
  }
}

To create a manual chart instead, pass chartSegments (and omit metadata). Each segment carries a formula whose logic.text/logic.html reference its values by their uid:

mutation CreateManualChart {
  createChart(
    input: {
      dashboardId: "dashboard_123"
      title: "Won vs. lost"
      type: PIE
      chartSegments: [
        {
          title: "Won deals"
          color: "#22c55e"
          uid: "seg_won"
          formula: {
            logic: { text: "val_won", html: "<span>val_won</span>" }
            display: { type: NUMBER }
          }
          chartSegmentValues: [
            { uid: "val_won", title: "Won", projectId: "project_123", function: COUNT }
          ]
        }
      ]
    }
  ) {
    id
    title
    chartSegments {
      id
      uid
      title
    }
  }
}

Errors

CodeWhen
DASHBOARD_NOT_FOUNDNo dashboard matches dashboardId for the caller, including when the caller lacks creator or EDITOR access to it.
{
  "errors": [
    {
      "message": "Dashboard was not found.",
      "extensions": { "code": "DASHBOARD_NOT_FOUND" }
    }
  ]
}

editChart

Use the editChart mutation to rename, reposition, restyle, or re-spec a chart. Only the fields you pass are changed; omitted fields are left untouched. Segments and values are not edited here — use the segment and value mutations.

Request

mutation EditChart {
  editChart(input: { id: "chart_123", title: "Open records (this quarter)" }) {
    id
    title
    updatedAt
  }
}

Parameters

EditChartInput

ParameterTypeRequiredDescription
idString!YesID of the chart to edit.
titleStringNoNew title.
positionFloatNoNew sort position within the dashboard.
displayFormulaDisplayInputNoNew display formatting. Replaces the existing display.
metadataChartMetadataInputNoNew query/presentation config. Changing the query re-triggers an asynchronous result calculation.

Response

{
  "data": {
    "editChart": {
      "id": "clm4n8qwx000008l0g4oxdqn7",
      "title": "Open records (this quarter)",
      "updatedAt": "2026-05-29T14:07:42.000Z"
    }
  }
}

editChart returns the updated Chart (same shape as createChart).

Errors

CodeWhen
CHART_NOT_FOUNDNo chart matches id for the caller, including when the caller lacks creator or EDITOR access to its dashboard.

copyChart

Use the copyChart mutation to duplicate a chart into the same dashboard or a different one. The copy is a deep copy: segments, values, formulas, type, display, and metadata are recreated with fresh identifiers.

Request

mutation CopyChart {
  copyChart(input: { chartId: "chart_123", dashboardId: "dashboard_456" }) {
    id
    title
    type
    position
  }
}

Parameters

CopyChartInput

ParameterTypeRequiredDescription
chartIdString!YesChart to copy. You must be the creator or an EDITOR of its dashboard.
dashboardIdString!YesDestination dashboard. You must be its creator or an EDITOR. May be the same dashboard.
titleStringNoTitle for the copy. If omitted, the copy keeps the original chart’s title.

Response

{
  "data": {
    "copyChart": {
      "id": "clm4n8qwx000409l0d1weq2bk",
      "title": "Open records",
      "type": "STAT",
      "position": 131070
    }
  }
}

copyChart returns the newly created Chart.

The copy is appended after the last chart in the destination dashboard (see Position defaults) and its results are recalculated asynchronously, so it briefly reports needCalculation: true.

Errors

CodeWhen
DASHBOARD_NOT_FOUNDNo destination dashboard matches dashboardId for the caller, or the caller lacks creator or EDITOR access to it.
CHART_NOT_FOUNDNo chart matches chartId for the caller, or the caller lacks creator or EDITOR access to the source chart’s dashboard.

deleteChart

Use the deleteChart mutation to remove a chart and its segments. deleteChart takes the chart id directly, not an input object.

Request

mutation DeleteChart {
  deleteChart(id: "chart_123") {
    success
  }
}

Parameters

ParameterTypeRequiredDescription
idString!YesID of the chart to delete.

Response

deleteChart returns a MutationResult. It exposes only success and operationId — there is no message field.

{
  "data": {
    "deleteChart": {
      "success": true
    }
  }
}

MutationResult

FieldTypeDescription
successBoolean!true if the chart was deleted; false if the delete failed.
operationIdStringCorrelation ID for the operation, when set.

Errors

CodeWhen
CHART_NOT_FOUNDNo chart matches id for the caller, including when the caller lacks creator or EDITOR access to its dashboard.

If the chart is found but the delete itself fails, the mutation returns { "success": false } rather than throwing.

ChartFunction vs. ChartSegmentValueFunctions

Two enums share the same seven members but live in different places — keep them straight in examples:

EnumWhere it appearsMembers
ChartFunctionFormulaDisplayInput.function (the chart-level display rollup)COUNT, COUNTA, SUM, AVERAGE, AVERAGEA, MIN, MAX
ChartSegmentValueFunctionsEach segment value and each chart metric (function)COUNT, COUNTA, SUM, AVERAGE, AVERAGEA, MIN, MAX

Position defaults

position is a Float! controlling order within the dashboard. When you omit position on createChart (and always on copyChart), the server appends the chart after the current last one: it takes the highest existing position and adds a fixed gap, or uses that gap as the starting position if the dashboard has no charts yet. Pass an explicit position to slot a chart between two others. Reorder later with editChart.

Permissions

All four mutations are writes and require the caller to either have created the chart’s dashboard or hold the EDITOR role on it. copyChart checks this on both the source chart’s dashboard and the destination dashboard.

Access to the dashboardCan create / edit / copy / delete
CreatorYes
EDITORYes
VIEWERNo
No accessNo

A caller without sufficient access receives CHART_NOT_FOUND (or DASHBOARD_NOT_FOUND for the dashboard argument) rather than a distinct permission error — the chart or dashboard simply does not match the access-scoped lookup. Reads, by contrast, require only viewer access; see Query and subscribe to charts.