---
title: "Tree Data - Row Dragging"
enterprise: true
framework: vue
version: "36.1.0"
---

# Tree Data - Row Dragging

Rows can be rearranged interactively when using Tree Data by dragging with the mouse.

## Enabling Row Dragging

To enable row dragging, set `rowDrag: true` on the group column (usually via `autoGroupColumnDef`).

```ts
<ag-grid-vue
    :treeData="treeData"
    :autoGroupColumnDef="autoGroupColumnDef"
    /* other grid options ... */>
</ag-grid-vue>

this.treeData = true;
this.autoGroupColumnDef = {
    field: 'name',
    rowDrag: true // Enable row dragging on the group column
};
```

See the [Row Dragging](https://www.ag-grid.com/vue-data-grid/row-dragging/) documentation for more information about row dragging options, APIs, and advanced usage.

There are two approaches to enable Row Dragging:

- [Managed Row Dragging](#enabling-managed-row-dragging): The grid handles row dragging automatically.
- [Unmanaged Row Dragging](#unmanaged-row-dragging): Customized application-specific logic for row dragging.

## Enabling Managed Row Dragging

This is the simplest way to enable row dragging with Tree Data. The grid will automatically handle the dragging of rows and updating the data structure. It supports reordering, moving parents and children, and converting a leaf node into a group. Moving a parent to be a child of itself is not allowed, as this would create a cycle. The grid will prevent this automatically.

To enable managed row dragging, set the following options:

- `rowDragManaged: true` — Enables managed row dragging, so the grid handles row movement automatically.
- `autoGroupColumnDef.rowDrag: true` — Enables the drag handle in the group column.
- `suppressMoveWhenRowDragging: true` — Prevents the grid from moving rows while dragging, showing a highlight over the row instead.

> **Note**
>
> It is recommended to enable `suppressMoveWhenRowDragging` when using managed row dragging with Tree Data. Without this option, moving subtrees can cause the grid to jump or scroll unexpectedly as rows are repositioned during the drag. Enabling it provides a smoother and more predictable user experience by only highlighting the drop target without moving rows until the drop is complete.

#### Managed Row Drag with Tree Data

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowDragModule,
  enableDevValidations,
} from "ag-grid-community";
import { TreeDataModule } from "ag-grid-enterprise";
import { Task } from "./data";
import { getData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  TreeDataModule,
  RowDragModule,
]);

const eGridDiv = document.getElementById("myGrid");

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :autoGroupColumnDef="autoGroupColumnDef"
      :rowData="rowData"
      :getRowId="getRowId"
      :treeData="true"
      :treeDataParentIdField="treeDataParentIdField"
      :groupDefaultExpanded="groupDefaultExpanded"
      :rowDragManaged="true"
      :suppressMoveWhenRowDragging="true"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<Task> | null>(null);
    const columnDefs = ref<ColDef[]>([{ field: "assignee" }]);
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      headerName: "Task",
      field: "title",
      rowDrag: true,
      flex: 2,
      minWidth: 200,
    });
    const rowData = ref<Task[] | null>(getData());
    const getRowId = ref<GetRowIdFunc>((params) => params.data.id);
    const treeDataParentIdField = ref("parentId");
    const groupDefaultExpanded = ref(-1);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      columnDefs,
      autoGroupColumnDef,
      rowData,
      getRowId,
      treeDataParentIdField,
      groupDefaultExpanded,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Managed Row Drag with Tree Data](https://www.ag-grid.com/examples/tree-data-row-dragging/tree-managed-row-drag/vue3)

Other relevant options used in the example above include:

- `getRowId` — Provides a unique ID for each row, required for row movement.
- `treeData: true` — Enables tree data mode, allowing hierarchical data structures.
- `treeDataParentIdField: 'parentId'` — Specifies the field that defines parent-child relationships.
- `groupDefaultExpanded: -1` — Expands all groups by default.

```ts
<ag-grid-vue
    :treeData="treeData"
    :getRowId="getRowId"
    :treeDataParentIdField="treeDataParentIdField"
    :rowDragManaged="rowDragManaged"
    :groupDefaultExpanded="groupDefaultExpanded"
    :suppressMoveWhenRowDragging="suppressMoveWhenRowDragging"
    :autoGroupColumnDef="autoGroupColumnDef"
    /* other grid options ... */>
</ag-grid-vue>

this.treeData = true;
this.getRowId = params => params.data.id;
this.treeDataParentIdField = 'parentId';
this.rowDragManaged = true;
this.groupDefaultExpanded = -1;
this.suppressMoveWhenRowDragging = true;
this.autoGroupColumnDef = {
    field: 'name',
    rowDrag: true
};
```

### Managed Row Dragging with getDataPath

This next examples shows how to use the `getDataPath` callback to define the hierarchical structure of the data.

> **Note**
>
> This example uses filler nodes (where some intermediate path segments do not exist as explicit nodes in the data). Empty filler nodes cannot exist in the grid; if all their children are moved out, the filler node will be deleted and disappear. It is instead recommended to provide a full grid without filler nodes to avoid this. See the [Providing Data Paths](https://www.ag-grid.com/vue-data-grid/tree-data-paths/) for details about filler nodes and `getDataPath`.

#### Managed Row Drag with Tree Data (getDataPath)

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetDataPath,
  GetRowIdFunc,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowDragModule,
  enableDevValidations,
} from "ag-grid-community";
import { TreeDataModule } from "ag-grid-enterprise";
import { Task } from "./data";
import { getData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  TreeDataModule,
  RowDragModule,
]);

const eGridDiv = document.getElementById("myGrid");

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :autoGroupColumnDef="autoGroupColumnDef"
      :rowData="rowData"
      :getRowId="getRowId"
      :treeData="true"
      :getDataPath="getDataPath"
      :groupDefaultExpanded="groupDefaultExpanded"
      :rowDragManaged="true"
      :suppressMoveWhenRowDragging="true"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<Task> | null>(null);
    const columnDefs = ref<ColDef[]>([{ field: "assignee" }]);
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      headerName: "Task",
      rowDrag: true,
      flex: 2,
      minWidth: 200,
    });
    const rowData = ref<Task[] | null>(getData());
    const getRowId = ref<GetRowIdFunc>((params) => params.data.id);
    const getDataPath = ref<GetDataPath>((data) => data.path);
    const groupDefaultExpanded = ref(-1);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      columnDefs,
      autoGroupColumnDef,
      rowData,
      getRowId,
      getDataPath,
      groupDefaultExpanded,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Managed Row Drag with Tree Data (getDataPath)](https://www.ag-grid.com/examples/tree-data-row-dragging/tree-managed-row-drag-data-path/vue3)

### Multi-Row Dragging

Managed row dragging supports multi-row dragging, allowing users to select multiple rows and drag them together, including rows in different levels.

To enable this, set the grid options `rowDragMultiRow = true` together with `rowSelection.mode = 'multiRow'`.

For this example note the following:

- When you select multiple items and drag one of them, all items in the selection will be dragged.
- When you drag an item that is not selected while other items are selected, only the unselected item will be dragged.

#### Row Drag with Multi-Row Drag

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowDragModule,
  RowSelectionModule,
  RowSelectionOptions,
  enableDevValidations,
} from "ag-grid-community";
import { TreeDataModule } from "ag-grid-enterprise";
import { Task } from "./data";
import { getData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  TreeDataModule,
  RowDragModule,
  RowSelectionModule,
]);

const eGridDiv = document.getElementById("myGrid");

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :autoGroupColumnDef="autoGroupColumnDef"
      :rowData="rowData"
      :getRowId="getRowId"
      :treeData="true"
      :treeDataChildrenField="treeDataChildrenField"
      :groupDefaultExpanded="groupDefaultExpanded"
      :rowDragManaged="true"
      :rowDragMultiRow="true"
      :rowSelection="rowSelection"
      :suppressMoveWhenRowDragging="true"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<Task> | null>(null);
    const columnDefs = ref<ColDef[]>([{ field: "assignee" }]);
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      headerName: "Task",
      field: "title",
      rowDrag: true,
      flex: 2,
      minWidth: 200,
    });
    const rowData = ref<Task[] | null>(getData());
    const getRowId = ref<GetRowIdFunc>((params) => params.data.id);
    const treeDataChildrenField = ref("children");
    const groupDefaultExpanded = ref(-1);
    const rowSelection = ref<RowSelectionOptions | "single" | "multiple">({
      mode: "multiRow",
    });

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      columnDefs,
      autoGroupColumnDef,
      rowData,
      getRowId,
      treeDataChildrenField,
      groupDefaultExpanded,
      rowSelection,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Row Drag with Multi-Row Drag](https://www.ag-grid.com/examples/tree-data-row-dragging/tree-managed-multi-row-drag/vue3)

### Row Drag Insert Delay

When using Tree Data with Managed Row Dragging, the `rowDragInsertDelay` grid option sets a delay (in milliseconds) before a dragged row is inserted into a new parent node. The default value is `500` milliseconds. This delay helps prevent accidental moves when hovering over potential drop targets. If the target is a collapsed parent or a leaf node, the grid will expand the parent or convert the leaf into a parent after this delay, allowing the dragged row to be inserted as a child.

### Preventing Dropping on Certain Rows

The `isRowValidDropPosition` callback allows you to control whether a row drop is allowed during managed or unmanaged row dragging, and optionally override the rows, parent or position for the drop. This is useful for restricting where rows can be dropped or customizing drop behaviour. Returning an object allows instead to filter the rows to drop, or change the parent or the position of the drop.

This affects also the icon and label shown when dragging a row for both managed and unmanaged row dragging.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `isRowValidDropPosition` | `IsRowValidDropPositionCallback` |  |  | Called by drag and drop when rows are dragged over another row to conditionally prevent dropping the dragged row on the hovered row. The user can cancel the drop by returning `false` or customize the operation by returning a `IsRowValidDropPositionResult`. Module: [`RowDragModule`](https://www.ag-grid.com/vue-data-grid/modules/). |

In the example below, note that:

- A file cannot be converted to a folder, dropping a file or a folder into a file is blocked.
- The `READONLY` folder cannot change parent, and drag and drop into or from it is not allowed.

#### Managed Row Drag with Tree Data and isRowValidDropPosition

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IRowNode,
  IsRowValidDropPositionCallback,
  ModuleRegistry,
  RowDragModule,
  ValueFormatterParams,
  enableDevValidations,
} from "ag-grid-community";
import { TreeDataModule } from "ag-grid-enterprise";
import { getData } from "./data";
import { IFile } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  TreeDataModule,
  RowDragModule,
]);

/** Returns true if the row is a readonly folder, false if it is a file or a normal folder */
function isReadonlyFolder(row: IRowNode<IFile> | null) {
  return !!row && row.data?.type === "readonly-folder";
}

/** Returns true if the row is a file or folder inside a readonly folder */
function isInsideReadonlyFolder(row: IRowNode<IFile> | null): boolean {
  if (!row || !row.parent) {
    return false; // Root level
  }
  if (isReadonlyFolder(row.parent)) {
    return true;
  }
  return isInsideReadonlyFolder(row.parent);
}

const eGridDiv = document.getElementById("myGrid");

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :autoGroupColumnDef="autoGroupColumnDef"
      :rowData="rowData"
      :getRowId="getRowId"
      :treeData="true"
      :treeDataChildrenField="treeDataChildrenField"
      :groupDefaultExpanded="groupDefaultExpanded"
      :rowDragManaged="true"
      :suppressMoveWhenRowDragging="true"
      :isRowValidDropPosition="isRowValidDropPosition"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<IFile> | null>(null);
    const columnDefs = ref<ColDef[]>([
      {
        field: "type",
        headerName: "Type",
        width: 90,
      },
      {
        field: "dateModified",
        headerName: "Modified",
        width: 130,
      },
      {
        field: "size",
        aggFunc: "sum",
        width: 140,
        valueFormatter: (params: ValueFormatterParams<IFile, number>) =>
          params.value ? params.value.toFixed(1) + " MB" : "",
      },
    ]);
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      headerName: "Task",
      field: "name",
      rowDrag: true,
      flex: 2,
      minWidth: 200,
    });
    const rowData = ref<IFile[] | null>(getData());
    const getRowId = ref<GetRowIdFunc>((params) => params.data.id);
    const treeDataChildrenField = ref("children");
    const groupDefaultExpanded = ref(-1);
    const isRowValidDropPosition = ref<IsRowValidDropPositionCallback>(
      (params) => {
        let { newParent, rows, moved } = params;
        if (!moved) {
          return { allowed: false };
        }
        if (isReadonlyFolder(newParent) || isInsideReadonlyFolder(newParent)) {
          return { allowed: false }; // Prevent dropping into a readonly folder
        }
        // Filter out anything that is a readonly folder or inside a readonly folder
        rows = rows.filter(
          (row) => !isReadonlyFolder(row) && !isInsideReadonlyFolder(row),
        );
        if (newParent && newParent.data && newParent.data.type !== "folder") {
          // Block changing parents on anything that is not of type 'folder'
          return { newParent: null, rows };
        }
        return { rows };
      },
    );

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      columnDefs,
      autoGroupColumnDef,
      rowData,
      getRowId,
      treeDataChildrenField,
      groupDefaultExpanded,
      isRowValidDropPosition,
      onGridReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Managed Row Drag with Tree Data and isRowValidDropPosition](https://www.ag-grid.com/examples/tree-data-row-dragging/tree-managed-row-drag-filesystem/vue3)

### Persisting Row Order

These three examples below show how to persist the row order from the grid on to the server after a row drag operation has been completed.

Example with Parent IDs:

#### Extracting Managed Row Dragging Data with Parent IDs

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./style.css";
import {
  AutoGroupColumnDef,
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowDragModule,
  enableDevValidations,
} from "ag-grid-community";
import { TreeDataModule } from "ag-grid-enterprise";
import { Task } from "./data";
import { getData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ClientSideRowModelApiModule,
  TreeDataModule,
  RowDragModule,
]);

function extractRowData(api: GridApi<Task>) {
  const extractedData: Task[] = [];
  api.forEachLeafNode((node) => {
    let data = node.data!;
    const parentId = node.parent?.data?.id;
    if (data.parentId !== parentId) {
      // We create a new object only if the parentId has changed
      data = { ...data, parentId };
    }
    extractedData.push(data);
  });
  return extractedData;
}

function showExtractedRowData(api: GridApi<Task>) {
  const extractedRowData = extractRowData(api);
  const json = JSON.stringify(extractedRowData, null, 2);
  document.getElementById("extracted-data-content")!.textContent = json;
}

const eGridDiv = document.getElementById("myGrid");

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="container">
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :columnDefs="columnDefs"
        :autoGroupColumnDef="autoGroupColumnDef"
        :rowData="rowData"
        :getRowId="getRowId"
        :treeData="true"
        :treeDataParentIdField="treeDataParentIdField"
        :groupDefaultExpanded="groupDefaultExpanded"
        :rowDragManaged="true"
        :suppressMoveWhenRowDragging="true"
        @row-drag-end="onRowDragEnd"></ag-grid-vue>
        <div id="extracted-data-content-container">
          <pre id="extracted-data-content">output</pre>
        </div>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<Task> | null>(null);
    const columnDefs = ref<ColDef[]>([{ field: "assignee" }]);
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      headerName: "Task",
      field: "title",
      rowDrag: true,
      flex: 2,
      minWidth: 200,
    });
    const rowData = ref<Task[] | null>(getData());
    const getRowId = ref<GetRowIdFunc>((params) => params.data.id);
    const treeDataParentIdField = ref("parentId");
    const groupDefaultExpanded = ref(-1);

    function onRowDragEnd(event) {
      showExtractedRowData(event.api);
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      columnDefs,
      autoGroupColumnDef,
      rowData,
      getRowId,
      treeDataParentIdField,
      groupDefaultExpanded,
      onGridReady,
      onRowDragEnd,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Extracting Managed Row Dragging Data with Parent IDs](https://www.ag-grid.com/examples/tree-data-row-dragging/tree-managed-row-drag-extract-parent-id/vue3)

Example with Children arrays:

#### Extracting Managed Row Dragging Data with Children

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./style.css";
import {
  AutoGroupColumnDef,
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IRowNode,
  ModuleRegistry,
  RowApiModule,
  RowDragModule,
  enableDevValidations,
} from "ag-grid-community";
import { TreeDataModule } from "ag-grid-enterprise";
import { Task } from "./data";
import { getData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ClientSideRowModelApiModule,
  RowApiModule,
  TreeDataModule,
  RowDragModule,
]);

function arrayEquals<T>(a: T[], b: T[]) {
  return a === b || (a.length === b.length && a.every((v, i) => v === b[i]));
}

/** Recursively build the tree structure from a node */
function buildTree(node: IRowNode<Task>): Task {
  const data = node.data!;
  const oldChildren = data.children ?? [];
  const children = node.childrenAfterGroup?.map(buildTree) ?? [];
  if (!arrayEquals(oldChildren, children)) {
    // We create a new object only if the children have changed
    return { ...data, children: children.length > 0 ? children : undefined };
  }
  return data; // unchanged
}

/** Extract children for each node in the tree */
function extractRowData(rootNode: IRowNode<Task> | undefined) {
  return rootNode?.childrenAfterGroup?.map(buildTree) ?? [];
}

function showExtractedRowData(rootNode: IRowNode<Task> | undefined) {
  const extractedRowData = extractRowData(rootNode);
  const json = JSON.stringify(extractedRowData, null, 2);
  document.getElementById("extracted-data-content")!.textContent = json;
}

const eGridDiv = document.getElementById("myGrid");

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="container">
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :columnDefs="columnDefs"
        :autoGroupColumnDef="autoGroupColumnDef"
        :rowData="rowData"
        :getRowId="getRowId"
        :treeData="true"
        :treeDataChildrenField="treeDataChildrenField"
        :groupDefaultExpanded="groupDefaultExpanded"
        :rowDragManaged="true"
        :suppressMoveWhenRowDragging="true"
        @row-drag-end="onRowDragEnd"></ag-grid-vue>
        <div id="extracted-data-content-container">
          <pre id="extracted-data-content">output</pre>
        </div>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<Task> | null>(null);
    const columnDefs = ref<ColDef[]>([{ field: "assignee" }]);
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      headerName: "Task",
      field: "title",
      rowDrag: true,
      flex: 2,
      minWidth: 200,
    });
    const rowData = ref<Task[] | null>(getData());
    const getRowId = ref<GetRowIdFunc>((params) => params.data.id);
    const treeDataChildrenField = ref("children");
    const groupDefaultExpanded = ref(-1);

    function onRowDragEnd(event) {
      showExtractedRowData(event.rowsDrop?.rootNode);
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      columnDefs,
      autoGroupColumnDef,
      rowData,
      getRowId,
      treeDataChildrenField,
      groupDefaultExpanded,
      onGridReady,
      onRowDragEnd,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Extracting Managed Row Dragging Data with Children](https://www.ag-grid.com/examples/tree-data-row-dragging/tree-managed-row-drag-extract-children/vue3)

Example with Data Paths:

#### Extracting Managed Row Dragging Data with Data Paths

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./style.css";
import {
  AutoGroupColumnDef,
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetDataPath,
  GetRowIdFunc,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IRowNode,
  ModuleRegistry,
  RowApiModule,
  RowDragModule,
  enableDevValidations,
} from "ag-grid-community";
import { TreeDataModule } from "ag-grid-enterprise";
import { Task } from "./data";
import { getData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ClientSideRowModelApiModule,
  RowApiModule,
  TreeDataModule,
  RowDragModule,
]);

function arrayEquals<T>(a: T[], b: T[]) {
  return a === b || (a.length === b.length && a.every((v, i) => v === b[i]));
}

// Rebuild the data array, updating the path for each node if changed
function extractRowData(api: GridApi<Task>) {
  const extractedData: Task[] = [];
  api.forEachLeafNode((node) => {
    const data = node.data;
    if (data) {
      // Use getRoute() to rebuild the path
      const path = node.getRoute() ?? [];
      if (!arrayEquals(data.path, path)) {
        // Create a new object only if the path has changed
        extractedData.push({ ...data, path });
      } else {
        extractedData.push(data);
      }
    }
  });
  return extractedData;
}

function showExtractedRowData(api: GridApi<Task>) {
  const extractedRowData = extractRowData(api);
  const json = JSON.stringify(extractedRowData, null, 2);
  document.getElementById("extracted-data-content")!.textContent = json;
}

const eGridDiv = document.getElementById("myGrid");

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="container">
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :columnDefs="columnDefs"
        :autoGroupColumnDef="autoGroupColumnDef"
        :rowData="rowData"
        :getRowId="getRowId"
        :treeData="true"
        :getDataPath="getDataPath"
        :groupDefaultExpanded="groupDefaultExpanded"
        :rowDragManaged="true"
        :suppressMoveWhenRowDragging="true"
        @row-drag-end="onRowDragEnd"></ag-grid-vue>
        <div id="extracted-data-content-container">
          <pre id="extracted-data-content">output</pre>
        </div>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<Task> | null>(null);
    const columnDefs = ref<ColDef[]>([{ field: "assignee" }]);
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      headerName: "Task",
      rowDrag: true,
      flex: 2,
      minWidth: 200,
    });
    const rowData = ref<Task[] | null>(getData());
    const getRowId = ref<GetRowIdFunc>((params) => params.data.id);
    const getDataPath = ref<GetDataPath>((data) => data.path);
    const groupDefaultExpanded = ref(-1);

    function onRowDragEnd(event) {
      showExtractedRowData(event.api);
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      columnDefs,
      autoGroupColumnDef,
      rowData,
      getRowId,
      getDataPath,
      groupDefaultExpanded,
      onGridReady,
      onRowDragEnd,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Extracting Managed Row Dragging Data with Data Paths](https://www.ag-grid.com/examples/tree-data-row-dragging/tree-managed-row-drag-extract-data-path/vue3)

## Unmanaged Row Dragging

In order to have full control over row dragging, it is possible to provide a customized implementation of row dragging using [Unmanaged Row Dragging](https://www.ag-grid.com/vue-data-grid/row-dragging-unmanaged/). In this case, the application is responsible for maintaining the rowData state, handling the dragging events and updating the rowData based on the drag events fired by the grid.

### Tree Data with getDataPath

The example below shows [Tree Data](https://www.ag-grid.com/vue-data-grid/tree-data/) and row dragging with getDataPath where the following can be noted:

- The [Auto-Group Column](https://www.ag-grid.com/vue-data-grid/grouping/) has row drag `true` for all rows.
- The application moves the rows in the row data while the row drag is happening in the `onRowDragMove` event handler.
- While row dragging, the row move operation can be reverted by pressing `⎋ Escape` key.
- Is possible to reorder a row only inside its current parent by holding the `⇧ Shift` key and dragging it.
- The expanded/contracted state of a folder and all of its child folders is preserved when the folder is moved to a new parent.

#### Unmanaged Row Drag with Tree Data

```ts
import { createApp, defineComponent, ref } from "vue";

import {
  ClientSideRowModelModule,
  ModuleRegistry,
  RowDragModule,
  enableDevValidations,
} from "ag-grid-community";
import type {
  GridOptions,
  RowDragEndEvent,
  RowDragMoveEvent,
  ValueFormatterParams,
} from "ag-grid-community";
import { TreeDataModule } from "ag-grid-enterprise";
import { AgGridVue } from "ag-grid-vue3";

import { getData } from "./data";
import FileCellRenderer from "./fileCellRenderer";
import { moveFiles } from "./fileUtils";
import type { IFile } from "./fileUtils";
import "./style.css";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  RowDragModule,
  TreeDataModule,
]);

const VueExample = defineComponent({
  template: `
        <ag-grid-vue
            class="myGrid"
            :gridOptions="gridOptions"
            :rowData="rowData">
        </ag-grid-vue>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup() {
    const rowData = ref<IFile[] | null | undefined>(getData());
    const rowDataDragging = ref<IFile[] | null>(null);

    /** Called when row dragging start */
    const onRowDragEnter = () => {
      // Store the original row data to restore it the drag is cancelled
      const rowDataValue = rowData.value;
      rowDataDragging.value = rowDataValue ? [...rowDataValue] : null;
    };

    /** Called both when dragging and dropping (drag end) */
    const onRowDragMove = (
      event: RowDragMoveEvent<IFile> | RowDragEndEvent<IFile>,
    ) => {
      let target = event.overNode?.data;
      const source = event.node.data;
      if (rowData.value && source && source !== target) {
        const reorderOnly = event.event?.shiftKey;
        rowData.value = moveFiles(rowData.value, source, target, reorderOnly);
      }
    };

    /** Called when row dragging end, and the operation need to be committed */
    const onRowDragEnd = (
      event: RowDragEndEvent<IFile> | RowDragMoveEvent<IFile>,
    ) => {
      event.api.clearFocusedCell();
      rowDataDragging.value = null;
      onRowDragMove(event);
    };

    /** Called when row dragging is cancelled, for example, ESC key is pressed */
    const onRowDragCancel = () => {
      if (rowDataDragging.value) {
        // Restore the original row data before the drag started
        rowData.value = rowDataDragging.value;
        rowDataDragging.value = null;
      }
    };

    const gridOptions: GridOptions<IFile> = {
      columnDefs: [
        { field: "dateModified" },
        {
          field: "size",
          aggFunc: "sum",
          valueFormatter: (params: ValueFormatterParams<IFile, number>) =>
            params.value ? params.value.toFixed(1) + " MB" : "",
        },
      ],
      autoGroupColumnDef: {
        rowDrag: true,
        headerName: "Files",
        minWidth: 300,
        cellRendererParams: {
          suppressCount: true,
          innerRenderer: FileCellRenderer,
        },
      },
      defaultColDef: { flex: 1 },
      groupDefaultExpanded: -1,
      treeData: true,
      getDataPath: (data) => data.filePath,
      getRowId: (params) => params.data.id,
      onRowDragEnter: onRowDragEnter,
      onRowDragMove: onRowDragMove,
      onRowDragEnd: onRowDragEnd,
      onRowDragCancel: onRowDragCancel,
    };

    return {
      rowData,
      gridOptions,
    };
  },
});

createApp(VueExample).mount("#app");
```

[Live example: Unmanaged Row Drag with Tree Data](https://www.ag-grid.com/examples/tree-data-row-dragging/tree-unmanaged-row-drag/vue3)

### Tree Data with getDataPath, Highlighting the Drop Parent Row

The example above works, however it is not intuitive as the user is given no visual hint what folder will be the destination folder. The example below continues with the example above by providing hints to the user while the drag is in progress. From the example the following can be observed:

- The example registers for `onRowDragMove` events and works out which folder the mouse is over as the drag is happening.
- While the row is dragging, the application highlights the folder that is currently selected as the destination folder (called `potentialParent` in the example code).
- The application does NOT rearrange the rows as the drag is happening. As with the previous example, it waits for the `onRowDragEnd` event before updating the data.
- The example uses [Cell Class Rules](https://www.ag-grid.com/vue-data-grid/cell-styles/#cell-class-rules) to highlight the destination folder. The example adds a CSS class `hover-over` to all the cells of the destination folder.
- The example uses [Refresh Cells](https://www.ag-grid.com/vue-data-grid/view-refresh/#refresh-cells) to get the grid to execute the Cell Class Rules again over the destination folder when the destination folder changes.

#### Highlighting Unmanaged Row Drag with Tree Data

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./style.css";
import {
  AutoGroupColumnDef,
  CellClassParams,
  CellStyleModule,
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetDataPath,
  GetRowIdFunc,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ICellRendererParams,
  IRowNode,
  ModuleRegistry,
  RefreshCellsParams,
  RenderApiModule,
  RowDragEndEvent,
  RowDragLeaveEvent,
  RowDragModule,
  RowDragMoveEvent,
  ValueFormatterParams,
  enableDevValidations,
} from "ag-grid-community";
import { TreeDataModule } from "ag-grid-enterprise";
import { getData } from "./data";
import { getFileCssIcon, moveFiles } from "./fileUtils";
import { IFile } from "./fileUtils";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowDragModule,
  ClientSideRowModelApiModule,
  RenderApiModule,
  CellStyleModule,
  ClientSideRowModelModule,
  TreeDataModule,
]);

class FileCellRenderer {
  private eGui!: any;

  init(params: ICellRendererParams<IFile>) {
    const eGui = document.createElement("div");

    const eIcon = document.createElement("i");
    eIcon.className = getFileCssIcon(params.data?.type, params.value);

    const eFilename = document.createElement("span");
    eFilename.className = "filename";
    eFilename.innerText = params.value;

    eGui.appendChild(eIcon);
    eGui.appendChild(eFilename);

    this.eGui = eGui;
  }
  getGui() {
    return this.eGui;
  }
}

const valueFormatter = function (params: ValueFormatterParams<IFile, number>) {
  return params.value ? params.value.toFixed(1) + " MB" : "";
};

const cellClassRules = {
  "hover-over": (params: CellClassParams) => {
    return params.node === potentialParent;
  },
};

var potentialParent: any = null;

function setPotentialParentForNode(
  api: GridApi<IFile>,
  overNode: IRowNode<IFile> | undefined | null,
) {
  let newPotentialParent: IRowNode<IFile> | null = null;
  if (overNode) {
    if (overNode.data?.type === "folder") {
      // over a folder, we take the immediate row
      newPotentialParent = overNode;
    } else if (overNode.parent) {
      // over a file, we take the parent row (which will be a folder)
      newPotentialParent = overNode.parent;
    }
  }
  const alreadySelected = potentialParent === newPotentialParent;
  if (alreadySelected) {
    return; // no change
  }
  // we refresh the previous selection (if it exists) to clear
  // the highlighted and then the new selection.
  const rowsToRefresh = [];
  if (potentialParent) {
    rowsToRefresh.push(potentialParent);
  }
  if (newPotentialParent) {
    rowsToRefresh.push(newPotentialParent);
  }
  potentialParent = newPotentialParent;
  refreshRows(api, rowsToRefresh);
}

function refreshRows(api: GridApi, rowsToRefresh: IRowNode<IFile>[]) {
  const params: RefreshCellsParams<IFile> = {
    // refresh these rows only.
    rowNodes: rowsToRefresh,
    // because the grid does change detection, the refresh
    // will not happen because the underlying value has not
    // changed. to get around this, we force the refresh,
    // which skips change detection.
    force: true,
  };
  api.refreshCells(params);
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :defaultColDef="defaultColDef"
      :rowData="rowData"
      :treeData="true"
      :groupDefaultExpanded="groupDefaultExpanded"
      :getDataPath="getDataPath"
      :getRowId="getRowId"
      :autoGroupColumnDef="autoGroupColumnDef"
      @row-drag-move="onRowDragMove"
      @row-drag-leave="onRowDragLeave"
      @row-drag-end="onRowDragEnd"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<IFile> | null>(null);
    const columnDefs = ref<ColDef[]>([
      {
        field: "dateModified",
        cellClassRules: cellClassRules,
      },
      {
        field: "size",
        aggFunc: "sum",
        valueFormatter: valueFormatter,
        cellClassRules: cellClassRules,
      },
    ]);
    const defaultColDef = ref<ColDef>({
      flex: 1,
    });
    const rowData = ref<IFile[] | null>(getData());
    const groupDefaultExpanded = ref(-1);
    const getDataPath = ref<GetDataPath>((data: IFile) => data.filePath);
    const getRowId = ref<GetRowIdFunc>(({ data }) => data.id);
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      rowDrag: true,
      headerName: "Files",
      minWidth: 300,
      cellRendererParams: {
        suppressCount: true,
        innerRenderer: FileCellRenderer,
      },
      cellClassRules: {
        "hover-over": (params) => {
          return params.node === potentialParent;
        },
      },
    });

    function onRowDragMove(event: RowDragMoveEvent) {
      setPotentialParentForNode(event.api, event.overNode);
    }
    function onRowDragLeave(event: RowDragLeaveEvent) {
      // clear node to highlight
      setPotentialParentForNode(event.api, null);
    }
    function onRowDragEnd(event: RowDragEndEvent) {
      let target = event.overNode?.data;
      if (!potentialParent && target) {
        return; // no move
      }
      const source = event.node.data;
      const rowData = event.api.getGridOption("rowData");
      if (rowData && source && source !== target) {
        const newRowData = moveFiles(rowData, source, target);
        if (!newRowData) {
          console.log("invalid move");
        } else if (newRowData !== rowData) {
          event.api.setGridOption("rowData", newRowData);
        }
        gridApi.value!.clearFocusedCell();
      }
      // clear node to highlight
      setPotentialParentForNode(event.api, null);
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      rowData,
      groupDefaultExpanded,
      getDataPath,
      getRowId,
      autoGroupColumnDef,
      onGridReady,
      onRowDragMove,
      onRowDragLeave,
      onRowDragEnd,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Highlighting Unmanaged Row Drag with Tree Data](https://www.ag-grid.com/examples/tree-data-row-dragging/tree-unmanaged-row-drag-highlight/vue3)

### Tree Data with Parent ID

The following example shows how to implement unmanaged row dragging using the `parentId` approach, which is simpler and more direct than using `getDataPath`. The grid uses the `treeDataParentIdField` property, and utility functions are provided to move rows and update the tree structure. This approach is recommended for most use cases where your data is already structured with parent IDs.

This example also demonstrates how to provide custom drop indicators using the [`setRowDropPositionIndicator`](https://www.ag-grid.com/javascript-data-grid/grid-api/#reference-setRowDropPositionIndicator) API.

#### Unmanaged Row Drag with parentId

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowApiModule,
  RowDragCancelEvent,
  RowDragEndEvent,
  RowDragLeaveEvent,
  RowDragModule,
  ValueFormatterParams,
  enableDevValidations,
} from "ag-grid-community";
import { TreeDataModule } from "ag-grid-enterprise";
import { getData } from "./data";
import { IFile } from "./fileUtils";
import { getFileDropPosition, moveFiles } from "./fileUtils";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  RowApiModule,
  TreeDataModule,
  RowDragModule,
]);

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :getRowId="getRowId"
      :columnDefs="columnDefs"
      :autoGroupColumnDef="autoGroupColumnDef"
      :treeData="true"
      :treeDataParentIdField="treeDataParentIdField"
      :rowData="rowData"
      :animateRows="true"
      :groupDefaultExpanded="groupDefaultExpanded"
      @row-drag-move="onRowDragMove"
      @row-drag-end="onRowDragEnd"
      @row-drag-leave="onRowDragLeave"
      @row-drag-cancel="onRowDragCancel"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<IFile> | null>(null);
    const columnDefs = ref<ColDef[]>([
      {
        field: "type",
        headerName: "Type",
        width: 90,
      },
      {
        field: "dateModified",
        headerName: "Modified",
        width: 130,
      },
      {
        field: "size",
        aggFunc: "sum",
        width: 140,
        valueFormatter: (params: ValueFormatterParams<IFile, number>) =>
          params.value ? params.value.toFixed(1) + " MB" : "",
      },
    ]);
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      rowDrag: true,
      field: "name",
      headerName: "Files",
      minWidth: 400,
      cellRendererParams: { suppressCount: true },
    });
    const treeDataParentIdField = ref("parentId");
    const rowData = ref<IFile[] | null>(getData());
    const groupDefaultExpanded = ref(-1);

    function onRowDragMove(event: any) {
      const source = event.node.data;
      const target = event.overNode?.data;
      const reorderOnly = event.event?.shiftKey;
      const rowData = gridApi.value.getGridOption("rowData") ?? [];
      const indicator = getFileDropPosition(
        rowData,
        source,
        target,
        !!reorderOnly,
      );
      if (indicator) {
        // Find the row node by file reference
        const rowNode = gridApi.value.getRowNode(indicator.target.id);
        if (rowNode) {
          // Update the position indicator
          gridApi.value.setRowDropPositionIndicator({
            row: rowNode,
            dropIndicatorPosition: indicator.position,
          });
          return;
        }
      }
      gridApi.value.setRowDropPositionIndicator(null);
    }
    function onRowDragEnd(event: RowDragEndEvent<IFile>) {
      const source = event.node.data;
      const target = event.overNode?.data;
      if (!source || source === target) {
        gridApi.value.setRowDropPositionIndicator(null);
        return;
      }
      const reorderOnly = event.event?.shiftKey;
      const rowData = gridApi.value.getGridOption("rowData") ?? [];
      const indicator = getFileDropPosition(
        rowData,
        source,
        target,
        !!reorderOnly,
      );
      if (indicator) {
        const newRowData = moveFiles(rowData, indicator);
        if (newRowData !== rowData) {
          gridApi.value.setGridOption("rowData", newRowData);
        }
      }
      event.api.setRowDropPositionIndicator(null);
    }
    function onRowDragLeave(event: RowDragLeaveEvent<IFile>) {
      event.api.setRowDropPositionIndicator(null);
    }
    function onRowDragCancel(event: RowDragCancelEvent<IFile>) {
      event.api.setRowDropPositionIndicator(null);
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };
    function getRowId(params: GetRowIdParams<IFile>) {
      return params.data.id;
    }

    return {
      gridApi,
      getRowId,
      columnDefs,
      autoGroupColumnDef,
      treeDataParentIdField,
      rowData,
      groupDefaultExpanded,
      onGridReady,
      onRowDragMove,
      onRowDragEnd,
      onRowDragLeave,
      onRowDragCancel,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Unmanaged Row Drag with parentId](https://www.ag-grid.com/examples/tree-data-row-dragging/tree-unmanaged-row-drag-with-parent-id/vue3)

## See Also

- [Aggregation](https://www.ag-grid.com/vue-data-grid/aggregation/) for aggregating values in tree data
- [Editing Groups](https://www.ag-grid.com/vue-data-grid/grouping-edit/) for editing aggregated values with cascading updates to children
