Amazon OpenSearch Service now supports custom plugins, allowing advanced users to extend the search engine’s functionality beyond its out-of-the-box features. In this deep dive, we focus on the newest plugin type – Script Plugins – and explore how to create one, how they differ from built-in scripts, and best practices for developing and deploying them. This guide provides a tutorial-style walkthrough with detailed technical insights.
What Are Custom Plugins?
OpenSearch plugins are modular extensions that run within the OpenSearch cluster, enabling custom functionality such as analyzers, queries, and scoring logic. While self-managed OpenSearch (and historically Elasticsearch) has long supported these plugins, Amazon OpenSearch Service (AOS) did not allow user-developed plugins—until late 2024.
That changed with the release of version 2.15, which introduced support for custom plugins in the managed service. This opened up new possibilities for developers to tailor AOS to meet specific application needs.
Timeline of Plugin Support in Amazon OpenSearch Service
-
Version 2.15 (Late 2024) – Custom plugin support launched with initial focus on
AnalysisPlugin
andSearchPlugin
. -
May 2025 –
ScriptPlugin
support was added, enabling advanced use cases such as custom scoring, filtering, and field transformations within queries.
Currently Supported Plugin Types in AOS
-
AnalysisPlugin
– Add custom analyzers, tokenizers, or filters to extend text analysis. -
SearchPlugin
– Create custom query types, scoring logic, suggesters, or aggregations. -
MapperPlugin
– Define custom field types and control how data is indexed and stored. -
ScriptPlugin
(since 2.15) – Embed custom scripting engines to implement complex query-time logic.
⚠️ As of mid-2025, other plugin types—such as
IngestPlugin
,ActionPlugin
, andEnginePlugin
—are not supported in Amazon OpenSearch Service.
Script Plugins: Core Concepts
What Is a Script Plugin?
In OpenSearch, scripts (written in the built-in Painless scripting language) are often used in queries for custom scoring, filtering, or field transformations. A script plugin allows you to go beyond what Painless scripts can do by adding new scripting logic in Java or even introducing entirely new scripting languages to OpenSearch. As the Tinder engineering team put it, a script plugin is essentially a run()
function that takes query parameters and a document (“lookup”) as input and produces a relevance score (or decision) as output. In other words, a script plugin lets you inject custom code into the scoring process of the search engine.
Script Plugins vs. Painless Scripts
Script plugins offer several advantages over standard Painless inline scripts:
- Richer Logic – You can implement complex algorithms and leverage Java libraries or external frameworks. (Painless is sandboxed and limited to basic operations.)
- New Scripting Languages – You aren’t limited to Painless; a plugin can define a new script language or domain-specific language for OpenSearch queries.
- Performance – Custom script engines are written in Java and compiled, which can yield better performance than interpreted Painless scripts for heavy computations.
- Greater Control – Script plugins run inside the OpenSearch JVM with broader privileges. This gives you more power (e.g. access to low-level APIs or optimized data structures) than the sandboxed environment of Painless. (Of course, with this power comes the responsibility to ensure safety and stability.)
When to Choose Script Plugins
Scenario | Script Plugin | Painless Script | Application Layer |
---|---|---|---|
Performance | ✅ Best (compiled Java) | ⚠️ Moderate | ❌ Higher latency |
Complex Logic | ✅ Full Java capabilities | ⚠️ Limited | ✅ Most flexible |
Deployment | ⚠️ Requires deployment | ✅ No deployment | ✅ No deployment |
Updates | ⚠️ Requires redeployment | ✅ Easy to update | ✅ Easy to update |
External Services | ❌ Not allowed | ❌ Not allowed | ✅ Full access |
Resource Usage | ✅ Optimized | ⚠️ Moderate | ❌ Higher overhead |
Before implementing a script plugin, consider these alternatives:
Painless Scripts: For simpler use cases, offering a good balance of flexibility and performance with no deployment required.
Application Layer: When you need maximum flexibility or access to external services, though it comes with higher latency.
Built-in Features: OpenSearch's built-in features like function score queries, runtime fields, and script fields might already provide what you need.
Limitations and Considerations for Script Plugins in Amazon OpenSearch Service
Before using script plugins in Amazon OpenSearch Service, be aware of the following constraints:
No External API Calls
Script plugins can't access external services or HTTP endpoints. This sandboxing ensures security and performance stability.
Version Compatibility
Only specific OpenSearch versions support custom plugins:
- Supported: 2.15, 2.17
- Not supported: 2.19 (in our tests in June 2025, plugin validation failed on AWS-managed clusters)
Blue/Green Deployment Required
Plugin installation triggers a blue/green deployment. The cluster is recreated behind the scenes. There is no downtime, but installation can take time. Plan accordingly in production.
Feature Limitations
Custom plugins disable several AWS-managed features:
- Cross-Cluster Search/Replication
- Remote Reindexing
- Auto-Tune
- Multi-AZ with Standby
- AWS-hosted OpenSearch Dashboards (requires self-hosting)
Performance Impact
Script logic runs per document at query time and may increase latency or resource usage.
Developing a Custom Script Plugin (Step-by-Step)
In this section, we’ll walk through creating a custom script plugin for OpenSearch. Our example will be a “Hello World” script plugin with a GenAI-powered scoring function. This plugin will demonstrate:
- Custom Scoring Logic – A scoring algorithm that considers multiple factors (product rating, price, stock availability, recency of updates, etc.) to adjust relevance scores.
- Parameterized Configuration – The ability to adjust scoring weights and thresholds at query time via parameters (so you can fine-tune the behavior without changing the code).
- Built-in Optimizations – Efficient calculations, input validation, and error handling to minimize performance overhead and ensure stability.
Development Environment Setup
For this example, we have a sample project available on GitHub that contains the full plugin implementation. You can use it as a starting point for your own plugin development:
# Clone the example repository
git clone https://github.com/vidanov/opensearch-script-plugin-hello-world.git
cd opensearch-script-plugin-hello-world
(Ensure you have a Java 17 JDK and Gradle available, as OpenSearch 2.x plugins use Java 17.)
Project Structure and Organization
The project follows a typical OpenSearch plugin layout. Key files and directories include:
genai-script-plugin-with-ai/
├── src/
│ ├── main/java/com/example/
│ │ └── HelloWorldScriptPlugin.java # Main plugin implementation (Java)
│ ├── main/resources/
│ │ └── plugin-descriptor.properties # Plugin metadata (name, version, type)
│ └── test/java/com/example/
│ └── HelloWorldScriptPluginTest.java # Unit tests for the plugin logic
├── build.gradle # Gradle build configuration for OpenSearch
└── README.md # Documentation and usage instructions
This structure is generated by the OpenSearch plugin build tools. The Java class HelloWorldScriptPlugin.java
is our primary focus – it defines the plugin and the custom script engine.
Core Implementation
Our plugin class needs to extend the base Plugin
class and implement the ScriptPlugin
interface provided by OpenSearch. This requires us to supply a custom Script Engine. Essentially, the script engine is where we define the logic of our new scripting language. Below is a key part of the implementation:
public class HelloWorldScriptPlugin extends Plugin implements ScriptPlugin {
@Override
public ScriptEngine getScriptEngine(Settings settings, Collection<ScriptContext<?>> contexts) {
return new HelloWorldScriptEngine();
}
}
In this snippet, we override getScriptEngine(...)
to return an instance of our custom HelloWorldScriptEngine
. This engine (implemented as an inner class or separate class) registers a new script language – in our case called "hello_world"
– with OpenSearch. The script engine is responsible for compiling script source code and producing a ScoreScript
that OpenSearch can execute for each document during queries.
How the script engine works: Inside HelloWorldScriptEngine
, we define how to handle different script contexts. For a score script, our engine provides a factory that uses the parameters and document fields to calculate a score. For example, if the script source
is "custom_score"
, our engine’s ScoreScript
will read the document’s fields (rating, price, stock, etc.) and the provided params (thresholds, boosts, penalties) and compute a final score. All of this logic is written in Java, giving us full flexibility in how scoring is done. (You could also implement other script functions or additional script source
names, e.g. different scoring strategies, within the same plugin.)
Parameterized Scoring Implementation
One of the most powerful features of script plugins in Amazon OpenSearch Service is the ability to parameterize the scoring logic. Instead of hard-coding thresholds and weights, the plugin can read parameters from the query at runtime.
This makes your scoring configurable, testable, and adaptive — ideal for scenarios like A/B testing, personalization, or multi-tenant ranking logic.
Why Use Parameterized Scoring?
- Dynamic Tuning at Query Time
- No Plugin Redeploy Required
- Multiple Strategies via One Plugin
- Support for A/B Testing and Experiments
How It Works (With Code Example)
In your GenAIScoreScriptFactory
, parameters are parsed using helpers like pDouble()
and pString()
:
double ratingThreshold = pDouble(params, "rating_threshold", 4.5);
double priceThreshold = pDouble(params, "price_threshold", 100.0);
double ratingWeight = pDouble(params, "rating_weight", 0.4);
String ratingField = pString(params, "rating_field", "rating");
These values are passed at query time. You can modify them without changing the plugin code.
Example: Passing Parameters in a Query
{
"query": {
"script_score": {
"query": { "match_all": {} },
"script": {
"source": "weighted_score",
"lang": "hello_world",
"params": {
"rating_field": "avg_rating",
"price_field": "discounted_price",
"rating_weight": 0.5,
"price_weight": 0.2,
"max_price": 500.0
}
}
}
}
}
In this example:
- We invoke the
weighted_score
strategy inside the plugin. - We override field names and scoring weights at query time.
Switching Between Scoring Strategies
The plugin supports different scoring strategies (weighted_score
, custom_score
, popularity_score
) based on the script source:
@Override
public double execute(ExplanationHolder explanation) {
if ("weighted_score".equals(scriptSource)) {
return weightedScore();
} else if ("popularity_score".equals(scriptSource)) {
return popularityScore();
} else {
return customScore(); // fallback
}
}
You can switch strategies with:
"source": "popularity_score"
No need to rebuild or redeploy — simply change the script source in the query.
Using Amazon Q Developer to Create and Implement a Java Score Script Plugin
If you're building custom scoring logic for Amazon OpenSearch Service, you don’t have to start from scratch. Amazon Q Developer can generate the entire Java class for your plugin — including parameterized scoring logic, plugin structure, and runtime selection of different strategies — from a single, well-crafted prompt.
Step 1: Define the Logic in Plain English
Start by describing your goal clearly. For example:
"I want to create a scoring plugin that boosts well-rated and cheap products, penalizes out-of-stock items, and includes a popularity score based on views, sales, and review count. All thresholds and weights should be configurable via query parameters."
Step 2: Use a Single Prompt in Q Developer
You can paste the following prompt into Q Developer to generate the entire plugin code:
Create a Java ScoreScript plugin for Amazon OpenSearch Service (Java 11 compatible) named `HelloWorldScriptPlugin`.
The plugin should:
1. Support a strategy called `popularity_score` with the following logic:
- Normalize `views`, `sales`, `review_count`, and `rating`
- Use logarithmic scaling for `views`, `sales`, and `review_count`
- Use: log(value + 1) / log(max_value + 1)
- Normalize `rating` by dividing by 5.0
2. Allow configurable weights via params:
- `views_weight`, `sales_weight`, `reviews_weight`, `rating_weight`
- Provide default weights (e.g., 0.25 for each)
3. Compute the final score as the weighted sum of the normalized values.
4. Parse parameters using helper method `pDouble(params, key, defaultValue)`
5. Extract document field values using `docDouble(field, defaultValue)`.
6. Add a fallback strategy `custom_score` with simplified logic: multiply three boosts based on rating, price, and stock.
7. Add support for passing `scriptSource` as a string (e.g. "popularity_score") to select between scoring strategies.
Generate the full plugin, including the `Plugin`, `ScriptPlugin`, `ScoreScript.Factory`, and `ScoreScript` logic.
Step 3: What You'll Get from Q Developer
Q Developer will typically generate:
- A plugin class implementing
ScriptPlugin
- A custom
ScriptEngine
with support forScoreScript
- A factory that reads parameters and selects logic
- A
ScoreScript
that implements:-
customScore()
logic with boost/penalty -
popularityScore()
logic using weighted normalized values -
execute()
method with strategy selection logic
-
- Helper methods for safe parameter and field access
Step 4: Adjust and Compile
After you get the code:
- Review field names and adjust if needed.
- Place the code inside a Gradle-based plugin scaffold.
- Ensure Java 17 and OpenSearch 2.x compatibility.
- Use the OpenSearch Gradle plugin to build your
.zip
package.
You can then deploy this plugin to your Amazon OpenSearch Service cluster.
Installation and Operations
Once your custom script plugin is developed and tested, the next step is to deploy it to an Amazon OpenSearch Service domain. Deploying a plugin on AWS involves preparing the plugin as a zip package, uploading it, and then instructing the OpenSearch Service to install it on your domain. Here we outline the requirements and steps for a successful deployment.
Prerequisites and Requirements
Before deploying a custom plugin, ensure your target OpenSearch domain meets the following requirements (these are mandated by AWS for custom plugins):
- OpenSearch Version 2.15 or 2.17 – Custom plugins are supported only on versions 2.15+ (and remember, not on 2.19 yet).
- Node-to-node encryption enabled – Your domain must have node-to-node encryption turned on.
- Encryption at rest enabled – The domain must have encryption of data at rest.
- HTTPS enforced – Only HTTPS access is allowed (no plaintext HTTP).
-
TLS security policy – The domain should use a modern TLS security policy (e.g.
Policy-Min-TLS-1-2-PFS-2023-10
or newer).
# Upload to S3
aws s3 cp build/distributions/hello-world-genai-script-plugin.zip s3://your-bucket/plugins/
# Create package
aws opensearch create-package \
--package-name hello-world-genai-script-plugin \
--package-type ZIP-PLUGIN \
--package-source S3BucketName=genai-plugin-bucket,S3Key=plugins/hello-world-genai-script-plugin.zip \
--engine-version OpenSearch_2.15 \
--region <YOUR_AWS_REGION>
# Wait till the package is validated, associate
aws opensearch associate-package \
--package-id <PACKAGE_ID> \
--domain-name <OPENSEARCH_DOMAIN_NAME> \
--region <YOUR_AWS_REGION>
# Verify
aws opensearch list-packages-for-domain --domain-name <OPENSEARCH_DOMAIN_NAME>
Plugin installation triggers blue/green deployment—no downtime but takes time.
You can filter the custom plugins in the AWS management console
Plugin usage examples
Example: Basic Script Score Query
To illustrate, consider an e-commerce product search scenario. We want to boost products that are highly rated, reasonably priced, in stock, and recently updated. We have deployed our HelloWorldScriptPlugin
which defines a script language "hello_world"
with a script function called "custom_score"
. Here’s how a search query might use this custom script with parameters:
# Let us create an example product index
PUT products_test
{
"settings": {
"index": {
"number_of_shards": 1,
"number_of_replicas": 0
}
},
"mappings": {
"properties": {
"name": {
"type": "text"
},
"rating": {
"type": "float"
},
"price": {
"type": "float"
},
"stock": {
"type": "integer"
},
"last_updated": {
"type": "date"
},
"views": {
"type": "integer"
},
"sales": {
"type": "integer"
}
}
}
}
# Let us add some products
POST products_test/_bulk?refresh=true
{"index":{}}
{"name":"Alpha Wireless Headphones","rating":4.6,"price":45.0,"stock":12,"last_updated":"2025-05-20","views":1000,"sales":150}
{"index":{}}
{"name":"Beta Noise-Cancelling Headphones","rating":4.9,"price":120.0,"stock":5,"last_updated":"2025-05-05","views":5000,"sales":400}
{"index":{}}
{"name":"Gamma Budget Earbuds","rating":3.9,"price":25.0,"stock":30,"last_updated":"2025-04-15","views":250,"sales":50}
{"index":{}}
{"name":"Delta Premium Over-Ear","rating":4.3,"price":220.0,"stock":0,"last_updated":"2025-04-01","views":3000,"sales":250}
{"index":{}}
{"name":"Epsilon Sport Earbuds","rating":4.1,"price":60.0,"stock":8,"last_updated":"2025-05-30","views":1800,"sales":300}
# And test the first query
GET products_test/_search
{
"query": {
"function_score": {
"query": {
"match": {
"name": "wireless headphones"
}
},
"script_score": {
"script": {
"lang": "hello_world",
"source": "custom_score",
"params": {
"rating_threshold": 4.0,
"rating_boost": 1.5,
"price_threshold": 50.0,
"cheap_boost": 1.3,
"expensive_penalty": 0.8,
"out_of_stock_penalty": 0.3,
"base_multiplier": 2.0,
"fallback_score": 0.5,
"recency_factor": 0.1,
"popularity_weight": 0.7,
"price_weight": 0.3
}
}
}
}
}
}
In this query, we search for products with descriptions matching “wireless headphones,” then apply a script_score
to modify the relevance score of each result using our plugin’s logic. We pass a number of parameters to custom_score
that control how the scoring works. Here’s what each parameter means:
-
Rating Threshold & Boost:
-
rating_threshold
– The minimum rating (e.g. average customer review) for a product to be considered “highly rated” and receive a boost. In our example, 4.0 stars. -
rating_boost
– The multiplier to apply if the product’s rating exceeds the threshold. (1.5x in this case, meaning highly-rated products get a 1.5× score boost from the rating factor.)
-
-
Price Parameters:
-
price_threshold
– A price cutoff to distinguish “cheap” vs “expensive” products (here $50). -
cheap_boost
– Multiplier for products priced under the threshold (1.3x, giving cheaper items a boost). -
expensive_penalty
– Multiplier for products over the threshold (0.8x, slightly penalizing pricier items).
-
-
Stock Parameter:
-
out_of_stock_penalty
– Multiplier to apply if an item is out of stock (0.3x in the example, significantly reducing the score for items that aren’t available to purchase).
-
-
Scoring Weights:
-
popularity_weight
– Weight (relative importance) of the item’s popularity in the overall score calculation (e.g. 0.7). -
price_weight
– Weight of the price factor in the overall score (e.g. 0.3). (These weights might be used inside the script to combine factors like popularity vs price impact. In our simple example, they could control a weighted sum, but how they’re applied depends on the script’s code.)
-
-
Recency Factor:
-
recency_factor
– A decay factor for recency (e.g. 0.1). This could be used to give a small boost to newer or recently updated products, or conversely to decay older items’ scores over time.
-
-
Base Multiplier:
-
base_multiplier
– An overall score multiplier applied at the end of the calculation (in our case 2.0, meaning after all other factors the score is doubled). This can be useful to calibrate the output of the script to a desired range or importance relative to the original query score.
-
-
Fallback Score:
-
fallback_score
– A default score to return if the script cannot compute a meaningful score for a document (for example, if required fields are missing or an error occurs). Here it’s 0.5. Using a fallback ensures that an error in script execution doesn’t completely drop the document from results; it still gets a baseline score.
-
These parameters correspond to how we wrote the script logic in the plugin. For instance, the plugin might check each document’s rating
field against rating_threshold
to decide whether to apply rating_boost
. It likely multiplies factors like rating boost, price boost/penalty, and stock penalty together (as we implemented) and then multiplies by base_multiplier
. The fallback_score
would be returned if any exception or missing data prevents the normal calculation.
Advanced Scoring Strategies
The real power of parameterized scripts is that you can adjust the scoring to different scenarios by simply changing the parameters. You might even store and reuse parameter sets for various contexts. For example:
Holiday Season: During a holiday shopping season, you might want to aggressively boost highly-rated products(assuming reviews matter more during gift shopping) and also raise the price threshold (people may spend more on gifts). You could use parameters like:
GET products_test/_search
{
"query": {
"script_score": {
"query": {
"match_all": {}
},
"script": {
"lang": "hello_world",
"source": "custom_score",
"params": {
"rating_threshold": 4.0,
"rating_boost": 2.0,
"price_threshold": 100.0,
"cheap_boost": 1.5,
"expensive_penalty": 0.9,
"out_of_stock_penalty": 0.1
}
}
}
}
}
Parameter Explanations:
-
rating_threshold
: 4.0 — Only highly rated items get boosted. -
rating_boost
: 2.0 — Extra score for items above the threshold. -
price_threshold
: 100.0 — Defines "cheap" items during promotions. -
cheap_boost
: 1.5 — Boost cheaper items more. -
expensive_penalty
: 0.9 — Slight penalty for costly products. -
out_of_stock_penalty
: 0.1 — Heavy penalty if the item is unavailable.
In this holiday configuration, we doubled the rating boost and increased the cheap boost, while being more lenient on expensive items (0.9 penalty is a mild reduction) because shoppers might splurge more.
Clearance Sale: For a clearance sale scenario, you might want to heavily favor cheaper items and don’t require as high a rating (since clearance items might not all be top-rated). A parameter set could be:
GET products_test/_search
{
"query": {
"script_score": {
"query": {
"match_all": {}
},
"script": {
"lang": "hello_world",
"source": "custom_score",
"params": {
"rating_threshold": 3.5,
"rating_boost": 1.2,
"price_threshold": 25.0,
"cheap_boost": 2.0,
"expensive_penalty": 0.5,
"out_of_stock_penalty": 0.2
}
}
}
}
}
Explanation of Parameters:
-
rating_threshold: 3.5
– Includes more moderately rated products. -
rating_boost: 1.2
– Smaller positive impact for meeting rating. -
price_threshold: 25.0
– Marks very cheap items. -
cheap_boost: 2.0
– Strong push for clearance deals. -
expensive_penalty: 0.5
– Heavy penalty for high-cost items. -
out_of_stock_penalty: 0.2
– Medium penalty for unavailable items.
Here, anything above $25 is considered expensive and heavily penalized (0.5 multiplier), encouraging cheaper items to rise to the top. Highly-rated isn’t as important (threshold 3.5 and only 1.2x boost), reflecting that during clearance, price and availability might matter more.
By adjusting parameters in this way, you can reuse the same plugin for very different ranking behaviors. Enterprise architects can define a few parameter sets (perhaps stored in the application or a config file) for various situations (seasonal promotions, different markets, etc.), and developers can apply them as needed in queries.
References
- Alexey Vidanov - A simple “Hello World” script plugin for OpenSearch Template in Github for the Amazon OpenSearch Service managed domain, written in Java. A great starting point if you want to learn how to create and integrate custom script plugins into your OpenSearch cluster.
- Amitai Stern – “Taking the Leap: My First Steps in OpenSearch Plugins” (Logz.io Blog) – Introduction to building a simple OpenSearch REST plugin, with prerequisites like Java and Gradle and step-by-step examples of a “Hello World” plugin.
- Amazon AWS – “Amazon OpenSearch Service now supports Custom Plugins” (Nov 21, 2024) – AWS announcement of custom plugin support in the managed service, including the motivation for custom plugins and the scope of supported plugin types.
- OpenSearch Project – “https://opensearch.org/blog/plugins-intro/” (Dec 2, 2021) – OpenSearch official blog post explaining the plugin architecture, how plugins are installed and loaded, and the role of the Security Manager and plugin policy files.
- OpenSearch Forum – “Set up communication with external service in OpenSearch plugin” (Discussion, May 2025) – A community discussion highlighting the challenges of making external network calls from within a plugin (SecurityManager restrictions and potential workarounds).
- OpenSearch Plugin Template (GitHub) – The official OpenSearch plugin template repository, useful as a starting point for new plugins. It contains the boilerplate code and files needed for a basic plugin project.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.