How the Plugins System in NextChat Works: OpenAPI to Function Tools
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, 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– The built-in plugin catalogue containing IDs, display names, and URLs to OpenAPI schemasapp/store/plugin.ts– The central Zustand store that persists plugin data and hosts theFunctionToolServiceclassapp/components/plugin.tsx– The React UI for creating, editing, and managing plugin instancesapp/constant.ts– DefinesPLUGINS_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:
- Fetch Manifest – Downloads
./plugins.jsonto retrieve the list of official plugins - Download Schemas – For each entry, retrieves the OpenAPI JSON/YAML from the
schemaURL - Create Records – Invokes
state.create(item)to instantiatePluginobjects with randomizednanoIdidentifiers - Register Tools – Calls
state.updatePlugin(..., add(plugin, true))to parse the OpenAPI spec and register operations inFunctionToolService - Mark Built-in – Copies the OpenAPI title/version into the plugin record and sets the
builtinflag totrue
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, 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:
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:
-
FunctionToolItemarray – Contains function descriptors with:name: Derived fromoperationIdor generated from path/methoddescription: Extracted from OpenAPIdescriptionorsummaryfieldsparameters: Merged JSON schema combining request body, query, and path parameters
-
funcsmap – Contains executable wrappers that assemble the final HTTP request, inject authentication tokens based onauthLocation, and forward toapi.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"– InjectsauthTokeninto the specifiedauthHeader(e.g.,Authorization)authLocation: "query"– Appends the token as a query parameter using theauthHeaderas the key nameauthLocation: "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:
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 provides a visual editor bound to the plugin's content field. When the user modifies the OpenAPI schema:
- The UI attempts
yaml.load()validation - Instantiates a temporary
OpenAPIClientAxiosclient to verify the definition - Calls
pluginStore.updatePlugin()to rebuild tool descriptors viaFunctionToolService.add(plugin, true) - 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
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:
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:
- Parses the JSON arguments
- Looks up the implementation:
const handler = funcs[functionCall.name] - Executes the HTTP request:
const result = await handler(parsedArgs) - Appends the result as a
functionrole 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
FunctionToolServiceclass inapp/store/plugin.tshandles 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.jsonand external repositories defined inapp/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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →