> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://openrouter.ai/docs/guides/features/plugins/llms.txt.
> For full documentation content, see https://openrouter.ai/docs/guides/features/plugins/llms-full.txt.
# Plugins
# Plugins
OpenRouter plugins extend the capabilities of any model by injecting or mutating a request or response to add functionality like PDF processing, automatic JSON repair, and context compression. Unlike [server tools](/docs/guides/features/server-tools) (which the model can call 0-N times), plugins always run once when enabled. Plugins can be enabled per-request via the API or configured as defaults for all your API requests through the [Plugins settings page](https://openrouter.ai/settings/plugins).
## Available Plugins
OpenRouter currently supports the following plugins:
| Plugin | Description | Docs |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| **Web Search** (deprecated) | Augment LLM responses with real-time web search results. Use the [`openrouter:web_search` server tool](/docs/guides/features/server-tools/web-search) instead. | [Web Search](/docs/guides/features/plugins/web-search) |
| **PDF Inputs** | Parse and extract content from uploaded PDF files | [PDF Inputs](/docs/guides/overview/multimodal/pdfs) |
| **Response Healing** | Automatically fix malformed JSON responses from LLMs | [Response Healing](/docs/guides/features/plugins/response-healing) |
| **Context Compression** | Compress prompts that exceed a model's context window using middle-out truncation | [Message Transforms](/docs/guides/features/message-transforms) |
## Enabling Plugins via API
Plugins are enabled by adding a `plugins` array to your chat completions request. Each plugin is identified by its `id` and can include optional configuration parameters.
```typescript title="TypeScript"
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: 'Bearer {{API_KEY_REF}}',
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: '{{MODEL}}',
messages: [
{
role: 'user',
content: 'What are the latest developments in AI?'
}
],
plugins: [
{ id: 'web' }
]
}),
});
const data = await response.json();
console.log(data.choices[0].message.content);
```
```python title="Python"
import requests
response = requests.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {{API_KEY_REF}}",
"Content-Type": "application/json",
},
json={
"model": "{{MODEL}}",
"messages": [
{
"role": "user",
"content": "What are the latest developments in AI?"
}
],
"plugins": [
{"id": "web"}
]
}
)
data = response.json()
print(data["choices"][0]["message"]["content"])
```
```bash title="cURL"
curl https://openrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer {{API_KEY_REF}}" \
-H "Content-Type: application/json" \
-d '{
"model": "{{MODEL}}",
"messages": [
{
"role": "user",
"content": "What are the latest developments in AI?"
}
],
"plugins": [
{"id": "web"}
]
}'
```
## Using Multiple Plugins
You can enable multiple plugins in a single request:
```json
{
"model": "openai/gpt-5.2",
"messages": [...],
"plugins": [
{ "id": "web", "max_results": 3 },
{ "id": "response-healing" }
],
"response_format": {
"type": "json_schema",
"json_schema": { ... }
}
}
```
## Default Plugin Settings
Organization admins and individual users can configure default plugin settings that apply to all API requests. This is useful for:
* Enabling plugins like web search or response healing by default across all requests
* Setting consistent plugin configurations without modifying application code
* Enforcing plugin settings that cannot be overridden by individual requests
To configure default plugin settings:
1. Navigate to [Settings > Plugins](https://openrouter.ai/settings/plugins)
2. Toggle plugins on/off to enable them by default
3. Click the configure button to customize plugin settings
4. Optionally enable "Prevent overrides" to enforce settings across all requests
In organizations, the Plugins settings page is only accessible to admins.
When "Prevent overrides" is enabled for a plugin, individual API requests cannot disable or modify that plugin's configuration. This is useful for enforcing organization-wide policies.
### Plugin precedence
Plugin settings are applied in the following order of precedence:
1. **Request-level settings**: Plugin configurations in the `plugins` array of individual requests
2. **Account defaults**: Settings configured in the Plugins settings page
If a plugin is enabled in your account defaults but not specified in a request, the default configuration will be applied. If you specify a plugin in your request, those settings will override the defaults.
If you want the account setting to take precedence, toggle on "Prevent overrides" in the config for the plugin. It will then be impossible for generations to override the config.
### Disabling a default plugin
If a plugin is enabled by default in your account settings, you can disable it for a specific request by passing `"enabled": false` in the plugins array:
```json
{
"model": "openai/gpt-5.2",
"messages": [...],
"plugins": [
{ "id": "web", "enabled": false }
]
}
```
This will turn off the web search plugin for that particular request, even if it's enabled in your account defaults.
## Model Variants as Plugin Shortcuts
The `:online` variant and the web search plugin are deprecated. Use the [`openrouter:web_search` server tool](/docs/guides/features/server-tools/web-search) instead.
Some plugins have convenient model variant shortcuts. For example, appending `:online` to any model ID enables web search:
```json
{
"model": "openai/gpt-5.2:online"
}
```
This is equivalent to:
```json
{
"model": "openai/gpt-5.2",
"plugins": [{ "id": "web" }]
}
```
See [Model Variants](/docs/guides/routing/model-variants) for more information about available shortcuts.
# Web Search
The web search plugin is deprecated. Use the [`openrouter:web_search` server tool](/docs/guides/features/server-tools/web-search) instead. Server tools give the model control over when and how often to search, rather than always running once per request.
You can incorporate relevant web search results for *any* model on OpenRouter by activating and customizing the `web` plugin, or by appending `:online` to the model slug:
```json
{
"model": "openai/gpt-5.2:online"
}
```
You can also append `:online` to `:free` model variants like so:
```json
{
"model": "openai/gpt-oss-20b:free:online"
}
```
Using web search will incur extra costs, even with free models. See the [pricing section](#pricing) below for details.
`:online` is a shortcut for using the `web` plugin, and is exactly equivalent to:
```json
{
"model": "openrouter/auto",
"plugins": [{ "id": "web" }]
}
```
The web search plugin is powered by native search for Anthropic, OpenAI, Perplexity, and xAI models.
For xAI models, the web search plugin enables both Web Search and X Search.
For other models, the web search plugin is powered by [Exa](https://exa.ai). It uses their ["auto"](https://docs.exa.ai/reference/how-exa-search-works#combining-neural-and-keyword-the-best-of-both-worlds-through-exa-auto-search) method (a combination of keyword search and embeddings-based web search) to find the most relevant results and augment/ground your prompt.
## Parsing web search results
Web search results for all models (including native-only models like Perplexity and OpenAI Online) are available in the API and standardized by OpenRouter to follow the same annotation schema in the [OpenAI Chat Completion Message type](https://platform.openai.com/docs/api-reference/chat/object):
```json
{
"message": {
"role": "assistant",
"content": "Here's the latest news I found: ...",
"annotations": [
{
"type": "url_citation",
"url_citation": {
"url": "https://www.example.com/web-search-result",
"title": "Title of the web search result",
"content": "Content of the web search result", // Added by OpenRouter if available
"start_index": 100, // The index of the first character of the URL citation in the message.
"end_index": 200 // The index of the last character of the URL citation in the message.
}
}
]
}
}
```
## Customizing the Web Plugin
The maximum results allowed by the web plugin and the prompt used to attach them to your message stream can be customized:
```json
{
"model": "openai/gpt-5.2:online",
"plugins": [
{
"id": "web",
"engine": "exa", // Optional: "native", "exa", "firecrawl", "parallel", or undefined
"max_results": 1, // Defaults to 5
"search_prompt": "Some relevant web results:", // See default below
"include_domains": ["example.com", "*.substack.com"], // Optional
"exclude_domains": ["reddit.com"] // Optional
}
]
}
```
By default, the web plugin uses the following search prompt, using the current date:
```
A web search was conducted on `date`. Incorporate the following web search results into your response.
IMPORTANT: Cite them using markdown links named using the domain of the source.
Example: [nytimes.com](https://nytimes.com/some-page).
```
## Domain Filtering
You can restrict which domains appear in web search results using `include_domains` and `exclude_domains`:
```json
{
"model": "openai/gpt-5.2",
"plugins": [
{
"id": "web",
"include_domains": ["example.com", "*.substack.com"],
"exclude_domains": ["reddit.com"]
}
]
}
```
Both fields accept an array of domain strings. You can use wildcards (`*.substack.com`) and path filtering (`openai.com/blog`).
### Engine Compatibility
| Engine | `include_domains` | `exclude_domains` | Notes |
| ------------- | :---------------: | :---------------: | ----------------------------------------------- |
| **Exa** | Yes | Yes | Both can be used simultaneously |
| **Parallel** | Yes | Yes | Either can be used, they are mutually exclusive |
| **Native** | Varies | Varies | See provider notes below |
| **Firecrawl** | Yes | Yes | Mutually exclusive (cannot use both at once) |
### Native Provider Behavior
When using native search, domain filter support depends on the provider:
* **Anthropic**: Supports both `include_domains` and `exclude_domains`, but they are mutually exclusive — you cannot use both at once
* **OpenAI**: Supports `include_domains` only; `exclude_domains` is silently ignored
* **xAI**: Supports both, but they are mutually exclusive with a maximum of 5 domains each
## X Search Filters (xAI only)
When using xAI models with web search enabled,
OpenRouter automatically adds the `x_search` tool
alongside `web_search`. You can pass filter
parameters to control X/Twitter search results
using the top-level `x_search_filter` parameter:
```json
{
"model": "x-ai/grok-4.1-fast",
"messages": [
{
"role": "user",
"content": "What are people saying about OpenRouter?"
}
],
"plugins": [{ "id": "web" }],
"x_search_filter": {
"allowed_x_handles": ["OpenRouterAI"],
"from_date": "2025-01-01",
"to_date": "2025-12-31"
}
}
```
### Filter Parameters
| Parameter | Type | Description |
| ---------------------------- | --------- | ----------------------------------------------------------- |
| `allowed_x_handles` | string\[] | Only include posts from these handles (max 10) |
| `excluded_x_handles` | string\[] | Exclude posts from these handles (max 10) |
| `from_date` | string | Start date for search range (ISO 8601, e.g. `"2025-01-01"`) |
| `to_date` | string | End date for search range (ISO 8601, e.g. `"2025-12-31"`) |
| `enable_image_understanding` | boolean | Enable analysis of images within posts |
| `enable_video_understanding` | boolean | Enable analysis of videos within posts |
`allowed_x_handles` and `excluded_x_handles` are
mutually exclusive — you cannot use both in the
same request. If validation fails, the filter is
silently dropped and a basic `x_search` tool is
used instead.
## Engine Selection
The web search plugin supports the following options for the `engine` parameter:
* **`native`**: Always uses the model provider's built-in web search capabilities
* **`exa`**: Uses Exa's search API for web results
* **`firecrawl`**: Uses [Firecrawl](https://firecrawl.dev)'s search API
* **`parallel`**: Uses [Parallel](https://parallel.ai)'s search API for web results
* **`undefined` (not specified)**: Uses native search if available for the provider, otherwise falls back to Exa
### Default Behavior
When the `engine` parameter is not specified:
* **Native search is used by default** for OpenAI, Anthropic, Perplexity, and xAI models that support it
* **Exa search is used** for all other models or when native search is not supported
When you explicitly specify `"engine": "native"`, it will always attempt to use the provider's native search, even if the model doesn't support it (which may result in an error).
### Forcing Engine Selection
You can explicitly specify which engine to use:
```json
{
"model": "openai/gpt-5.2",
"plugins": [
{
"id": "web",
"engine": "native"
}
]
}
```
Or force Exa search even for models that support native search:
```json
{
"model": "openai/gpt-5.2",
"plugins": [
{
"id": "web",
"engine": "exa",
"max_results": 3
}
]
}
```
### Firecrawl
Firecrawl is a BYOK (bring your own key) search engine. To use it:
1. Go to your [OpenRouter plugin settings](https://openrouter.ai/settings/plugins) and select Firecrawl as the web search engine
2. Accept the [Firecrawl Terms of Service](https://www.firecrawl.dev/terms-of-service) — this automatically creates a Firecrawl account linked to your email
3. Your account starts with **10,000 free credits** (credits expire after 3 months)
Once set up, Firecrawl searches use your Firecrawl credits directly — there is no additional charge from OpenRouter.
```json
{
"model": "openai/gpt-5.2",
"plugins": [
{
"id": "web",
"engine": "firecrawl",
"max_results": 5
}
]
}
```
Firecrawl supports `include_domains` and `exclude_domains`, but they are mutually exclusive — you cannot use both in the same request.
### Parallel
[Parallel](https://parallel.ai) is a search engine that supports domain filtering and uses OpenRouter credits at \$0.005 per request. Includes up to 10 results in a request, then \$0.001 per additional result.
```json
{
"model": "openai/gpt-5.2",
"plugins": [
{
"id": "web",
"engine": "parallel",
"max_results": 5,
"include_domains": ["arxiv.org"]
}
]
}
```
### Engine-Specific Pricing
* **Native search**: Pricing is passed through directly from the provider (see provider-specific pricing info below)
* **Exa search**: Uses OpenRouter credits at \$4 per 1000 results (default 5 results = \$0.02 per request)
* **Parallel search**: Uses OpenRouter credits at \$0.005 per request. Includes up to 10 results in a request, then \$0.001 per additional result
* **Firecrawl search**: Uses your Firecrawl credits directly, refill at [Firecrawl.dev](https://www.firecrawl.dev)
## Pricing
### Exa Search Pricing
When using Exa search (either explicitly via `"engine": "exa"` or as fallback), the web plugin uses your OpenRouter credits and charges *\$4 per 1000 results*. By default, `max_results` set to 5, this comes out to a maximum of \$0.02 per request, in addition to the LLM usage for the search result prompt tokens.
### Native Search Pricing (Provider Passthrough)
Some models have built-in web search. These models charge a fee based on the search context size, which determines how much search data is retrieved and processed for a query.
### Search Context Size Thresholds
Search context can be 'low', 'medium', or 'high' and determines how much search context is retrieved for a query:
* **Low**: Minimal search context, suitable for basic queries
* **Medium**: Moderate search context, good for general queries
* **High**: Extensive search context, ideal for detailed research
### Specifying Search Context Size
You can specify the search context size in your API request using the `web_search_options` parameter:
```json
{
"model": "openai/gpt-4.1",
"messages": [
{
"role": "user",
"content": "What are the latest developments in quantum computing?"
}
],
"web_search_options": {
"search_context_size": "high"
}
}
```
Refer to each provider's documentation for their native web search pricing info:
* [OpenAI Pricing](https://platform.openai.com/docs/pricing#built-in-tools)
* [Anthropic Pricing](https://docs.claude.com/en/docs/agents-and-tools/tool-use/web-search-tool#usage-and-pricing)
* [Perplexity Pricing](https://docs.perplexity.ai/getting-started/pricing)
* [xAI Pricing](https://docs.x.ai/docs/models#tool-invocation-costs)
Native web search pricing only applies when using `"engine": "native"` or when native search is used by default for supported models. When using `"engine": "exa"`, the Exa search pricing applies instead.
# Response Healing
The Response Healing plugin automatically validates and repairs malformed JSON responses from AI models. When models return imperfect formatting – missing brackets, trailing commas, markdown wrappers, or mixed text – this plugin attempts to repair the response so you receive valid, parseable JSON.
## Overview
Response Healing provides:
* **Automatic JSON repair**: Fixes missing brackets, commas, quotes, and other syntax errors
* **Markdown extraction**: Extracts JSON from markdown code blocks
## How It Works
The plugin activates for non-streaming requests when you use `response_format` with either `type: "json_schema"` or `type: "json_object"`, and include the response-healing plugin in your `plugins` array. See the [Complete Example](#complete-example) below for a full implementation.
## What Gets Fixed
The Response Healing plugin handles common issues in LLM responses:
### JSON Syntax Errors
**Input:** Missing closing bracket
```text
{"name": "Alice", "age": 30
```
**Output:** Fixed
```json
{"name": "Alice", "age": 30}
```
### Markdown Code Blocks
**Input:** Wrapped in markdown
````text
```json
{"name": "Bob"}
```
````
**Output:** Extracted
```json
{"name": "Bob"}
```
### Mixed Text and JSON
**Input:** Text before JSON
```text
Here's the data you requested:
{"name": "Charlie", "age": 25}
```
**Output:** Extracted
```json
{"name": "Charlie", "age": 25}
```
### Trailing Commas
**Input:** Invalid trailing comma
```text
{"name": "David", "age": 35,}
```
**Output:** Fixed
```json
{"name": "David", "age": 35}
```
### Unquoted Keys
**Input:** JavaScript-style
```text
{name: "Eve", age: 40}
```
**Output:** Fixed
```json
{"name": "Eve", "age": 40}
```
## Complete Example
```typescript title="TypeScript"
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: 'Bearer {{API_KEY_REF}}',
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: '{{MODEL}}',
messages: [
{
role: 'user',
content: 'Generate a product listing with name, price, and description'
}
],
response_format: {
type: 'json_schema',
json_schema: {
name: 'Product',
schema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Product name'
},
price: {
type: 'number',
description: 'Price in USD'
},
description: {
type: 'string',
description: 'Product description'
}
},
required: ['name', 'price']
}
}
},
plugins: [
{ id: 'response-healing' }
]
}),
});
const data = await response.json();
const product = JSON.parse(data.choices[0].message.content);
// The plugin attempts to repair malformed JSON syntax
console.log(product.name, product.price);
```
```python title="Python"
import requests
import json
response = requests.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {{API_KEY_REF}}",
"Content-Type": "application/json",
},
json={
"model": "{{MODEL}}",
"messages": [
{
"role": "user",
"content": "Generate a product listing with name, price, and description"
}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "Product",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Product name"
},
"price": {
"type": "number",
"description": "Price in USD"
},
"description": {
"type": "string",
"description": "Product description"
}
},
"required": ["name", "price"]
}
}
},
"plugins": [
{"id": "response-healing"}
]
}
)
data = response.json()
product = json.loads(data["choices"][0]["message"]["content"])
# The plugin attempts to repair malformed JSON syntax
print(product["name"], product["price"])
```
## Limitations
Response Healing only applies to non-streaming requests.
Some malformed JSON responses may still be unrepairable. In particular, if the response is truncated by `max_tokens`, the plugin will not be able to repair it.