---
title: "Notes"
enterprise: true
framework: vue
version: "36.1.0"
---

# Notes

Notes let users attach comments to individual cells without storing note text in row data. Cells with notes are marked in the grid, note actions are available from the context menu, hovering a noted cell opens the built-in resizable note editor, and `Shift + F2` opens or creates a note for the focused cell when notes are allowed.

## Enabling Notes

Notes are enabled by providing a `notesDataSource` to the grid. The datasource has two required methods `getNote()` and `setNote()` and is responsible for managing the state of the notes for the grid. To ensure stable row ids [getRowId()](https://www.ag-grid.com/vue-data-grid/row-ids/) is required.

To add a new note either right-click a cell to open the context menu or press `Shift + F2`. Hovering a cell with a note will display the note popup. Use `noteShowDelay` and `noteHideDelay` to control how quickly note popups appear and disappear on hover.

#### Notes

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FullWidthNotesDataSource,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  Note,
  NotesDataSource,
  NotesDataSourceGetNoteParams,
  NotesDataSourceSetNoteParams,
  enableDevValidations,
} from "ag-grid-community";
import { ContextMenuModule, NotesModule } from "ag-grid-enterprise";
import { OlympicWinner } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ContextMenuModule,
  NotesModule,
]);

const getNoteKey = (rowId: string, colId: string) => `${rowId}::${colId}`;

const noteStore = new Map<string, Note>([
  [
    getNoteKey("1", "athlete"),
    {
      text: "Confirm the athlete biography before the next review.",
    },
  ],
  [
    getNoteKey("3", "country"),
    {
      text: "Check the latest federation naming guidance for this country.",
    },
  ],
]);

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :notesDataSource="notesDataSource"
        :columnDefs="columnDefs"
        :rowData="rowData"
        :getRowId="getRowId"
        :defaultColDef="defaultColDef"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<OlympicWinner> | null>(null);
    const notesDataSource = ref<NotesDataSource | FullWidthNotesDataSource>({
      getNote: (params: NotesDataSourceGetNoteParams) =>
        noteStore.get(getNoteKey(params.rowNode.id!, params.column.getColId())),
      setNote: (params: NotesDataSourceSetNoteParams) => {
        const key = getNoteKey(params.rowNode.id!, params.column.getColId());
        if (params.note === undefined) {
          noteStore.delete(key);
        } else {
          noteStore.set(key, params.note);
        }
      },
    });
    const columnDefs = ref<ColDef[]>([
      { field: "athlete" },
      { field: "age", maxWidth: 110 },
      { field: "country" },
      { field: "year", maxWidth: 110 },
      { field: "sport" },
    ]);
    const rowData = ref<OlympicWinner[] | null>([
      {
        id: "1",
        athlete: "Michael Phelps",
        age: 23,
        country: "United States",
        year: 2008,
        sport: "Swimming",
      },
      {
        id: "2",
        athlete: "Usain Bolt",
        age: 22,
        country: "Jamaica",
        year: 2008,
        sport: "Athletics",
      },
      {
        id: "3",
        athlete: "Simone Biles",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Gymnastics",
      },
      {
        id: "4",
        athlete: "Katie Ledecky",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Swimming",
      },
      {
        id: "5",
        athlete: "Allyson Felix",
        age: 30,
        country: "United States",
        year: 2016,
        sport: "Athletics",
      },
      {
        id: "6",
        athlete: "Mo Farah",
        age: 33,
        country: "Great Britain",
        year: 2016,
        sport: "Athletics",
      },
    ]);
    const getRowId = ref<GetRowIdFunc>(
      ({ data }: GetRowIdParams<OlympicWinner>) => data.id,
    );
    const defaultColDef = ref<ColDef>({
      flex: 1,
      minWidth: 120,
    });

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

    return {
      gridApi,
      notesDataSource,
      columnDefs,
      rowData,
      getRowId,
      defaultColDef,
      onGridReady,
    };
  },
});

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

[Live example: Notes](https://www.ag-grid.com/examples/notes/notes/vue3)

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `notesDataSource` | `NotesDataSource \| FullWidthNotesDataSource` |  |  | Provide a data source to control where notes are stored and retrieved. Can be updated to enable, disable, or replace Notes at runtime. Module: [`NotesModule`](https://www.ag-grid.com/vue-data-grid/modules/). |

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

this.getRowId = (params) => String(params.data.id);
this.notesDataSource = {
    getNote: ({ rowNode, column }) => notesStore[rowNode.id]?.[column.getColId()],
    setNote: ({ rowNode, column, note }) => {
        const row = (notesStore[rowNode.id] ??= {});

        if (note === undefined) {
            delete row[column.getColId()];
        } else {
            row[column.getColId()] = note;
        }
    },
};
```

## Notes Trigger

Use `noteTrigger` to control whether existing notes open on hover or on left-click. `hover` is the default. When using `click`, `noteShowDelay` no longer applies, but `noteHideDelay` still controls how long the note stays open after the pointer leaves the cell or popup.

Click mode follows the same passive note rules as hover mode: notes still do not open for the cell currently being edited.

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

this.noteTrigger = 'click';
```

#### Notes Trigger

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FullWidthNotesDataSource,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  Note,
  NotesDataSource,
  NotesDataSourceGetNoteParams,
  NotesDataSourceSetNoteParams,
  enableDevValidations,
} from "ag-grid-community";
import { ContextMenuModule, NotesModule } from "ag-grid-enterprise";
import { OlympicWinner } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ContextMenuModule,
  NotesModule,
]);

const getNoteKey = (rowId: string, colId: string) => `${rowId}::${colId}`;

const noteStore = new Map<string, Note>([
  [
    getNoteKey("1", "athlete"),
    {
      text: "Click a noted cell to open this note instead of hovering it.",
    },
  ],
  [
    getNoteKey("3", "country"),
    {
      text: "Click trigger still uses the same note datasource and built-in popup.",
    },
  ],
]);

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :notesDataSource="notesDataSource"
        :columnDefs="columnDefs"
        :rowData="rowData"
        :getRowId="getRowId"
        :defaultColDef="defaultColDef"
        :noteTrigger="noteTrigger"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<OlympicWinner> | null>(null);
    const notesDataSource = ref<NotesDataSource | FullWidthNotesDataSource>({
      getNote: (params: NotesDataSourceGetNoteParams) =>
        noteStore.get(getNoteKey(params.rowNode.id!, params.column.getColId())),
      setNote: (params: NotesDataSourceSetNoteParams) => {
        const key = getNoteKey(params.rowNode.id!, params.column.getColId());
        if (params.note === undefined) {
          noteStore.delete(key);
        } else {
          noteStore.set(key, params.note);
        }
      },
    });
    const columnDefs = ref<ColDef[]>([
      { field: "athlete" },
      { field: "age", maxWidth: 110 },
      { field: "country" },
      { field: "year", maxWidth: 110 },
      { field: "sport" },
    ]);
    const rowData = ref<OlympicWinner[] | null>([
      {
        id: "1",
        athlete: "Michael Phelps",
        age: 23,
        country: "United States",
        year: 2008,
        sport: "Swimming",
      },
      {
        id: "2",
        athlete: "Usain Bolt",
        age: 22,
        country: "Jamaica",
        year: 2008,
        sport: "Athletics",
      },
      {
        id: "3",
        athlete: "Simone Biles",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Gymnastics",
      },
      {
        id: "4",
        athlete: "Katie Ledecky",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Swimming",
      },
      {
        id: "5",
        athlete: "Allyson Felix",
        age: 30,
        country: "United States",
        year: 2016,
        sport: "Athletics",
      },
      {
        id: "6",
        athlete: "Mo Farah",
        age: 33,
        country: "Great Britain",
        year: 2016,
        sport: "Athletics",
      },
    ]);
    const getRowId = ref<GetRowIdFunc>(
      ({ data }: GetRowIdParams<OlympicWinner>) => data.id,
    );
    const defaultColDef = ref<ColDef>({
      flex: 1,
      minWidth: 120,
    });
    const noteTrigger = ref<"hover" | "click">("click");

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

    return {
      gridApi,
      notesDataSource,
      columnDefs,
      rowData,
      getRowId,
      defaultColDef,
      noteTrigger,
      onGridReady,
    };
  },
});

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

[Live example: Notes Trigger](https://www.ag-grid.com/examples/notes/notes-trigger/vue3)

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `noteTrigger` | `'hover' \| 'click'` |  | `'hover'` | Changes how existing notes are opened. - `'hover'` - Existing notes open when hovering a noted cell or full width row. - `'click'` - Existing notes open when clicking a noted cell or full width row. Module: [`NotesModule`](https://www.ag-grid.com/vue-data-grid/modules/). |
| `noteShowDelay` | `number` |  | `180` | The delay in milliseconds before a note is shown when hovering a noted cell. Only applies when `noteTrigger = 'hover'`. Module: [`NotesModule`](https://www.ag-grid.com/vue-data-grid/modules/). |
| `noteHideDelay` | `number` |  | `220` | The delay in milliseconds before a note is hidden after the pointer leaves a noted cell or note popup. Module: [`NotesModule`](https://www.ag-grid.com/vue-data-grid/modules/). |

## Metadata

Built-in note metadata, including `author`, `createdAt`, and `updatedAt`, is rendered exactly as provided by your datasource.

The built-in note editor only updates the note text. If notes created from the built-in UI should also include metadata, stamp it inside `notesDataSource.setNote()`. The example below includes an `Authenticated User` input to simulate the current user being stamped into saved notes.

#### Metadata

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FullWidthNotesDataSource,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  Note,
  NotesDataSource,
  NotesDataSourceGetNoteParams,
  NotesDataSourceSetNoteParams,
  enableDevValidations,
} from "ag-grid-community";
import { ContextMenuModule, NotesModule } from "ag-grid-enterprise";
import { OlympicWinner } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ContextMenuModule,
  NotesModule,
]);

const getNoteKey = (rowId: string, colId: string) => `${rowId}::${colId}`;

const getDisplayTimestamp = () =>
  new Intl.DateTimeFormat("en-GB", {
    dateStyle: "medium",
    timeStyle: "short",
  }).format(new Date());

const getCurrentUser = () => {
  const user = (
    document.getElementById("current-user") as HTMLInputElement | null
  )?.value.trim();
  return user || undefined;
};

const noteStore = new Map<string, Note>([
  [
    getNoteKey("1", "athlete"),
    {
      text: "Confirm the athlete biography before the next review.",
      author: "AG Grid",
      createdAt: "26 Mar 2026, 10:30",
      updatedAt: "29 Mar 2026, 09:15",
    },
  ],
  [
    getNoteKey("3", "country"),
    {
      text: "Check the latest federation naming guidance for this country.",
      author: "Chris",
      createdAt: "24 Mar 2026, 16:10",
      updatedAt: "27 Mar 2026, 14:30",
    },
  ],
]);

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <div class="example-toolbar">
        <label class="example-field">
          <span>Authenticated User</span>
          <input id="current-user" type="text" value="AG Grid">
          </label>
        </div>
        <ag-grid-vue
          style="width: 100%; height: 100%;"
          @grid-ready="onGridReady"
          :notesDataSource="notesDataSource"
          :columnDefs="columnDefs"
          :rowData="rowData"
          :getRowId="getRowId"
          :defaultColDef="defaultColDef"></ag-grid-vue>
        </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<OlympicWinner> | null>(null);
    const notesDataSource = ref<NotesDataSource | FullWidthNotesDataSource>({
      getNote: (params: NotesDataSourceGetNoteParams) =>
        noteStore.get(getNoteKey(params.rowNode.id!, params.column.getColId())),
      setNote: (params: NotesDataSourceSetNoteParams) => {
        const key = getNoteKey(params.rowNode.id!, params.column.getColId());
        const existingNote = noteStore.get(key);
        if (params.note === undefined) {
          noteStore.delete(key);
        } else {
          noteStore.set(key, {
            ...existingNote,
            ...params.note,
            author: getCurrentUser(),
            createdAt: existingNote?.createdAt ?? getDisplayTimestamp(),
            updatedAt: getDisplayTimestamp(),
          });
        }
      },
    });
    const columnDefs = ref<ColDef[]>([
      { field: "athlete" },
      { field: "age", maxWidth: 110 },
      { field: "country" },
      { field: "year", maxWidth: 110 },
      { field: "sport" },
    ]);
    const rowData = ref<OlympicWinner[] | null>([
      {
        id: "1",
        athlete: "Michael Phelps",
        age: 23,
        country: "United States",
        year: 2008,
        sport: "Swimming",
      },
      {
        id: "2",
        athlete: "Usain Bolt",
        age: 22,
        country: "Jamaica",
        year: 2008,
        sport: "Athletics",
      },
      {
        id: "3",
        athlete: "Simone Biles",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Gymnastics",
      },
      {
        id: "4",
        athlete: "Katie Ledecky",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Swimming",
      },
      {
        id: "5",
        athlete: "Allyson Felix",
        age: 30,
        country: "United States",
        year: 2016,
        sport: "Athletics",
      },
      {
        id: "6",
        athlete: "Mo Farah",
        age: 33,
        country: "Great Britain",
        year: 2016,
        sport: "Athletics",
      },
    ]);
    const getRowId = ref<GetRowIdFunc>(
      ({ data }: GetRowIdParams<OlympicWinner>) => data.id,
    );
    const defaultColDef = ref<ColDef>({
      flex: 1,
      minWidth: 120,
    });

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

    return {
      gridApi,
      notesDataSource,
      columnDefs,
      rowData,
      getRowId,
      defaultColDef,
      onGridReady,
    };
  },
});

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

[Live example: Metadata](https://www.ag-grid.com/examples/notes/notes-metadata/vue3)

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

const getCurrentUser = () => document.getElementById('current-user').value;
const getDisplayTimestamp = () => new Date().toLocaleString('en-GB');

this.notesDataSource = {
    setNote: ({ rowNode, column, note }) => {
        const row = (noteStore[rowNode.id] ??= {});
        const colId = column.getColId();
        const existingNote = row[colId];

        if (note === undefined) {
            delete row[colId];
        } else {
            row[colId] = {
                ...existingNote,
                ...note,
                author: getCurrentUser(),
                createdAt: existingNote?.createdAt ?? getDisplayTimestamp(),
                updatedAt: getDisplayTimestamp(),
            };
        }
    },
};
```

### Custom Data

Use `note.metadata` to store any application-defined data alongside the built-in note fields. The Grid preserves this data when the built-in editor updates an existing note, but the built-in note popup does not render it.

The example below stores `metadata.type` and `metadata.priority` on notes, then uses application-level cell classes and CSS variables to style note indicators differently. This keeps custom Note data in your datasource while letting your app decide how that data should affect presentation.

#### Custom Data

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  CellClassParams,
  CellStyleModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FullWidthNotesDataSource,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  Note,
  NotesDataSource,
  NotesDataSourceGetNoteParams,
  NotesDataSourceSetNoteParams,
  enableDevValidations,
} from "ag-grid-community";
import { ContextMenuModule, NotesModule } from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  CellStyleModule,
  ClientSideRowModelModule,
  ContextMenuModule,
  NotesModule,
]);

interface OlympicWinner {
  id: string;
  athlete: string;
  age: number;
  country: string;
  year: number;
  sport: string;
}
interface NoteMetadata {
  type: "team" | "review" | "personal";
  priority: "high" | "medium" | "low";
}

const getNoteKey = (rowId: string, colId: string) => `${rowId}::${colId}`;

const defaultMetadataByColumn: Record<string, NoteMetadata> = {
  athlete: { type: "team", priority: "high" },
  country: { type: "review", priority: "medium" },
  sport: { type: "personal", priority: "low" },
};

const noteStore = new Map<string, Note<NoteMetadata>>([
  [
    getNoteKey("1", "athlete"),
    {
      text: "This team note needs a response before the next content review.",
      author: "AG Grid",
      updatedAt: "29 Mar 2026, 09:15",
      metadata: { type: "team", priority: "high" },
    },
  ],
  [
    getNoteKey("3", "country"),
    {
      text: "This review note tracks external naming guidance for this country.",
      author: "Chris",
      updatedAt: "27 Mar 2026, 14:30",
      metadata: { type: "review", priority: "medium" },
    },
  ],
  [
    getNoteKey("5", "sport"),
    {
      text: "This personal note is a lightweight reminder for later follow-up.",
      author: "Martha",
      updatedAt: "28 Mar 2026, 11:45",
      metadata: { type: "personal", priority: "low" },
    },
  ],
]);

const getCellNoteMetadata = (
  rowId: string,
  colId: string,
): NoteMetadata | undefined =>
  noteStore.get(getNoteKey(rowId, colId))?.metadata;

const getNoteClasses = (
  metadata: NoteMetadata | undefined,
): string[] | undefined =>
  metadata
    ? [`note-type-${metadata.type}`, `note-priority-${metadata.priority}`]
    : undefined;

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <div class="example-toolbar">
        <div class="legend-chip note-type-team note-priority-high">Team · High</div>
        <div class="legend-chip note-type-review note-priority-medium">Review · Medium</div>
        <div class="legend-chip note-type-personal note-priority-low">Personal · Low</div>
      </div>
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :notesDataSource="notesDataSource"
        :columnDefs="columnDefs"
        :rowData="rowData"
        :getRowId="getRowId"
        :defaultColDef="defaultColDef"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<OlympicWinner> | null>(null);
    const notesDataSource = ref<NotesDataSource | FullWidthNotesDataSource>({
      getNote: (params: NotesDataSourceGetNoteParams) =>
        noteStore.get(getNoteKey(params.rowNode.id!, params.column.getColId())),
      setNote: (params: NotesDataSourceSetNoteParams<NoteMetadata>) => {
        const key = getNoteKey(params.rowNode.id!, params.column.getColId());
        const existingNote = noteStore.get(key);
        if (params.note === undefined) {
          noteStore.delete(key);
        } else {
          noteStore.set(key, {
            ...existingNote,
            ...params.note,
            metadata:
              existingNote?.metadata ??
              defaultMetadataByColumn[params.column.getColId()],
          });
        }
      },
    });
    const columnDefs = ref<ColDef[]>([
      { field: "athlete" },
      { field: "age", maxWidth: 110 },
      { field: "country" },
      { field: "year", maxWidth: 110 },
      { field: "sport" },
    ]);
    const rowData = ref<OlympicWinner[] | null>([
      {
        id: "1",
        athlete: "Michael Phelps",
        age: 23,
        country: "United States",
        year: 2008,
        sport: "Swimming",
      },
      {
        id: "2",
        athlete: "Usain Bolt",
        age: 22,
        country: "Jamaica",
        year: 2008,
        sport: "Athletics",
      },
      {
        id: "3",
        athlete: "Simone Biles",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Gymnastics",
      },
      {
        id: "4",
        athlete: "Katie Ledecky",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Swimming",
      },
      {
        id: "5",
        athlete: "Allyson Felix",
        age: 30,
        country: "United States",
        year: 2016,
        sport: "Athletics",
      },
      {
        id: "6",
        athlete: "Mo Farah",
        age: 33,
        country: "Great Britain",
        year: 2016,
        sport: "Athletics",
      },
    ]);
    const getRowId = ref<GetRowIdFunc>(
      ({ data }: GetRowIdParams<OlympicWinner>) => data.id,
    );
    const defaultColDef = ref<ColDef>({
      flex: 1,
      minWidth: 120,
      cellClass: (params: CellClassParams<OlympicWinner>) =>
        getNoteClasses(
          getCellNoteMetadata(params.node.id!, params.column.getColId()),
        ),
    });

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

    return {
      gridApi,
      notesDataSource,
      columnDefs,
      rowData,
      getRowId,
      defaultColDef,
      onGridReady,
    };
  },
});

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

[Live example: Custom Data](https://www.ag-grid.com/examples/notes/notes-custom-data/vue3)

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

this.defaultColDef = {
    cellClass: ({ node, column }) => {
        const colId = column.getColId();
        const metadata = getCellNoteMetadata(node.id, colId);

        if (metadata) {
            const { type, priority } = metadata;
            return [`note-type-${type}`, `note-priority-${priority}`]
        }
    },
};
this.notesDataSource = {
    setNote: ({ rowNode, column, note }) => {
        const key = `${rowNode.id}::${column.getColId()}`;
        const existingNote = noteStore.get(key);

        if (note === undefined) {
            noteStore.delete(key);
        } else {
            noteStore.set(key, {
                ...existingNote,
                ...note,
                metadata: existingNote?.metadata ?? { type: 'team', priority: 'medium' },
            });
        }
    },
};
```

## Read-Only Notes

Set `Note.readOnly = true` to make a note view-only. Read-only notes can still be opened from hover or click based on `noteTrigger`, the context menu, or `Shift + F2`, but they cannot be edited or removed through the built-in UI. The grid API can still update or remove read-only notes programmatically.

`Athlete` has an editable note. `Country` and `Sport` have **read-only** notes, so they can be viewed but not edited or removed through the built-in UI.

#### Read-Only Notes

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FullWidthNotesDataSource,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  Note,
  NotesDataSource,
  NotesDataSourceGetNoteParams,
  NotesDataSourceSetNoteParams,
  enableDevValidations,
} from "ag-grid-community";
import { ContextMenuModule, NotesModule } from "ag-grid-enterprise";
import { OlympicWinner } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ContextMenuModule,
  NotesModule,
]);

const getNoteKey = (rowId: string, colId: string) => `${rowId}::${colId}`;

const noteStore = new Map<string, Note>([
  [
    getNoteKey("1", "athlete"),
    {
      text: "This note can still be edited from hover, the context menu, or Shift + F2.",
      author: "AG Grid",
      updatedAt: "29 Mar 2026, 09:15",
    },
  ],
  [
    getNoteKey("3", "country"),
    {
      text: "This note is read-only, so the built-in UI opens it in view-only mode.",
      author: "AG Grid",
      updatedAt: "27 Mar 2026, 14:30",
      readOnly: true,
    },
  ],
  [
    getNoteKey("5", "sport"),
    {
      text: "Read-only notes can still show metadata and can still be opened with Shift + F2.",
      updatedAt: "28 Mar 2026, 11:45",
      readOnly: true,
    },
  ],
]);

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :notesDataSource="notesDataSource"
      :columnDefs="columnDefs"
      :rowData="rowData"
      :getRowId="getRowId"
      :defaultColDef="defaultColDef"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<OlympicWinner> | null>(null);
    const notesDataSource = ref<NotesDataSource | FullWidthNotesDataSource>({
      getNote: (params: NotesDataSourceGetNoteParams) =>
        noteStore.get(getNoteKey(params.rowNode.id!, params.column.getColId())),
      setNote: (params: NotesDataSourceSetNoteParams) => {
        const key = getNoteKey(params.rowNode.id!, params.column.getColId());
        if (params.note === undefined) {
          noteStore.delete(key);
        } else {
          noteStore.set(key, params.note);
        }
      },
    });
    const columnDefs = ref<ColDef[]>([
      { field: "athlete" },
      { field: "age", maxWidth: 110 },
      { field: "country" },
      { field: "year", maxWidth: 110 },
      { field: "sport" },
    ]);
    const rowData = ref<OlympicWinner[] | null>([
      {
        id: "1",
        athlete: "Michael Phelps",
        age: 23,
        country: "United States",
        year: 2008,
        sport: "Swimming",
      },
      {
        id: "2",
        athlete: "Usain Bolt",
        age: 22,
        country: "Jamaica",
        year: 2008,
        sport: "Athletics",
      },
      {
        id: "3",
        athlete: "Simone Biles",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Gymnastics",
      },
      {
        id: "4",
        athlete: "Katie Ledecky",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Swimming",
      },
      {
        id: "5",
        athlete: "Allyson Felix",
        age: 30,
        country: "United States",
        year: 2016,
        sport: "Athletics",
      },
      {
        id: "6",
        athlete: "Mo Farah",
        age: 33,
        country: "Great Britain",
        year: 2016,
        sport: "Athletics",
      },
    ]);
    const getRowId = ref<GetRowIdFunc>(
      ({ data }: GetRowIdParams<OlympicWinner>) => data.id,
    );
    const defaultColDef = ref<ColDef>({
      flex: 1,
      minWidth: 120,
    });

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

    return {
      gridApi,
      notesDataSource,
      columnDefs,
      rowData,
      getRowId,
      defaultColDef,
      onGridReady,
    };
  },
});

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

[Live example: Read-Only Notes](https://www.ag-grid.com/examples/notes/notes-read-only/vue3)

```ts
noteStore.set(noteKey('3', 'country'), {
    text: 'Check the latest federation naming guidance for this country.',
    author: 'AG Grid',
    updatedAt: '27 Mar 2026, 14:30',
    readOnly: true,
});
```

## Suppressing Note Actions

Use `colDef.suppressNoteActions` to suppress built-in note actions for a column or specific row. Suppressed cells still allow existing notes to be viewed through the configured note trigger and through `getNote()`, but add/edit/remove actions and note creation shortcuts are blocked.

`Year` and `Sport` suppress built-in note actions. Existing notes on those cells can still be viewed normally, while other columns keep the standard add, edit, and remove behaviour.

#### Suppressing Notes

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FullWidthNotesDataSource,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  Note,
  NotesDataSource,
  NotesDataSourceGetNoteParams,
  NotesDataSourceSetNoteParams,
  enableDevValidations,
} from "ag-grid-community";
import { ContextMenuModule, NotesModule } from "ag-grid-enterprise";
import { OlympicWinner } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ContextMenuModule,
  NotesModule,
]);

const getNoteKey = (rowId: string, colId: string) => `${rowId}::${colId}`;

const noteStore = new Map<string, Note>([
  [
    getNoteKey("1", "athlete"),
    {
      text: "This cell still allows the full built-in note workflow.",
      updatedAt: "29 Mar 2026, 09:15",
    },
  ],
  [
    getNoteKey("2", "year"),
    {
      text: "Year suppresses note actions, but existing notes still open on hover.",
      updatedAt: "28 Mar 2026, 11:45",
    },
  ],
  [
    getNoteKey("5", "sport"),
    {
      text: "Sport also suppresses note actions for the entire column.",
      updatedAt: "27 Mar 2026, 14:30",
    },
  ],
]);

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :notesDataSource="notesDataSource"
      :columnDefs="columnDefs"
      :rowData="rowData"
      :getRowId="getRowId"
      :defaultColDef="defaultColDef"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<OlympicWinner> | null>(null);
    const notesDataSource = ref<NotesDataSource | FullWidthNotesDataSource>({
      getNote: (params: NotesDataSourceGetNoteParams) =>
        noteStore.get(getNoteKey(params.rowNode.id!, params.column.getColId())),
      setNote: (params: NotesDataSourceSetNoteParams) => {
        const key = getNoteKey(params.rowNode.id!, params.column.getColId());
        if (params.note === undefined) {
          noteStore.delete(key);
        } else {
          noteStore.set(key, params.note);
        }
      },
    });
    const columnDefs = ref<ColDef[]>([
      { field: "athlete" },
      { field: "age", maxWidth: 110 },
      { field: "country" },
      { field: "year", maxWidth: 110, suppressNoteActions: true },
      { field: "sport", suppressNoteActions: true },
    ]);
    const rowData = ref<OlympicWinner[] | null>([
      {
        id: "1",
        athlete: "Michael Phelps",
        age: 23,
        country: "United States",
        year: 2008,
        sport: "Swimming",
      },
      {
        id: "2",
        athlete: "Usain Bolt",
        age: 22,
        country: "Jamaica",
        year: 2008,
        sport: "Athletics",
      },
      {
        id: "3",
        athlete: "Simone Biles",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Gymnastics",
      },
      {
        id: "4",
        athlete: "Katie Ledecky",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Swimming",
      },
      {
        id: "5",
        athlete: "Allyson Felix",
        age: 30,
        country: "United States",
        year: 2016,
        sport: "Athletics",
      },
      {
        id: "6",
        athlete: "Mo Farah",
        age: 33,
        country: "Great Britain",
        year: 2016,
        sport: "Athletics",
      },
    ]);
    const getRowId = ref<GetRowIdFunc>(
      ({ data }: GetRowIdParams<OlympicWinner>) => data.id,
    );
    const defaultColDef = ref<ColDef>({
      flex: 1,
      minWidth: 120,
    });

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

    return {
      gridApi,
      notesDataSource,
      columnDefs,
      rowData,
      getRowId,
      defaultColDef,
      onGridReady,
    };
  },
});

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

[Live example: Suppressing Notes](https://www.ag-grid.com/examples/notes/notes-suppressing/vue3)

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

this.columnDefs = [
    { field: 'athlete' },
    { field: 'year', suppressNoteActions: true },
    { field: 'sport', suppressNoteActions: params => params.data?.sport === 'Swimming' },
];
```

## Full Width Rows

To support adding notes to full width rows ensure the `notesDataSource` implements the `FullWidthNotesDataSource` interface. The interface requires `supportsFullWidthRows:true` to be set on the `notesDataSource`.

The example below includes both regular notes on cells and notes on full width rows in the same datasource. Full width rows use a separate note identity. Instead of receiving a `column`, the datasource receives `location: 'fullWidthRow'` and an optional `pinned` value when in `embedFullWidthRows` mode.

#### Notes with Full Width Rows

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FullWidthNotesDataSource,
  FullWidthNotesDataSourceGetNoteParams,
  FullWidthNotesDataSourceSetNoteParams,
  GetRowHeight,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ICellRendererComp,
  ICellRendererParams,
  IsFullWidthRow,
  IsFullWidthRowParams,
  ModuleRegistry,
  Note,
  NotesDataSource,
  RowHeightParams,
  enableDevValidations,
} from "ag-grid-community";
import { ContextMenuModule, NotesModule } from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ContextMenuModule,
  NotesModule,
]);

class FullWidthCellRenderer implements ICellRendererComp {
  private eGui!: HTMLElement;

  public init(params: ICellRendererParams<OlympicWinner>): void {
    const data = params.data!;
    const eGui = document.createElement("div");
    eGui.className = "notes-full-width-row";
    eGui.innerHTML = `
            <div class="notes-full-width-row__title">${data.athlete}</div>
            <div class="notes-full-width-row__details">
                <span>${data.country}</span>
                <span>${data.year}</span>
                <span>${data.sport}</span>
            </div>
        `;
    this.eGui = eGui;
  }

  public getGui(): HTMLElement {
    return this.eGui;
  }

  public refresh(): boolean {
    return false;
  }
}

interface OlympicWinner extends Partial<IOlympicData> {
  id: string;
  featured?: boolean;
}

const noteStore = new Map<string, Note>([
  [
    "cell::1::athlete",
    {
      text: "This note belongs to a regular cell.",
    },
  ],
  [
    "fullWidth::2",
    {
      text: "This note belongs to a full width row. The datasource receives location: fullWidthRow instead of a column.",
    },
  ],
]);

const getNoteKey = (rowId: string, colId: string) => `cell::${rowId}::${colId}`;

const getFullWidthNoteKey = (rowId: string) => `fullWidth::${rowId}`;

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :notesDataSource="notesDataSource"
        :columnDefs="columnDefs"
        :rowData="rowData"
        :getRowId="getRowId"
        :defaultColDef="defaultColDef"
        :getRowHeight="getRowHeight"
        :isFullWidthRow="isFullWidthRow"
        :fullWidthCellRenderer="fullWidthCellRenderer"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<OlympicWinner> | null>(null);
    const notesDataSource = ref<NotesDataSource | FullWidthNotesDataSource>({
      supportsFullWidthRows: true,
      getNote: (params: FullWidthNotesDataSourceGetNoteParams) =>
        params.location === "fullWidthRow"
          ? noteStore.get(getFullWidthNoteKey(params.rowNode.id!))
          : noteStore.get(
              getNoteKey(params.rowNode.id!, params.column.getColId()),
            ),
      setNote: (params: FullWidthNotesDataSourceSetNoteParams) => {
        const key =
          params.location === "fullWidthRow"
            ? getFullWidthNoteKey(params.rowNode.id!)
            : getNoteKey(params.rowNode.id!, params.column.getColId());
        if (params.note === undefined) {
          noteStore.delete(key);
        } else {
          noteStore.set(key, params.note);
        }
      },
    });
    const columnDefs = ref<ColDef[]>([
      { field: "athlete" },
      { field: "age", maxWidth: 110 },
      { field: "country" },
      { field: "year", maxWidth: 110 },
      { field: "sport" },
    ]);
    const rowData = ref<OlympicWinner[] | null>([
      {
        id: "1",
        athlete: "Michael Phelps",
        age: 23,
        country: "United States",
        year: 2008,
        sport: "Swimming",
      },
      {
        id: "2",
        athlete: "Usain Bolt",
        age: 22,
        country: "Jamaica",
        year: 2008,
        sport: "Athletics",
        featured: true,
      },
      {
        id: "3",
        athlete: "Simone Biles",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Gymnastics",
      },
      {
        id: "4",
        athlete: "Katie Ledecky",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Swimming",
      },
      {
        id: "5",
        athlete: "Allyson Felix",
        age: 30,
        country: "United States",
        year: 2016,
        sport: "Athletics",
        featured: true,
      },
      {
        id: "6",
        athlete: "Mo Farah",
        age: 33,
        country: "Great Britain",
        year: 2016,
        sport: "Athletics",
      },
    ]);
    const getRowId = ref<GetRowIdFunc>(
      ({ data }: GetRowIdParams<OlympicWinner>) => data.id,
    );
    const defaultColDef = ref<ColDef>({
      flex: 1,
      minWidth: 120,
    });
    const getRowHeight = ref<GetRowHeight>(
      (params: RowHeightParams<OlympicWinner>) => {
        if (params.data?.featured) {
          return 96;
        }
      },
    );
    const isFullWidthRow = ref<IsFullWidthRow>(
      (params: IsFullWidthRowParams<OlympicWinner>) =>
        !!params.rowNode.data?.featured,
    );
    const fullWidthCellRenderer = ref(FullWidthCellRenderer);

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

    return {
      gridApi,
      notesDataSource,
      columnDefs,
      rowData,
      getRowId,
      defaultColDef,
      getRowHeight,
      isFullWidthRow,
      fullWidthCellRenderer,
      onGridReady,
    };
  },
});

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

[Live example: Notes with Full Width Rows](https://www.ag-grid.com/examples/notes/notes-full-width/vue3)

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

const notesStore = new Map();

this.getRowId = (params) => String(params.data.id);
this.notesDataSource = {
    // Enable support for Full Width Rows
    supportsFullWidthRows: true,
    getNote: (params) =>
        params.location === 'fullWidthRow'
            ? notesStore.get(getFullWidthNoteKey(params.rowNode.id))
            : notesStore.get(getNoteKey(params.rowNode.id, params.column.getColId())),
    setNote: (params) => {
        const key =
            params.location === 'fullWidthRow'
                ? getFullWidthNoteKey(params.rowNode.id)
                : getNoteKey(params.rowNode.id, params.column.getColId());
        if (params.note === undefined) {
            notesStore.delete(key);
        } else {
            notesStore.set(key, params.note);
        }
    },
};
```

```ts
notesStore.set('2', {
    text: 'This note belongs to a full width row.',
});
```

### Embedded Full Width Rows

When `embedFullWidthRows=true`, the datasource still receives `location: 'fullWidthRow'`, but `pinned` identifies whether the note belongs to the left, centre, or right rendered section so that the `notesDataSource` can save the note appropriately.

#### Notes with Embedded Full Width Rows

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FullWidthNotesDataSource,
  FullWidthNotesDataSourceGetNoteParams,
  FullWidthNotesDataSourceSetNoteParams,
  GetRowHeight,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ICellRendererComp,
  ICellRendererParams,
  IsFullWidthRow,
  IsFullWidthRowParams,
  ModuleRegistry,
  Note,
  NotesDataSource,
  RowHeightParams,
  enableDevValidations,
} from "ag-grid-community";
import { ContextMenuModule, NotesModule } from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ContextMenuModule,
  NotesModule,
]);

class FullWidthCellRenderer implements ICellRendererComp {
  private eGui!: HTMLElement;

  public init(params: ICellRendererParams<OlympicWinner>): void {
    const data = params.data!;
    const section = getSection(params.pinned);

    const eGui = document.createElement("div");
    eGui.className = `notes-full-width-row notes-full-width-row--${section}`;
    eGui.innerHTML = `
            <div class="notes-full-width-row__badge">${section}</div>
            <div class="notes-full-width-row__title">${data.athlete}</div>
            <div class="notes-full-width-row__details">
                <span>${data.country}</span>
                <span>${data.year}</span>
                <span>${data.sport}</span>
            </div>
        `;
    this.eGui = eGui;
  }

  public getGui(): HTMLElement {
    return this.eGui;
  }

  public refresh(): boolean {
    return false;
  }
}

interface OlympicWinner extends Partial<IOlympicData> {
  id: string;
  featured?: boolean;
}

const getSection = (pinned: "left" | "right" | null | undefined) =>
  pinned ?? "center";

const getFullWidthNoteKey = (
  rowId: string,
  pinned: "left" | "right" | null | undefined,
) => `${rowId}::${getSection(pinned)}`;

const noteStore = new Map<string, Note>([
  [
    getFullWidthNoteKey("2", "left"),
    {
      text: "This note belongs to the left embedded full width section.",
    },
  ],
  [
    getFullWidthNoteKey("2", undefined),
    {
      text: "This note belongs to the centre embedded full width section.",
    },
  ],
  [
    getFullWidthNoteKey("2", "right"),
    {
      text: "This note belongs to the right embedded full width section.",
    },
  ],
]);

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :notesDataSource="notesDataSource"
        :columnDefs="columnDefs"
        :rowData="rowData"
        :embedFullWidthRows="true"
        :getRowId="getRowId"
        :defaultColDef="defaultColDef"
        :getRowHeight="getRowHeight"
        :isFullWidthRow="isFullWidthRow"
        :fullWidthCellRenderer="fullWidthCellRenderer"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<OlympicWinner> | null>(null);
    const notesDataSource = ref<NotesDataSource | FullWidthNotesDataSource>({
      supportsFullWidthRows: true,
      getNote: (params: FullWidthNotesDataSourceGetNoteParams) =>
        params.location === "fullWidthRow"
          ? noteStore.get(
              getFullWidthNoteKey(params.rowNode.id!, params.pinned),
            )
          : undefined,
      setNote: (params: FullWidthNotesDataSourceSetNoteParams) => {
        if (params.location !== "fullWidthRow") {
          return;
        }
        const key = getFullWidthNoteKey(params.rowNode.id!, params.pinned);
        if (params.note === undefined) {
          noteStore.delete(key);
        } else {
          noteStore.set(key, params.note);
        }
      },
    });
    const columnDefs = ref<ColDef[]>([
      { field: "athlete", pinned: "left", width: 235 },
      { field: "age", maxWidth: 110 },
      { field: "country" },
      { field: "year", maxWidth: 110 },
      { field: "sport", pinned: "right", width: 235 },
    ]);
    const rowData = ref<OlympicWinner[] | null>([
      {
        id: "1",
        athlete: "Michael Phelps",
        age: 23,
        country: "United States",
        year: 2008,
        sport: "Swimming",
      },
      {
        id: "2",
        athlete: "Usain Bolt",
        age: 22,
        country: "Jamaica",
        year: 2008,
        sport: "Athletics",
        featured: true,
      },
      {
        id: "3",
        athlete: "Simone Biles",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Gymnastics",
      },
      {
        id: "4",
        athlete: "Katie Ledecky",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Swimming",
      },
      {
        id: "5",
        athlete: "Allyson Felix",
        age: 30,
        country: "United States",
        year: 2016,
        sport: "Athletics",
        featured: true,
      },
      {
        id: "6",
        athlete: "Mo Farah",
        age: 33,
        country: "Great Britain",
        year: 2016,
        sport: "Athletics",
      },
    ]);
    const getRowId = ref<GetRowIdFunc>(
      ({ data }: GetRowIdParams<OlympicWinner>) => data.id,
    );
    const defaultColDef = ref<ColDef>({
      flex: 1,
      minWidth: 120,
    });
    const getRowHeight = ref<GetRowHeight>(
      (params: RowHeightParams<OlympicWinner>) => {
        if (params.data?.featured) {
          return 96;
        }
      },
    );
    const isFullWidthRow = ref<IsFullWidthRow>(
      (params: IsFullWidthRowParams<OlympicWinner>) =>
        !!params.rowNode.data?.featured,
    );
    const fullWidthCellRenderer = ref(FullWidthCellRenderer);

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

    return {
      gridApi,
      notesDataSource,
      columnDefs,
      rowData,
      getRowId,
      defaultColDef,
      getRowHeight,
      isFullWidthRow,
      fullWidthCellRenderer,
      onGridReady,
    };
  },
});

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

[Live example: Notes with Embedded Full Width Rows](https://www.ag-grid.com/examples/notes/notes-full-width-embedded/vue3)

## Feature Interaction

### Context Menu

When the `ContextMenuModule` is registered, the note actions are included automatically. If you customise `getContextMenuItems()`, include the built-in `note` item to keep the standard note actions:

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

this.getContextMenuItems = () => ['note', 'copy', 'export'];
```

The built-in `note` item expands based on the current cell state:

- `Add Note` when the cell has no note and note creation is allowed.
- `Edit Note` and `Remove Note` when the existing note is editable.
- `View Note`, plus a disabled `Remove Note`, when the existing note is read-only.
- Disabled note actions when the cell is suppressed. Existing suppressed notes still show `View Note`.

### Keyboard Shortcuts

`Shift + F2` opens an existing note for the focused cell, or creates a new note if the cell allows notes and does not already have one. Plain `F2` keeps the normal cell editing behaviour.

### Cell Editing

Notes are **not** displayed if the cell is currently being edited. Hovering that cell or pressing `Shift + F2` will have no effect until editing is complete.

## API

Use the grid API to read, write, remove and refresh notes programmatically. This is useful when notes are edited from application UI outside the grid, or when the underlying note store changes directly. The API example also shows how to set `readOnly` on a note payload.

In the example below:

- clicking a cell selects it and syncs the toolbar controls automatically.
- `Save via API` updates the note with `setNote()`, including setting or clearing `readOnly`.
- `Remove via API` clears the note
- `Mutate Store Directly` plus `Refresh Notes` shows how to resync the grid after external store updates.

#### Notes API

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./styles.css";
import {
  CellFocusedEvent,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  Column,
  FullWidthNotesDataSource,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IRowNode,
  ModuleRegistry,
  Note,
  NotesDataSource,
  NotesDataSourceGetNoteParams,
  NotesDataSourceSetNoteParams,
  RowApiModule,
  enableDevValidations,
} from "ag-grid-community";
import { ContextMenuModule, NotesModule } from "ag-grid-enterprise";
import { OlympicWinner } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ContextMenuModule,
  NotesModule,
  RowApiModule,
]);

const getDisplayTimestamp = () =>
  new Intl.DateTimeFormat("en-GB", {
    dateStyle: "medium",
    timeStyle: "short",
  }).format(new Date());

const noteStore: Record<string, Record<string, Note>> = {
  "2": {
    athlete: {
      text: "Follow up with the regional team before publishing this profile.",
      author: "Martha",
      readOnly: true,
      updatedAt: "29 Mar 2026, 09:15",
    },
  },
};

const getSelectionStatusElement = () =>
  document.getElementById("selection-status") as HTMLElement;

const getAuthorInput = () =>
  document.getElementById("note-author") as HTMLInputElement;

const getNoteTextArea = () =>
  document.getElementById("note-text") as HTMLTextAreaElement;

const getReadOnlyInput = () =>
  document.getElementById("note-readonly") as HTMLInputElement;

const describeCell = (cell: SelectedCell) =>
  `${cell.rowNode.data?.athlete ?? cell.rowNode.id} / ${cell.column.getColId()}`;

const setStatus = (message: string) => {
  getSelectionStatusElement().textContent = message;
};

const getSelectedCell = (): SelectedCell | undefined => {
  const win = window as any;
  const selectedCell = win.selectedCell as SelectedCell | undefined;
  if (!selectedCell) {
    setStatus("No cell selected.");
    return undefined;
  }
  return selectedCell;
};

const syncNote = (gridApi: GridApi<OlympicWinner>) => {
  const cell = getSelectedCell();
  if (!cell || !gridApi) {
    return;
  }
  const note = gridApi.getNote(cell);
  getNoteTextArea().value = note?.text ?? "";
  getAuthorInput().value =
    (note?.author ?? getAuthorInput().value) || "API Demo";
  getReadOnlyInput().checked = !!note?.readOnly;
  setStatus(
    note
      ? `Loaded note for ${describeCell(cell)}.`
      : `No note stored for ${describeCell(cell)}.`,
  );
};

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div class="example-wrapper">
      <div class="toolbar">
        <div class="toolbar-row">
          <span id="selection-status">No cell selected.</span>
        </div>
        <div class="toolbar-row">
          <label class="toolbar-field">
            <span>Author</span>
            <input id="note-author" type="text" value="API Demo">
            </label>
            <label class="toolbar-field">
              <span>Read-only</span>
              <input id="note-readonly" type="checkbox">
              </label>
              <button v-on:click="saveSelectedNote()">Save via API</button>
              <button v-on:click="removeSelectedNote()">Remove via API</button>
              <button v-on:click="mutateStoreDirectly()">Mutate Store Directly</button>
              <button v-on:click="refreshSelectedNotes()">Refresh Notes</button>
            </div>
            <textarea id="note-text" rows="3" placeholder="Edit note text."></textarea>
          </div>
          <ag-grid-vue
            style="width: 100%; height: 100%;"
            @grid-ready="onGridReady"
            :notesDataSource="notesDataSource"
            :columnDefs="columnDefs"
            :rowData="rowData"
            :getRowId="getRowId"
            :defaultColDef="defaultColDef"
            @cell-focused="onCellFocused"></ag-grid-vue>
          </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<OlympicWinner> | null>(null);
    const notesDataSource = ref<NotesDataSource | FullWidthNotesDataSource>({
      getNote: (params: NotesDataSourceGetNoteParams) =>
        noteStore[params.rowNode.id!]?.[params.column.getColId()],
      setNote: (params: NotesDataSourceSetNoteParams) => {
        const rowId = params.rowNode.id!;
        const colId = params.column.getColId();
        if (params.note === undefined) {
          delete noteStore[rowId]?.[colId];
        } else {
          const row = (noteStore[rowId] ??= {});
          row[colId] = params.note;
        }
      },
    });
    const columnDefs = ref<ColDef[]>([
      { field: "athlete" },
      { field: "age", maxWidth: 110 },
      { field: "country" },
      { field: "year", maxWidth: 110 },
      { field: "sport" },
    ]);
    const rowData = ref<OlympicWinner[] | null>([
      {
        id: "1",
        athlete: "Michael Phelps",
        age: 23,
        country: "United States",
        year: 2008,
        sport: "Swimming",
      },
      {
        id: "2",
        athlete: "Usain Bolt",
        age: 22,
        country: "Jamaica",
        year: 2008,
        sport: "Athletics",
      },
      {
        id: "3",
        athlete: "Simone Biles",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Gymnastics",
      },
      {
        id: "4",
        athlete: "Katie Ledecky",
        age: 19,
        country: "United States",
        year: 2016,
        sport: "Swimming",
      },
      {
        id: "5",
        athlete: "Allyson Felix",
        age: 30,
        country: "United States",
        year: 2016,
        sport: "Athletics",
      },
      {
        id: "6",
        athlete: "Mo Farah",
        age: 33,
        country: "Great Britain",
        year: 2016,
        sport: "Athletics",
      },
    ]);
    const getRowId = ref<GetRowIdFunc>(
      ({ data }: GetRowIdParams<OlympicWinner>) => data.id,
    );
    const defaultColDef = ref<ColDef>({
      flex: 1,
      minWidth: 120,
    });

    function onCellFocused(event: CellFocusedEvent<OlympicWinner>) {
      const win = window as any;
      win.selectedCell = {
        rowNode: gridApi.value.getDisplayedRowAtIndex(event.rowIndex!),
        column: event.column,
      };
      syncNote(gridApi.value);
    }
    function saveSelectedNote() {
      const cell = getSelectedCell();
      if (!cell || !gridApi.value) {
        return;
      }
      const text = getNoteTextArea().value.trim();
      const author = getAuthorInput().value.trim();
      const readOnly = getReadOnlyInput().checked;
      const nextNote = text
        ? {
            text,
            author: author || undefined,
            readOnly: readOnly || undefined,
            updatedAt: getDisplayTimestamp(),
          }
        : undefined;
      gridApi.value.setNote({
        ...cell,
        note: nextNote,
      });
      syncNote(gridApi.value);
      setStatus(
        text
          ? `Saved note for ${describeCell(cell)} via gridApi.value.setNote().`
          : `Removed note for ${describeCell(cell)} via gridApi.value.setNote().`,
      );
    }
    function removeSelectedNote() {
      const cell = getSelectedCell();
      if (!cell || !gridApi.value) {
        return;
      }
      gridApi.value.setNote({
        ...cell,
        note: undefined,
      });
      syncNote(gridApi.value);
      setStatus(
        `Removed note for ${describeCell(cell)} via gridApi.value.setNote().`,
      );
    }
    function mutateStoreDirectly() {
      const cell = getSelectedCell();
      if (!cell) {
        return;
      }
      const rowId = cell.rowNode.id!;
      const colId = cell.column.getColId();
      const currentNote = noteStore[rowId]?.[colId];
      const author = getAuthorInput().value.trim() || "External Store";
      const text =
        getNoteTextArea().value.trim() ||
        currentNote?.text ||
        "Updated outside the grid";
      const readOnly = getReadOnlyInput().checked;
      const row = (noteStore[rowId] ??= {});
      row[colId] = {
        ...(currentNote ?? {}),
        text: `${text} (external update)`,
        author,
        readOnly: readOnly || undefined,
        updatedAt: getDisplayTimestamp(),
      };
      setStatus(`Updated the store directly for ${describeCell(cell)}.`);
    }
    function refreshSelectedNotes() {
      const cell = getSelectedCell();
      if (!cell || !gridApi.value) {
        return;
      }
      gridApi.value.refreshNotes({
        rowNodes: [cell.rowNode],
        columns: [cell.column],
      });
      syncNote(gridApi.value);
      setStatus(
        `Refreshed notes for ${describeCell(cell)} via gridApi.value.refreshNotes().`,
      );
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;
    };

    return {
      gridApi,
      notesDataSource,
      columnDefs,
      rowData,
      getRowId,
      defaultColDef,
      onGridReady,
      onCellFocused,
      saveSelectedNote,
      removeSelectedNote,
      mutateStoreDirectly,
      refreshSelectedNotes,
    };
  },
});

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

[Live example: Notes API](https://www.ag-grid.com/examples/notes/notes-api/vue3)

### Grid Options

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `noteTrigger` | `'hover' \| 'click'` |  | `'hover'` | Changes how existing notes are opened. - `'hover'` - Existing notes open when hovering a noted cell or full width row. - `'click'` - Existing notes open when clicking a noted cell or full width row. Module: [`NotesModule`](https://www.ag-grid.com/vue-data-grid/modules/). |
| `noteShowDelay` | `number` |  | `180` | The delay in milliseconds before a note is shown when hovering a noted cell. Only applies when `noteTrigger = 'hover'`. Module: [`NotesModule`](https://www.ag-grid.com/vue-data-grid/modules/). |
| `noteHideDelay` | `number` |  | `220` | The delay in milliseconds before a note is hidden after the pointer leaves a noted cell or note popup. Module: [`NotesModule`](https://www.ag-grid.com/vue-data-grid/modules/). |

### API Reference

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `getNote` | `Function` |  |  | Return the current note for a cell. Module: [`NotesModule`](https://www.ag-grid.com/vue-data-grid/modules/). |
| `setNote` | `Function` |  |  | Set or remove the note for a cell. Pass `note: undefined` to remove the note. Module: [`NotesModule`](https://www.ag-grid.com/vue-data-grid/modules/). |
| `refreshNotes` | `Function` |  |  | Refresh note presence for the currently rendered cells. Module: [`NotesModule`](https://www.ag-grid.com/vue-data-grid/modules/). |

### NotesDataSource

Properties available on the `NotesDataSource&lt;TMetadata = any&gt;` interface.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `getNote` | `Function` |  |  | Return the note for the given cell. |
| `setNote` | `Function` |  |  | Set or clear the note for the given cell. |
| `init` | `Function` |  |  | Initialise the data source so that the user can take a reference to the gridApi if needed. |
| `destroy` | `Function` |  |  | Called by the grid when the data source is being disposed. |

### FullWidthNotesDataSource

Properties available on the `FullWidthNotesDataSource&lt;TMetadata = any&gt;` interface.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `supportsFullWidthRows` | `true` |  |  | Enables full width row notes for this datasource. |
| `getNote` | `Function` |  |  | Return the note for the given cell or full width row. |
| `setNote` | `Function` |  |  | Set or clear the note for the given cell or full width row. |
| `init` | `Function` |  |  | Initialise the data source so that the user can take a reference to the gridApi if needed. |
| `destroy` | `Function` |  |  | Called by the grid when the data source is being disposed. |

### Note

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `text` | `string` |  |  | Text content of the note. |
| `readOnly` | `boolean` |  |  | Set to `true` to make this note readonly. |
| `author` | `string` |  |  | Optional author of the note. |
| `createdAt` | `string` |  |  | Optional creation timestamp. |
| `updatedAt` | `string` |  |  | Optional updated timestamp. |
| `metadata` | `TMetadata` |  |  | Optional application metadata to be associated with this note. |

### RefreshNotesParams

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `rowNodes` | [`IRowNode[]`](https://www.ag-grid.com/vue-data-grid/row-object/) |  |  | Only refresh the provided rowNodes. If `undefined` refresh all rows. |
| `columns` | `(string \| Column)[]` |  |  | Only refresh the provided columns. If `undefined` refresh all columns. |
