diff --git a/supabase/functions/chatgpt-plugin/README.md b/supabase/functions/chatgpt-plugin/README.md
new file mode 100644
index 0000000..ef5200f
--- /dev/null
+++ b/supabase/functions/chatgpt-plugin/README.md
@@ -0,0 +1,13 @@
+# Build a ChatGPT Plugin
+
+## Generate openapi spec
+
+```bash
+deno run -A supabase/functions/chatgpt-plugin/generate_openapi_spec.ts
+```
+
+## Deploy function
+
+```bash
+supabase functions deploy chatgpt-plugin --no-verify-jwt
+```
diff --git a/supabase/functions/chatgpt-plugin/generate_openapi_spec.ts b/supabase/functions/chatgpt-plugin/generate_openapi_spec.ts
new file mode 100644
index 0000000..bb7fc99
--- /dev/null
+++ b/supabase/functions/chatgpt-plugin/generate_openapi_spec.ts
@@ -0,0 +1,20 @@
+import swaggerJsdoc from "npm:swagger-jsdoc@6.2.8";
+
+const options = {
+  definition: {
+    openapi: "3.0.1",
+    info: {
+      title: "TODO Plugin",
+      description: `A plugin that allows the user to create and manage a TODO list using ChatGPT. If you do not know the user's username, ask them first before making queries to the plugin. Otherwise, use the username "global".`,
+      version: "1.0.0",
+    },
+  },
+  apis: ["./supabase/functions/chatgpt-plugin/index.ts"], // files containing annotations as above
+};
+
+const openapiSpecification = swaggerJsdoc(options);
+const openapiString = JSON.stringify(openapiSpecification, null, 2);
+const encoder = new TextEncoder();
+const data = encoder.encode(openapiString);
+await Deno.writeFile("./supabase/functions/chatgpt-plugin/openapi.json", data);
+console.log(openapiString);
diff --git a/supabase/functions/chatgpt-plugin/index.ts b/supabase/functions/chatgpt-plugin/index.ts
new file mode 100644
index 0000000..635a247
--- /dev/null
+++ b/supabase/functions/chatgpt-plugin/index.ts
@@ -0,0 +1,69 @@
+import { Application, Router } from "oak";
+import manifest from "./manifest.json" assert { type: "json" };
+import openapi from "./openapi.json" assert { type: "json" };
+
+console.log("Hello from `chatgpt-plugin` Function!");
+
+const _TODOS: { [key: string]: Array<string> } = {
+  global: ["test this plugin template"],
+};
+
+/**
+ * @openapi
+ * components:
+ *   schemas:
+ *     getTodosResponse:
+ *       type: object
+ *       properties:
+ *         todos:
+ *           type: array
+ *           items:
+ *             type: string
+ *           description: The list of todos.
+ */
+
+const router = new Router();
+router
+  .get("/chatgpt-plugin", (ctx) => {
+    ctx.response.body =
+      "Building ChatGPT plugins with Supabase Edge Functions!";
+  })
+  /**
+   * @openapi
+   * /todos/{username}:
+   *   get:
+   *     operationId: getTodos
+   *     summary: Get the list of todos
+   *     parameters:
+   *     - in: path
+   *       name: username
+   *       schema:
+   *         type: string
+   *       required: true
+   *       description: The name of the user.
+   *     responses:
+   *       200:
+   *         description: OK
+   *         content:
+   *           application/json:
+   *             schema:
+   *               $ref: '#/components/schemas/getTodosResponse'
+   */
+  .get("/chatgpt-plugin/todos/:username", (ctx) => {
+    const username = ctx.params.username;
+    ctx.response.body = _TODOS[username] ?? [];
+  })
+  .get("/chatgpt-plugin/.well-known/ai-plugin.json", (ctx) => {
+    ctx.response.type = "text/json";
+    ctx.response.body = JSON.stringify(manifest);
+  })
+  .get("/chatgpt-plugin/openapi.json", (ctx) => {
+    ctx.response.type = "text/json";
+    ctx.response.body = JSON.stringify(openapi);
+  });
+
+const app = new Application();
+app.use(router.routes());
+app.use(router.allowedMethods());
+
+await app.listen({ port: 8000 });
diff --git a/supabase/functions/chatgpt-plugin/manifest.json b/supabase/functions/chatgpt-plugin/manifest.json
new file mode 100644
index 0000000..4d6c803
--- /dev/null
+++ b/supabase/functions/chatgpt-plugin/manifest.json
@@ -0,0 +1,18 @@
+{
+  "schema_version": "v1",
+  "name_for_human": "TODO Plugin (no auth)",
+  "name_for_model": "todo",
+  "description_for_human": "Plugin for managing a TODO list, you can add, remove and view your TODOs.",
+  "description_for_model": "Plugin for managing a TODO list, you can add, remove and view your TODOs.",
+  "auth": {
+    "type": "none"
+  },
+  "api": {
+    "type": "openapi",
+    "url": "PLUGIN_HOSTNAME/openapi.json",
+    "is_user_authenticated": false
+  },
+  "logo_url": "PLUGIN_HOSTNAME/logo.png",
+  "contact_email": "dummy@email.com",
+  "legal_info_url": "http://www.example.com/legal"
+}
diff --git a/supabase/functions/chatgpt-plugin/openapi.json b/supabase/functions/chatgpt-plugin/openapi.json
new file mode 100644
index 0000000..332c773
--- /dev/null
+++ b/supabase/functions/chatgpt-plugin/openapi.json
@@ -0,0 +1,56 @@
+{
+  "openapi": "3.0.1",
+  "info": {
+    "title": "TODO Plugin",
+    "description": "A plugin that allows the user to create and manage a TODO list using ChatGPT. If you do not know the user's username, ask them first before making queries to the plugin. Otherwise, use the username \"global\".",
+    "version": "1.0.0"
+  },
+  "paths": {
+    "/todos/{username}": {
+      "get": {
+        "operationId": "getTodos",
+        "summary": "Get the list of todos",
+        "parameters": [
+          {
+            "in": "path",
+            "name": "username",
+            "schema": {
+              "type": "string"
+            },
+            "required": true,
+            "description": "The name of the user."
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/getTodosResponse"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "getTodosResponse": {
+        "type": "object",
+        "properties": {
+          "todos": {
+            "type": "array",
+            "items": {
+              "type": "string"
+            },
+            "description": "The list of todos."
+          }
+        }
+      }
+    }
+  },
+  "tags": []
+}
\ No newline at end of file