# Admin hooks

> Source: https://docs.strapi.io/cms/plugins-development/admin-hooks

The Hooks API lets plugins create extension points (`createHook` in `register`) and subscribe to them (`registerHook` in `bootstrap`). Hooks run in series, waterfall, or parallel. Strapi includes predefined hooks for the Content Manager's List and Edit views.

The Hooks API allows a plugin to create and register hooks, i.e. places in the application where plugins can add personalized behavior.

:::prerequisites
Before diving deeper into the concepts on this page, please ensure you have:
- [created a Strapi plugin](/cms/plugins-development/create-a-plugin),
- read and understood the basics of the [Admin Panel API](/cms/plugins-development/admin-panel-api)
:::

## Creating hooks

Create hook extension points with `createHook()` during the [`register`](/cms/plugins-development/admin-panel-api#register) lifecycle. This declares that your plugin provides an extension point that other plugins can subscribe to.

```js title="my-plugin/admin/src/index.js"

  register(app) {
    app.createHook('My-PLUGIN/MY_HOOK');
  },
};
```
```ts title="my-plugin/admin/src/index.ts"

  register(app: StrapiApp) {
    app.createHook('My-PLUGIN/MY_HOOK');
  },
};
```

:::note
For predictable interoperability between plugins, use stable namespaced hook IDs such as `my-plugin/my-hook`.
:::

## Subscribing to hooks

Subscribe to hooks with `registerHook()` during the [`bootstrap`](/cms/plugins-development/admin-panel-api#bootstrap) lifecycle, once all plugins are loaded. The callback receives arguments from the hook caller and should return the (optionally mutated) data.

```js title="my-other-plugin/admin/src/index.js"

  bootstrap(app) {
    app.registerHook('My-PLUGIN/MY_HOOK', (...args) => {
      console.log(args);

      // important: return the mutated data
      return args;
    });
  },
};
```
```ts title="my-other-plugin/admin/src/index.ts"

  bootstrap(app: StrapiApp) {
    app.registerHook('My-PLUGIN/MY_HOOK', (...args: unknown[]) => {
      console.log(args);

      // important: return the mutated data
      return args;
    });
  },
};
```

Async callbacks are also supported:

```js title="my-other-plugin/admin/src/index.js"

  bootstrap(app) {
    app.registerHook('My-PLUGIN/MY_HOOK', async (data) => {
      const enrichedData = await fetchExternalData(data);

      // always return data for waterfall hooks
      return enrichedData;
    });
  },
};
```
```ts title="my-other-plugin/admin/src/index.ts"

  bootstrap(app: StrapiApp) {
    app.registerHook('My-PLUGIN/MY_HOOK', async (data: unknown) => {
      const enrichedData = await fetchExternalData(data);

      // always return data for waterfall hooks
      return enrichedData;
    });
  },
};
```

## Running hooks

Hooks can be run in 3 modes:

| Mode | Function | Return value |
|---|---|---|
| Series | `runHookSeries` | Array of results from each function, in order |
| Parallel | `runHookParallel` | Array of resolved promise results, in order |
| Waterfall | `runHookWaterfall` | Single value after all transformations applied sequentially |

:::caution
For `runHookWaterfall`, each subscriber must return the transformed value so that the next subscriber in the chain receives it. Failing to return a value will break the chain.
:::

## Using predefined hooks

Strapi includes predefined hooks for the Content Manager's List and Edit views.

### `INJECT-COLUMN-IN-TABLE`

The `Admin/CM/pages/ListView/inject-column-in-table` hook can add or mutate columns in the List View of the [Content Manager](/cms/intro):

```tsx
runHookWaterfall(INJECT_COLUMN_IN_TABLE, {
  displayedHeaders: ListFieldLayout[],
  layout: ListFieldLayout,
});
```

The following example subscribes to this hook to add a custom "External id" column:

```js title="my-plugin/admin/src/index.js"

  bootstrap(app) {
    app.registerHook(
      'Admin/CM/pages/ListView/inject-column-in-table',
      ({ displayedHeaders, layout }) => {
        return {
          displayedHeaders: [
            ...displayedHeaders,
            {
              attribute: { type: 'custom' },
              label: 'External id',
              name: 'externalId',
              searchable: false,
              sortable: false,
              cellFormatter: (document) => document.externalId,
            },
          ],
          layout,
        };
      }
    );
  },
};
```
```ts title="my-plugin/admin/src/index.ts"

  bootstrap(app: StrapiApp) {
    app.registerHook(
      'Admin/CM/pages/ListView/inject-column-in-table',
      ({ displayedHeaders, layout }) => {
        return {
          displayedHeaders: [
            ...displayedHeaders,
            {
              attribute: { type: 'custom' },
              label: 'External id',
              name: 'externalId',
              searchable: false,
              sortable: false,
              cellFormatter: (document) => document.externalId,
            },
          ],
          layout,
        };
      }
    );
  },
};
```

**ListFieldLayout and ListLayout type definitions**:

<ExpandableContent>

```tsx
interface ListFieldLayout {
  /**
   * The attribute data from the content-type's schema for the field
   */
  attribute: Attribute.Any | { type: 'custom' };
  /**
   * Typically used by plugins to render a custom cell
   */
  cellFormatter?: (
    data: Document,
    header: Omit<ListFieldLayout, 'cellFormatter'>,
    { collectionType, model }: { collectionType: string; model: string }
  ) => React.ReactNode;
  label: string | MessageDescriptor;
  /**
   * the name of the attribute we use to display the actual name e.g. relations
   * are just ids, so we use the mainField to display something meaninginful by
   * looking at the target's schema
   */
  mainField?: string;
  name: string;
  searchable?: boolean;
  sortable?: boolean;
}

interface ListLayout {
  layout: ListFieldLayout[];
  components?: never;
  metadatas: {
    [K in keyof Contracts.ContentTypes.Metadatas]: Contracts.ContentTypes.Metadatas[K]['list'];
  };
  options: LayoutOptions;
  settings: LayoutSettings;
}

type LayoutOptions = Schema['options'] & Schema['pluginOptions'] & object;

interface LayoutSettings extends Contracts.ContentTypes.Settings {
  displayName?: string;
  icon?: never;
}
```

### `MUTATE-EDIT-VIEW-LAYOUT`

The `Admin/CM/pages/EditView/mutate-edit-view-layout` hook can mutate the Edit View layout of the [Content Manager](/cms/intro).

The following example subscribes to this hook to force all fields to full width:

```js title="my-plugin/admin/src/index.js"

  bootstrap(app) {
    app.registerHook(
      'Admin/CM/pages/EditView/mutate-edit-view-layout',
      ({ layout, ...rest }) => {
        // Force all fields to full width in the default edit layout
        const updatedLayout = layout.map((rowGroup) =>
          rowGroup.map((row) => row.map((field) => ({ ...field, size: 12 })))
        );

        return {
          ...rest,
          layout: updatedLayout,
        };
      }
    );
  },
};
```
```ts title="my-plugin/admin/src/index.ts"

  bootstrap(app: StrapiApp) {
    app.registerHook(
      'Admin/CM/pages/EditView/mutate-edit-view-layout',
      ({ layout, ...rest }) => {
        // Force all fields to full width in the default edit layout
        const updatedLayout = layout.map((rowGroup) =>
          rowGroup.map((row) => row.map((field) => ({ ...field, size: 12 })))
        );

        return {
          ...rest,
          layout: updatedLayout,
        };
      }
    );
  },
};
```

**EditLayout and EditFieldLayout type definitions:**

<ExpandableContent>

```tsx
interface EditLayout {
  layout: Array<Array<EditFieldLayout[]>>;
  components: {
    [uid: string]: {
      layout: Array<EditFieldLayout[]>;
      settings: Contracts.Components.ComponentConfiguration['settings'] & {
        displayName?: string;
        icon?: string;
      };
    };
  };
  metadatas: {
    [K in keyof Contracts.ContentTypes.Metadatas]: Contracts.ContentTypes.Metadatas[K]['edit'];
  };
  options: LayoutOptions;
  settings: LayoutSettings;
}

interface EditFieldSharedProps extends Omit<InputProps, 'hint' | 'type'> {
  hint?: string;
  mainField?: string;
  size: number;
  unique?: boolean;
  visible?: boolean;
}

/**
 * Map over all the types in Attribute Types and use that to create a union of new types where the attribute type
 * is under the property attribute and the type is under the property type.
 */
type EditFieldLayout = {
  [K in Attribute.Kind]: EditFieldSharedProps & {
    attribute: Extract<Attribute.Any, { type: K }>;
    type: K;
  };
}[Attribute.Kind];

type LayoutOptions = Schema['options'] & Schema['pluginOptions'] & object;

interface LayoutSettings extends Contracts.ContentTypes.Settings {
  displayName?: string;
  icon?: never;
}
```

:::note
The `EditLayout` and `ListLayout` shapes documented here come from the `useDocumentLayout` hook (see [source code](https://github.com/strapi/strapi/blob/develop/packages/core/content-manager/admin/src/hooks/useDocumentLayout.ts)). Internal package naming can vary, but plugin authors should rely on the `EditLayout` and `ListLayout` shapes exposed in this page.
:::
