# 创建你的第一个插件 (/docs/guides/your-first-plugin)

一步步创建你的第一个 Better Auth 插件的指南。



在本指南中，我们将带你一步步创建你的第一个 Better Auth 插件。

<Callout type="warn">
  本指南假设你已经 <Link href="/docs/installation">完成了基础设置</Link>，并且准备创建你的第一个插件。
</Callout>

<Steps>
  <Step>
    ## 规划你的想法 [#规划你的想法]

    在开始之前，你必须明确你想要创建什么样的插件。

    在本指南中，我们将创建一个 **生日插件**，用于记录用户的出生日期。
  </Step>

  <Step>
    ## 首先创建服务端插件 [#首先创建服务端插件]

    Better Auth 插件以成对形式工作：一个 <Link href="/docs/concepts/plugins#创建服务器插件">服务端插件</Link> 和一个 <Link href="/docs/concepts/plugins#创建客户端插件">客户端插件</Link>。
    服务端插件构成了你认证系统的基础，而客户端插件则提供了方便的前端 API，用于与服务端实现进行交互。

    <Callout>
      你可以在我们的 <Link href="/docs/concepts/plugins#创建插件">文档</Link> 中了解更多关于服务端/客户端插件的信息。
    </Callout>

    ### 创建服务端插件 [#创建服务端插件]

    找到一个合适的位置来创建你的生日插件文件夹，并在其中创建一个 `index.ts` 文件。

    <Files>
      <Folder name="birthday-plugin">
        <File name="index.ts" />
      </Folder>
    </Files>

    在 `index.ts` 文件中，我们将导出一个函数来代表我们的服务端插件。
    这将是我们后续添加到 `auth.ts` 文件插件列表中的内容。

    ```ts title="index.ts"
    import { createAuthClient } from "better-auth/client";
    import type { BetterAuthPlugin } from "better-auth";

    export const birthdayPlugin = () =>
      ({
        id: "birthdayPlugin",
      } satisfies BetterAuthPlugin);

    ```

    虽然这段代码什么也不做，但严格来说，你刚刚已经创建了自己的第一个插件，恭喜！🎉

    <Callout type="info">
      要发布可复用的插件？<Link href="/docs/concepts/plugins#注册插件类型">注册其服务端类型</Link>，这样 `getPlugin()` 就能推断出准确的返回类型。
    </Callout>
  </Step>

  <Step>
    ### 定义一个数据表结构 [#定义一个数据表结构]

    为了保存每个用户的生日数据，我们必须在 `user` 模型之上创建一个数据表结构。

    在这里创建数据表结构，也能让 <Link href="/docs/concepts/cli">Better Auth 的 CLI</Link> 生成更新数据库所需的脚本。

    <Callout type="info">
      你可以在这里了解更多关于 <Link href="/docs/concepts/plugins#模式">插件数据表结构</Link>。
    </Callout>

    ```ts title="index.ts"
    //...
    export const birthdayPlugin = () =>
      ({
        id: "birthdayPlugin",
        schema: {// [!code highlight]
          user: {// [!code highlight]
            fields: {// [!code highlight]
              birthday: {// [!code highlight]
                type: "date", // string, number, boolean, date // [!code highlight]
                required: true, // 如果该字段在新记录中为必填项。（默认：false） // [!code highlight]
                unique: false, // 如果该字段需要唯一性。（默认：false） // [!code highlight]
              },// [!code highlight]
            },// [!code highlight]
          },// [!code highlight]
        },
      } satisfies BetterAuthPlugin);
    ```
  </Step>

  <Step>
    ### 授权逻辑 [#授权逻辑]

    在这个示例指南中，我们将设置认证逻辑来检查并确保注册用户年龄大于 5 岁。
    但同样的概念也可以应用于验证用户是否同意服务条款等情况。

    为此，我们将使用 <Link href="/docs/concepts/plugins#钩子hooks">钩子（Hooks）</Link>，它允许我们在某个操作执行 `之前` 或 `之后` 运行代码。

    ```ts title="index.ts"
    export const birthdayPlugin = () => ({
        //...
        // 在我们的例子中，我们想编写授权逻辑，
        // 意味着我们想在操作执行 `之前` 进行拦截。
        hooks: {
          before: [
            {
              matcher: (context) => /* ... */,
              handler: createAuthMiddleware(async (ctx) => {
                //...
              }),
            },
          ],
        },
    } satisfies BetterAuthPlugin)
    ```

    在我们的例子中，我们希望匹配所有发送到注册路径的请求：

    ```ts title="Before hook"
    {
      matcher: (context) => context.path.startsWith("/sign-up/email"),
      //...
    }
    ```

    对于我们的逻辑，我们将编写以下代码来检查用户的生日是否使他们年龄大于 5 岁。

    ```ts title="导入部分"
    import { createAuthMiddleware, APIError } from "better-auth/api";
    ```

    ```ts title="Before hook"
    {
      //...
      handler: createAuthMiddleware(async (ctx) => {
        const { birthday } = ctx.body;
        if(!(birthday instanceof Date)) {
          throw new APIError("BAD_REQUEST", { message: "生日必须为 Date 类型。" });
        }

        const today = new Date();
        const fiveYearsAgo = new Date(today.setFullYear(today.getFullYear() - 5));

        if(birthday >= fiveYearsAgo) {
          throw new APIError("BAD_REQUEST", { message: "用户必须年满 5 岁。" });
        }

        return { context: ctx };
      }),
    }
    ```

    **已授权！** 🔒

    我们现在已成功编写了确保年龄大于 5 岁的授权代码。
  </Step>

  <Step>
    ## 客户端插件 [#客户端插件]

    我们快完成了！🏁

    现在我们已经创建了服务端插件，下一步是开发客户端插件。
    由于这个插件不涉及太多的前端 API，因此操作相对简单！

    首先，创建我们的 `client.ts` 文件：

    <Files>
      <Folder name="birthday-plugin">
        <File name="index.ts" />

        <File name="client.ts" />
      </Folder>
    </Files>

    然后，添加以下代码：

    ```ts title="client.ts"
    import type { BetterAuthClientPlugin } from "better-auth/client";
    import type { birthdayPlugin } from "./index"; // 确保导入服务端插件作为类型 // [!code highlight]

    type BirthdayPlugin = typeof birthdayPlugin;

    export const birthdayClientPlugin = () => {
      return {
        id: "birthdayPlugin",
        $InferServerPlugin: {} as ReturnType<BirthdayPlugin>,
      } satisfies BetterAuthClientPlugin;
    };
    ```

    我们所做的是允许客户端插件推断服务端插件中定义的数据表结构类型。

    就这样！这就是生日客户端插件所需的一切。🎂
  </Step>

  <Step>
    ## 启用你的插件！ [#启用你的插件]

    服务端和客户端插件都已准备就绪，最后一步是将它们分别导入到 `auth-client.ts` 和 `server.ts` 文件中进行启用。

    ### 服务端启用 [#服务端启用]

    ```ts title="server.ts"
    import { betterAuth } from "better-auth";
    import { birthdayPlugin } from "./birthday-plugin";// [!code highlight]
     
    export const auth = betterAuth({
        plugins: [
          birthdayPlugin(),// [!code highlight]
        ]
    });
    ```

    ### 客户端启用 [#客户端启用]

    ```ts title="auth-client.ts"
    import { createAuthClient } from "better-auth/client";
    import { birthdayClientPlugin } from "./birthday-plugin/client";// [!code highlight]
     
    const authClient = createAuthClient({
        plugins: [
          birthdayClientPlugin()// [!code highlight]
        ]
    });
    ```

    ### 别忘了，数据表结构！ [#别忘了数据表结构]

    不要忘记将你的 `birthday` 字段添加到 `user` 表模型中！

    或者，使用 `generate` <Link href="/docs/concepts/cli#生成-generate">CLI 命令</Link>：

    ```bash
    npx auth@latest generate
    ```
  </Step>
</Steps>

## 总结 [#总结]

恭喜！你已成功创建了你的第一个 Better Auth 插件。
我们强烈建议你访问我们的 <Link href="/docs/concepts/plugins">插件文档</Link> 以了解更多信息。

如果你有一个想要与社区分享的插件，请随时通过
我们的 <Link href="https://discord.gg/better-auth">Discord 服务器</Link>
或通过 <Link href="https://github.com/better-auth/better-auth/pulls">拉取请求</Link>
与我们联系，我们可能会将其添加到 <Link href="/docs/plugins/community-plugins">社区插件列表</Link>！。

