# How the Plugins System in NextChat Works: OpenAPI to Function Tools

> Discover how NextChat's plugins system transforms OpenAPI services into LLM function tools for seamless external service integration during your chats.

- Repository: [NextChat/NextChat](https://github.com/ChatGPTNextWeb/NextChat)
- Tags: internals
- Published: 2026-02-28

---

**NextChat's plugins system treats every plugin as an OpenAPI-described HTTP service, dynamically converting API operations into LLM function tools that authenticate and invoke external services during chat sessions.**

NextChat (formerly ChatGPTNextWeb) implements a flexible plugins architecture that bridges OpenAPI specifications with LLM function calling. The system loads plugin definitions from JSON manifests located in [`public/plugins.json`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/public/plugins.json), parses OpenAPI schemas to generate callable tools via the `FunctionToolService`, and manages authentication injection at request time. This allows users to extend chat capabilities with any HTTP API that exposes a valid OpenAPI description.

## Architecture Overview

The plugins system consists of four core components working together to transform static API specifications into runtime function tools:

- **[`public/plugins.json`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/public/plugins.json)** – The built-in plugin catalogue containing IDs, display names, and URLs to OpenAPI schemas
- **[`app/store/plugin.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/plugin.ts)** – The central Zustand store that persists plugin data and hosts the `FunctionToolService` class
- **[`app/components/plugin.tsx`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/components/plugin.tsx)** – The React UI for creating, editing, and managing plugin instances
- **[`app/constant.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/constant.ts)** – Defines `PLUGINS_REPO_URL`, the external repository pointer for fetching built-in plugin schemas

When a user enables plugins for a conversation, the store's `getAsTools()` method returns both the function descriptors (for the LLM) and the executable wrappers (for the runtime).

## Loading Built-in Plugins

On application startup, the `usePluginStore` triggers its `onRehydrateStorage` lifecycle hook to populate the default plugin registry:

1. **Fetch Manifest** – Downloads [`./plugins.json`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/./plugins.json) to retrieve the list of official plugins
2. **Download Schemas** – For each entry, retrieves the OpenAPI JSON/YAML from the `schema` URL
3. **Create Records** – Invokes `state.create(item)` to instantiate `Plugin` objects with randomized `nanoId` identifiers
4. **Register Tools** – Calls `state.updatePlugin(..., add(plugin, true))` to parse the OpenAPI spec and register operations in `FunctionToolService`
5. **Mark Built-in** – Copies the OpenAPI title/version into the plugin record and sets the `builtin` flag to `true`

This process ensures that official plugins like DALL-E 3 or web search are immediately available without user configuration.

## The FunctionToolService Engine

The `FunctionToolService` class, defined within [`app/store/plugin.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/plugin.ts), serves as the transformation engine. Its `add(plugin, replace)` method performs the heavy lifting of converting OpenAPI specifications into executable JavaScript functions.

### Transforming OpenAPI to Function Tools

When `add()` receives a plugin object, it executes the following transformation pipeline:

```typescript
const definition = yaml.load(plugin.content) as any;
const serverURL = definition?.servers?.[0]?.url;
const baseURL = !isApp ? "/api/proxy" : serverURL;
const headers = { "X-Base-URL": !isApp ? serverURL : undefined };
if (authLocation === "header") headers[headerName] = tokenValue;

const api = new OpenAPIClientAxios({ definition });
await api.init();

```

**Operation Mapping** – The service iterates through `api.getOperations()` to build two critical data structures:

- **`FunctionToolItem` array** – Contains function descriptors with:
  - `name`: Derived from `operationId` or generated from path/method
  - `description`: Extracted from OpenAPI `description` or `summary` fields
  - `parameters`: Merged JSON schema combining request body, query, and path parameters

- **`funcs` map** – Contains executable wrappers that assemble the final HTTP request, inject authentication tokens based on `authLocation`, and forward to `api.client.paths[path][method]`

These structures are cached in `FunctionToolService.tools[plugin.id]` for rapid retrieval during chat sessions.

### Authentication Handling

The engine supports three authentication injection strategies stored in the `Plugin` object:

- **`authLocation: "header"`** – Injects `authToken` into the specified `authHeader` (e.g., `Authorization`)
- **`authLocation: "query"`** – Appends the token as a query parameter using the `authHeader` as the key name
- **`authLocation: "body"`** – Merges the token into the request payload

This configuration is applied at request time within the wrapper functions, ensuring sensitive credentials never leak into the stored OpenAPI schema.

## Adding Custom Plugins

Users can extend NextChat with custom APIs through either programmatic interfaces or the management UI.

### Programmatic Creation

Developers can dynamically register plugins using the store methods:

```typescript
import { usePluginStore } from '@/store/plugin';

const store = usePluginStore();

// Create plugin record
const plugin = store.create({
  id: 'weather-api',
  title: 'WeatherService',
  version: '1.0.0',
  content: `
openapi: 3.0.0
info:
  title: WeatherService
  version: 1.0.0
servers:
  - url: https://api.weather.com/v1
paths:
  /current:
    get:
      operationId: getCurrentWeather
      description: Retrieve current weather conditions
      parameters:
        - in: query
          name: city
          schema:
            type: string
          required: true
      responses:
        '200':
          description: Success
`
});

// Register with optional authentication
store.updatePlugin(plugin.id, p => {
  p.authType = 'bearer';
  p.authToken = 'sk-weather-token';
  p.authLocation = 'header';
  p.authHeader = 'Authorization';
});

```

The `create()` method generates a unique identifier and stores the raw OpenAPI content, while `updatePlugin()` triggers the parsing logic in `FunctionToolService.add()`.

### UI-Based Management

The `PluginPage` component in [`app/components/plugin.tsx`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/components/plugin.tsx) provides a visual editor bound to the plugin's `content` field. When the user modifies the OpenAPI schema:

1. The UI attempts `yaml.load()` validation
2. Instantiates a temporary `OpenAPIClientAxios` client to verify the definition
3. Calls `pluginStore.updatePlugin()` to rebuild tool descriptors via `FunctionToolService.add(plugin, true)`
4. Persists authentication settings (`authType`, `authLocation`, etc.) to the store

## Runtime Execution Flow

When a conversation requires plugin capabilities, the application follows this execution sequence:

**1. Tool Retrieval**

```typescript
const [tools, funcs] = pluginStore.getAsTools(selectedPluginIds);

```

The `getAsTools()` method aggregates all `FunctionToolItem` arrays and `funcs` maps from the requested plugin IDs into unified collections.

**2. LLM Submission**

The `tools` array is serialized into the `functions` parameter of the OpenAI or Claude API request:

```typescript
await fetch('/api/openai/chat', {
  method: 'POST',
  body: JSON.stringify({
    messages: conversationHistory,
    functions: tools,
    function_call: "auto"
  })
});

```

**3. Function Invocation**

When the model responds with a `function_call` object containing `name` and `arguments`, the frontend:

1. Parses the JSON arguments
2. Looks up the implementation: `const handler = funcs[functionCall.name]`
3. Executes the HTTP request: `const result = await handler(parsedArgs)`
4. Appends the result as a `function` role message to the conversation history

This round-trip allows the LLM to retrieve real-time data, generate images, or trigger external workflows while maintaining conversational context.

## Summary

- **OpenAPI-Native**: NextChat plugins require valid OpenAPI 3.0 specifications, with each operation automatically converted to a callable function tool
- **Centralized Service**: The `FunctionToolService` class in [`app/store/plugin.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/plugin.ts) handles parsing, caching, and request execution for all plugins
- **Flexible Auth**: Supports header, query, and body-based authentication injection configured per-plugin via the UI or programmatically
- **Built-in Registry**: Default plugins load from [`public/plugins.json`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/public/plugins.json) and external repositories defined in [`app/constant.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/constant.ts)
- **Runtime Integration**: The `getAsTools()` method bridges the plugin store with LLM function calling, returning both descriptors for the model and executable wrappers for the client

## Frequently Asked Questions

### What format must plugin schemas follow?

Plugins must provide valid OpenAPI 3.0 specifications in either JSON or YAML format. The `FunctionToolService` uses `yaml.load()` for parsing, so YAML is preferred for readability. Each operation requires an `operationId` (or generates one from the path/method) to serve as the function name exposed to the LLM.

### How does authentication work for external APIs?

Authentication is handled at the plugin level through the `authType`, `authLocation`, `authHeader`, and `authToken` fields stored in the `Plugin` object. During request execution, `FunctionToolService.add()` injects these credentials into headers, query parameters, or the request body based on the `authLocation` setting, ensuring tokens are never hardcoded in the OpenAPI schema itself.

### Can I use custom plugins not in the official repository?

Yes. Users can create custom plugins through the `PluginPage` UI or programmatically via `usePluginStore().create()`. Custom plugins follow the same OpenAPI-to-function transformation pipeline as built-in plugins, allowing integration with private internal APIs or third-party services not included in the default [`plugins.json`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/plugins.json) manifest.

### Where does the actual HTTP request execute?

In web deployments, requests route through `/api/proxy` with the target URL passed in the `X-Base-URL` header to avoid CORS issues. In native app builds (`isApp === true`), requests execute directly against the `serverURL` defined in the OpenAPI spec's `servers` array. The `FunctionToolService` automatically determines the appropriate base URL and request strategy based on the environment.