---
title: "Duplicate a chart"
method: POST
path: "/api/v3/charts/{chartId}/duplicate"
tags: ["V3 Charts"]
---

# Duplicate a chart

`POST /api/v3/charts/{chartId}/duplicate`

## Request

- Base URL: `https://app.formbricks.com`
- URL: `https://app.formbricks.com/api/v3/charts/{chartId}/duplicate`
- Auth: one of:
  - API key in cookie `next-auth.session-token`
  - API key in header `x-api-key`
  - HTTP bearer

Copies a chart — same Feedback Dataset, type, chart query and display settings — so the copy can
change without touching the dashboards that show the original. With `dashboardId`, the copy is
also put on that dashboard (first open slot, or `layout`) in the same transaction, and the
dashboard's `updatedAt` moves; a dashboard already holding 250 widgets, or with no open slot above
the grid's last row, answers 409 and no copy is made. Needs write access to the chart's workspace and to its Feedback Dataset.

## Path parameters

- `chartId` string, cuid2, required

## Request body

- ChartDuplicateRequest
  - `name` string: Name for the copy, unique within the workspace. Without one the copy is named "<original> (copy)", then "(copy 2)" and so on.
  - `dashboardId` string, cuid2: Also put the copy on this dashboard, in the same transaction: if placing it fails, no copy is left behind. Must be in the chart's workspace.
  - `layout` WidgetLayout: A widget's position and size on the dashboard's 12-column grid, in whole grid units: `1 ≤ w ≤ 12`, `x + w ≤ 12`, `1 ≤ h ≤ 8`, `0 ≤ y ≤ 1000`. Widgets may not overlap; gaps are allowed and close upward when the dashboard is drawn.
    - `x` integer, required
    - `y` integer, required
    - `w` integer, required
    - `h` integer, required

## Response `201`

The copy

- object
  - `data` ChartResource, required: A chart: one saved visualisation of one Feedback Dataset. It belongs to a workspace and can sit on any number of dashboards — `dashboards` lists them, so a caller can see what an edit or a delete would affect.
    - `id` string, cuid2, required
    - `workspaceId` string, cuid2, required
    - `datasetId` string, cuid2, required: The Feedback Dataset the chart reads. Fixed when the chart is created.
    - `name` string, required: Unique within the workspace.
    - `type` 'area' | 'bar' | 'pie' | 'big_number' | 'matrix', required
    - `query` ChartQuery, required: What a chart computes, in Cube's query format (ADR 0001): measures, dimensions, time dimensions, filters, order and limit over its Feedback Dataset's fields. Every field must come from the field catalog (`GET /api/v3/charts/fields`), named with its data-source prefix. Display settings are separate (`config`). Refused with **422** (`invalid_params` names each problem): a field that is not in the catalog; Cube options the app does not use (`ungrouped`, `segments`, `renewQuery`, `joinHints`, `total`, `compareDateRange`); a `timezone` (every range is read in the organization's display time zone); more than 15 fields across `measures`, `dimensions` and `timeDimensions`; more than 25 filter conditions or filters nested deeper than three levels; a date range that is not a preset, `all-time` or two ISO calendar dates; on a chart grouped by time alone, a date range that spans more than 10,000 buckets of its `granularity` (code `limit_exceeded`).
      - `measures` string[]
      - `dimensions` string[]
      - `timeDimensions` object[]
        - `dimension` string, required: A field of kind `timeDimension`.
        - `granularity` 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'
        - `dateRange` union: A date range on a time dimension: a named preset from the field catalog's `datePresets` (`last 7 days`, `this month`, …), `all-time` (no range), or a pair of ISO calendar dates `[from, to]`, both inclusive, `from` not after `to`. Every range is read in the organization's display time zone; a `timezone` cannot be supplied. Charts saved before ranges were limited to presets may hold a relative range Cube reads itself (`last 3 months`, `this week`). Such a chart still renders and can be previewed, within the bucket limit below, and can be saved with that same range kept; a new chart, or an update to another range, must use a preset or a pair. A chart grouped by time alone (one time dimension with a `granularity`, no other dimensions) gets one row per bucket in its range, empty or not, so its range may span at most 10,000 buckets: about 13 months by the hour, or 27 years by the day. A wider range is refused with **422** `limit_exceeded`; use a coarser granularity or a shorter range. An older chart's relative range is measured back from now (`last 999 years` spans 999 years). Every preset fits at every granularity, and a chart with other dimensions returns only its real rows, so it is not limited.
          - string: A preset name from `datePresets`, or `all-time`.
          - string[]
      - `filters` ChartQueryFilter[]
        - union: A filter: either one condition on a field, or an `and` / `or` group of filters. Filters nest at most three levels: a group, a group inside it, and conditions inside that. A chart query holds at most 25 conditions in all.
          - object
            - `member` string, required: A field name from the field catalog.
            - `operator` 'equals' | 'notEquals' | 'contains' | 'notContains' | 'set' | 'notSet' | 'gt' | 'gte' | 'lt' | 'lte', required: One of the field's `filterOperators` in the catalog.
            - `values` string[]: The values to compare against. Required by every operator except `set` and `notSet`, which take none.
          - object
            - `and` ChartQueryFilter[], required
          - object
            - `or` ChartQueryFilter[], required
      - `order` union: Sort order: `[field, direction]` pairs, or an object mapping a field to its direction. At most 10 entries.
        - array[]
          - unknown[]
            - unknown
        - object
      - `limit` integer
      - `offset` integer
    - `config` ChartConfig, required: Display settings, kept apart from the chart query. Every key is optional; each applies to the chart types named in its description and is ignored by the others.
      - `barOrientation` 'vertical' | 'horizontal': Bar charts. Defaults to `vertical`.
      - `pieDisplay` 'pie' | 'breakdown': Pie charts. Defaults to `pie`.
      - `areaDisplay` 'filled' | 'line': Area charts. Defaults to `filled`.
      - `matrixCellValue` 'percent' | 'count' | 'both': Matrix charts. Defaults to `percent`.
      - `matrixColorScale` boolean: Matrix charts. Defaults to `true`.
      - `matrixShowTotals` boolean: Matrix charts. Defaults to `false`.
      - `matrixTranspose` boolean: Matrix charts. Swaps rows and columns on screen; the query is unchanged.
      - `colors` string[]
      - `xAxisLabel` string
      - `yAxisLabel` string
      - `showLegend` boolean
      - `legendPosition` 'top' | 'bottom' | 'left' | 'right'
      - `stacked` boolean
      - `showGrid` boolean
      - `showValues` boolean
      - `numberFormat` string
      - `dateFormat` string
      - `prefix` string
      - `suffix` string
    - `creator` object, nullable, required: Who created the chart, for display only; `null` once that user is gone.
      - `name` string, required
    - `dashboards` object[], required: The dashboards that hold the chart, by name.
      - `id` string, cuid2, required
      - `name` string, required
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required

## Other responses

- `400`: Bad Request — malformed JSON, invalid query/body/params, duplicate name, or unsupported field.
- `401`: Not authenticated (no valid session or API key).
- `403`: Forbidden. `code: forbidden` when the caller has no access to the workspace, the Feedback Dataset or the resource (which may also not exist; 404 is not used, to avoid leaking existence). `code: dashboards_plan_required` when the caller is authorized but the organization does not have the Dashboards add-on or a license that includes it — checked after authorization, so a caller without access never learns the organization's plan.
- `409`: Conflict (`code: conflict`) — the request clashes with the current state: a chart or dashboard name already used in the workspace, a chart already on the dashboard, a dashboard already holding its 250 widgets, a dashboard with no open slot left above its grid's last row, or a dashboard changed since the `expectedUpdatedAt` the caller sent.
- `422`: Unprocessable Content — semantic validation failed or the requested lifecycle transition is invalid.
- `429`: Rate limit exceeded.
- `500`: Internal Server Error.

## Changes

- **2026-10-28** `84820a01e550`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/formbricks/apis/formbricks-api-v3/changes/api/v3/charts/:chartId/duplicate/post.md)

---

[API](https://skmtc.dev/formbricks/apis/formbricks-api-v3.md) - [All operations](https://skmtc.dev/formbricks/apis/formbricks-api-v3/llms.txt) - [OpenAPI document](https://skmtc.dev/formbricks/apis/formbricks-api-v3/revisions/25335c959a13?raw)
