# Plugins (https://form.dimah.dev/docs/plugins)



A plugin can add HTTP endpoints, lifecycle hooks, error codes, field types, and matching methods on `createFormClient`.

***

## 1. Creating a Server Plugin [#1-creating-a-server-plugin]

Use `definePlugin()` and `createFormEndpoint()` from `@dimah-form/server` to build a server extension:

```ts title="lib/plugins/analytics-plugin.ts"
import {
  createFormEndpoint,
  defineErrorCodes,
  definePlugin,
} from "@dimah-form/server";
import * as z from "zod";

// 1. Declare domain-specific error codes
export const ANALYTICS_ERROR_CODES = defineErrorCodes({
  STATS_UNAVAILABLE: "Analytics data could not be computed",
});

// 2. Define the server plugin
export const analyticsPlugin = () =>
  definePlugin({
    id: "analytics",
    $ERROR_CODES: ANALYTICS_ERROR_CODES,

    // Add custom HTTP endpoints to the dimahForm router
    endpoints: {
      getFormStats: createFormEndpoint(
        "/analytics/stats",
        {
          method: "GET",
          query: z.object({
            formId: z.string(),
          }),
        },
        async ({ query, store, request }) => {
          const list = await store.listResponses({
            formId: query.formId,
            limit: 100,
          });

          const total = list.responses.length;
          const submitted = list.responses.filter(
            (r) => r.status === "submitted",
          ).length;

          return {
            formId: query.formId,
            totalResponses: total,
            completionRate: total > 0 ? (submitted / total) * 100 : 0,
          };
        },
      ),
    },

    // Optional lifecycle hooks contributed by this plugin
    hooks: {
      afterSubmit: async ({ response }) => {
        console.log(`[Analytics] Response ${response.id} submitted.`);
      },
    },
  });
```

***

## 2. Registering on the Server [#2-registering-on-the-server]

Pass the plugin to `dimahForm({ plugins: [...] })`:

```ts title="lib/form.ts"
import { dimahForm, memoryAdapter } from "@dimah-form/server";
import { analyticsPlugin } from "@/lib/plugins/analytics-plugin";
import { forms } from "@/lib/forms";

export const form = dimahForm({
  database: memoryAdapter(),
  forms,
  plugins: [analyticsPlugin()],
});

export type Form = typeof form;
```

You can now call the plugin endpoint directly through `form.api`:

```ts
const stats = await form.api.getFormStats({
  query: { formId: "feedback" },
});
```

***

## 3. Creating a Companion Client Plugin [#3-creating-a-companion-client-plugin]

To expose typed methods on `createFormClient`, create a companion client plugin using `defineClientPlugin()`:

```ts title="lib/plugins/analytics-client.ts"
import { defineClientPlugin } from "@dimah-form/react";
import { ANALYTICS_ERROR_CODES } from "./analytics-plugin";

export const analyticsClientPlugin = () =>
  defineClientPlugin({
    id: "analytics",
    $ERROR_CODES: ANALYTICS_ERROR_CODES,
    endpoints: ({ $fetch }) => ({
      getStats: (formId: string) =>
        $fetch<{
          formId: string;
          totalResponses: number;
          completionRate: number;
        }>(`/analytics/stats?formId=${encodeURIComponent(formId)}`, {
          method: "GET",
        }),
    }),
  });
```

Register the client plugin with `createFormClient`:

```ts title="lib/form-client.ts"
"use client";

import { createFormClient } from "@dimah-form/react";
import type { Form } from "@/lib/form";
import { analyticsClientPlugin } from "@/lib/plugins/analytics-client";

export const formClient = createFormClient<Form>({
  basePath: "/api/form",
  plugins: [analyticsClientPlugin()],
});

// Call the plugin method from client components
const stats = await formClient.getStats("feedback");
```

***

## Plugin Options Reference [#plugin-options-reference]

| Property           | Type                           | Description                                                          |
| :----------------- | :----------------------------- | :------------------------------------------------------------------- |
| **`id`**           | `string`                       | Unique alphanumeric identifier for the plugin.                       |
| **`$ERROR_CODES`** | `ErrorCodeCatalog`             | Custom error codes declared via `defineErrorCodes()`.                |
| **`endpoints`**    | `Record<string, FormEndpoint>` | Map of custom route handlers.                                        |
| **`hooks`**        | `DimahFormHooks`               | Pre-write and post-write lifecycle hooks.                            |
| **`fieldTypes`**   | `FieldTypeDefinition[]`        | Custom field validators added by this plugin.                        |
| **`dependsOn`**    | `string[]`                     | Array of required plugin IDs that must be loaded before this plugin. |

***

## Next Steps [#next-steps]

<Cards>
  <Card title="Auth" href="/docs/auth" description="Learn how guard hooks secure plugin endpoints." />

  <Card title="Custom Fields" href="/docs/custom-fields" description="Package reusable validators inside plugins." />

  <Card title="Errors" href="/docs/errors" description="Understand how custom error codes merge with core error catalogs." />
</Cards>
