# Plugins (https://v1.s3.dimah.dev/docs/server/plugins)

Use `definePlugin` to create reusable server extensions such as
[`db()`](https://v1.s3.dimah.dev/docs/db). Plugins can contribute lifecycle hooks, HTTP endpoints
under your `basePath`, and typed instance context (`s3[id]`).

```ts title="lib/s3-audit-plugin.ts"
import { definePlugin, createS3Endpoint } from "@dimah-s3/server";

export const auditPlugin = definePlugin({
  id: "audit",
  hooks: {
    upload: {
      onConfirmed: async ({ key }) => {
        console.log("[audit] Upload confirmed:", key);
      },
    },
  },
  endpoints: {
    stats: createS3Endpoint("/audit/stats", { method: "GET" }, async () => ({
      status: "ok",
    })),
  },
  context: {
    log: (msg: string) => console.log("[audit]", msg),
  },
});
```

Register the plugin in your instance:

```ts title="lib/s3.ts"
import { dimahS3, route } from "@dimah-s3/server";
import { auditPlugin } from "./s3-audit-plugin";

export const s3 = dimahS3({
  client: awsS3,
  bucket: process.env.S3_BUCKET!,
  plugins: [auditPlugin], // [!code ++]
  routes: {
    avatar: route({ upload: true }),
  },
});

s3.audit.log("Storage ready"); // [!code ++]
```

---

## Hook execution order [#hook-execution-order]

- **Guards** — Plugins execute first in registration order, followed by user config guards.
- **Lifecycle hooks (`onConfirmed`, `onDeleted`)** — User config hooks execute first, followed by plugin hooks.

## Frequently asked questions [#frequently-asked-questions]

<Accordions>

<Accordion title="How do I add client-side actions for a server plugin?">

Create a client plugin with `defineClientPlugin` from `@dimah-s3/core`:

```ts
import { defineClientPlugin, pluginPath } from "@dimah-s3/core";

export function auditClient() {
  return defineClientPlugin({
    id: "audit",
    getActions: ($fetch) => ({
      getStats: () => $fetch(pluginPath("audit", "stats"), { method: "GET" }),
    }),
  });
}
```

</Accordion>

<Accordion title="Can a route opt out of a global plugin?">

Yes. Set `plugins: { [pluginId]: false }` in your route definition:

```ts
avatar: route({
  upload: true,
  plugins: { audit: false }, // [!code ++]
}),
```

</Accordion>

</Accordions>
