LocalStack LogoLocalStack Icon

How to build your own LocalStack Extension

LocalStack Extensions can facilitate specialized workflows or development needs. This guide walks through using LocalStack's Extensions API to create a mock API for Anthropic, a popular LLM provider, that allows you to test your application code without relying on the live service.

How to build your own LocalStack Extension

Introduction

LocalStack allows developers to develop and test cloud applications locally, but it goes beyond emulating AWS or Snowflake services. It can also be extended with custom functionality to support workflows or development needs that don’t fit into the standard cloud service model. LocalStack Extensions make this possible: they are pluggable Python distributions that run alongside the LocalStack runtime in a Docker container.

Through the Extension API, you can hook into different lifecycle phases of LocalStack, execute custom code, or add custom routes to LocalStack’s HTTP gateway with your own server-side logic.

In this tutorial, we’ll build a LocalStack Extension that mocks Anthropic’s Messages API. The mock generates fake responses that the Anthropic Python SDK can parse, so you can test your Anthropic integration code locally without real API keys or network calls. Anthropic is just the example here; the same approach works for mocking any third-party API.

Key Concepts

LocalStack Extensions API

Extensions are Python packages that LocalStack loads at startup. Each extension is a class inheriting from Extension that can override these lifecycle hooks:

  • on_extension_load(): runs when LocalStack loads the extension
  • on_platform_start(): runs when LocalStack begins its startup sequence
  • on_platform_ready(): runs after LocalStack is fully initialized
  • update_gateway_routes(): adds custom HTTP routes to LocalStack’s gateway

The update_gateway_routes hook is how we’ll serve a mock API. You register route handlers, and LocalStack serves them on its gateway (port 4566 by default), alongside the emulated AWS services.

How the mock works

The Anthropic Python SDK supports overriding the base URL when creating a client. We’ll point the SDK at our LocalStack extension instead of the real Anthropic API. The extension exposes a /v1/messages endpoint that returns JSON matching the expected schema, and the SDK parses it as if it came from the real service.

Prerequisites

You’ll also need a PyPI account and API token if you plan to publish the extension.

Step 1: Scaffold the extension project

The LocalStack CLI includes a developer toolkit for extensions. Generate a new project from the official template:

Terminal window
pip install cookiecutter
localstack extensions dev new

The command prompts for a few fields. Here’s what I entered:

Terminal window
[1/9] project_name (My LocalStack Extension): LocalStack Extension for Anthropic
[2/9] project_short_description (All the boilerplate you need to create a LocalStack
extension.): LocalStack Extension for Anthropic
[3/9] project_slug (localstack-extension-for-anthropic): localstack-extension-anthropic
[4/9] module_name (localstack_extension_anthropic): localstack_anthropic
[5/9] class_name (LocalstackExtensionForAnthropic): LocalstackAnthropicExtension
[6/9] full_name (Jane Doe): Harsh Mishra
[7/9] email (jane@example.com): harsh@localstack.cloud
[8/9] github_username (janedoe): HarshCasper
[9/9] version (0.1.0):

You’ll get the following project structure:

Terminal window
localstack-extension-anthropic
├── Makefile
├── README.md
├── localstack_anthropic
├── __init__.py
└── extension.py
└── pyproject.toml

Install the project dependencies:

Terminal window
cd localstack-extension-anthropic
make install

A virtual environment is created under .venv/ and the extension is installed in editable mode.

Step 2: Start the extension in developer mode

Developer mode mounts your local extension directory into the LocalStack container, so you can edit code and restart without rebuilding anything. Enable it from inside the project directory:

Terminal window
localstack extensions dev enable ./

Then start LocalStack:

Terminal window
EXTENSION_DEV_MODE=1 localstack start

The logs should show:

Terminal window
──────────────────── LocalStack Runtime Log (press CTRL-C to quit) ────────────────────
==================================================
👷 LocalStack extension developer mode enabled 🏗
- mounting extension /opt/code/extensions/localstack-extension-anthropic
Resuming normal execution, ...
==================================================

The generated extension.py has a basic skeleton:

from localstack.extensions.api import Extension, http, aws
class LocalstackAnthropicExtension(Extension):
name = "localstack-extension-anthropic"
def on_extension_load(self):
print("MyExtension: extension is loaded")
def on_platform_start(self):
print("MyExtension: localstack is starting")
def on_platform_ready(self):
print("MyExtension: localstack is running")
def update_gateway_routes(self, router: http.Router[http.RouteHandler]):
pass
def update_request_handlers(self, handlers: aws.CompositeHandler):
pass
def update_response_handlers(self, handlers: aws.CompositeResponseHandler):
pass

To verify the extension loads correctly, modify on_platform_ready to use proper logging:

import logging
LOG = logging.getLogger(__name__)
class LocalstackAnthropicExtension(Extension):
name = "localstack-extension-anthropic"
# ...other methods...
def on_platform_ready(self):
LOG.info("my plugin is loaded and localstack is ready to roll!")

Restart LocalStack (localstack restart) and check the logs. You should see:

Terminal window
2026-04-01T11:39:44.415 INFO --- [ady_monitor)] l.extension : my plugin is loaded and localstack is ready to roll!
Ready.

Step 3: Implement the Anthropic Messages API

Anthropic’s Messages API accepts a POST to /v1/messages with a list of messages and returns a structured response. A typical request through the Python SDK:

import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[
{"role": "user", "content": "Hello, world"}
]
)

The response JSON looks like:

{
"content": [
{
"text": "Hi! My name is Claude.",
"type": "text"
}
],
"id": "msg_013Zva2CMHLNnXjNJJKqJ2EF",
"model": "claude-sonnet-4-5-20250929",
"role": "assistant",
"stop_reason": "end_turn",
"stop_sequence": null,
"type": "message",
"usage": {
"input_tokens": 25,
"output_tokens": 15
}
}

The important detail: content is a list of content blocks (each with a type and text field), not a plain string. Our mock needs to match this structure or the SDK will fail to deserialize it.

Create localstack_anthropic/mock_anthropic.py:

import json
import logging
from faker import Faker
from rolo import Request, Response, route
faker = Faker()
LOG = logging.getLogger(__name__)
class Api:
@route("/v1/messages", methods=["POST"])
def complete(self, request: Request):
data = request.get_data()
req = json.loads(data)
user_message = next(
(msg["content"] for msg in req.get("messages", []) if msg["role"] == "user"),
"",
)
model = req.get("model", "claude-sonnet-4-5-20250929")
if not user_message:
return Response(
json.dumps({"error": "Missing user message"}),
content_type="application/json",
status=400,
)
completion_text = faker.sentence(nb_words=12)
fake_id = f"msg_{faker.hexify(text='^^^^^^^^^^^^^^^^^^^^^^^^^^^^')}"
response_data = {
"content": [{"type": "text", "text": completion_text}],
"id": fake_id,
"model": model,
"role": "assistant",
"stop_reason": "end_turn",
"stop_sequence": None,
"type": "message",
"usage": {
"input_tokens": faker.random_int(min=10, max=50),
"output_tokens": faker.random_int(min=10, max=100),
},
}
return Response(json.dumps(response_data), content_type="application/json")

We’re using Rolo for route handling (a Python HTTP framework on top of Werkzeug, used internally by LocalStack) and Faker to generate random response text. The content field is returned as a list of TextBlock-style objects to match the Anthropic SDK’s expected format.

Step 4: Wire the mock into LocalStack’s gateway

Replace the contents of localstack_anthropic/extension.py to register the mock API as gateway routes:

import logging
from localstack import config
from localstack.extensions.api import Extension, http
from rolo.router import RuleAdapter, WithHost
from werkzeug.routing import Submount
LOG = logging.getLogger(__name__)
class LocalstackAnthropicExtension(Extension):
name = "localstack-extension-anthropic"
submount = "/_extension/anthropic"
subdomain = "anthropic"
def on_extension_load(self):
logging.getLogger("localstack_anthropic").setLevel(
logging.DEBUG if config.DEBUG else logging.INFO
)
def on_platform_start(self):
LOG.info("Anthropic extension: LocalStack is starting")
def on_platform_ready(self):
LOG.info("Anthropic extension: LocalStack is running")
def update_gateway_routes(self, router: http.Router[http.RouteHandler]):
from localstack_anthropic.mock_anthropic import Api
api = RuleAdapter(Api())
router.add(
[
Submount(self.submount, [api]),
WithHost(
f"{self.subdomain}.{config.LOCALSTACK_HOST.host}<__host__>",
[api],
),
]
)
LOG.info(
"Anthropic mock available at %s%s",
str(config.LOCALSTACK_HOST).rstrip("/"),
self.submount,
)
LOG.info(
"Anthropic mock available at %s",
f"{self.subdomain}.{config.LOCALSTACK_HOST}",
)

The mock Api class gets registered on two paths:

  • Submount at localhost.localstack.cloud:4566/_extension/anthropic (path prefix)
  • Subdomain at anthropic.localhost.localstack.cloud:4566 (works well as a base_url for SDKs)

Step 5: Add the Faker dependency

Open pyproject.toml and add faker to the dependencies list:

dependencies = [
"faker>=30.0.0"
]

Then rebuild and restart:

Terminal window
make clean && make install
localstack restart

Check the LocalStack logs. You should see:

Terminal window
2026-04-01T11:39:40.042 INFO --- [ MainThread] l.extension : Anthropic mock available at localhost.localstack.cloud:4566/_extension/anthropic
2026-04-01T11:39:40.042 INFO --- [ MainThread] l.extension : Anthropic mock available at anthropic.localhost.localstack.cloud:4566

Step 6: Test the mock with curl

Send a request to the subdomain route:

Terminal window
curl -s -X POST http://anthropic.localhost.localstack.cloud:4566/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: test" \
-d '{"model":"claude-sonnet-4-5-20250929","max_tokens":1024,"messages":[{"role":"user","content":"Hello, Claude"}]}'

Expected output (the text will differ since Faker generates random sentences):

{
"content": [
{
"type": "text",
"text": "Minute middle medical start fight well heavy poor camera those usually."
}
],
"id": "msg_1e00935c3e874b49ef943da4b752",
"model": "claude-sonnet-4-5-20250929",
"role": "assistant",
"stop_reason": "end_turn",
"stop_sequence": null,
"type": "message",
"usage": {
"input_tokens": 18,
"output_tokens": 12
}
}

The submount path works the same way:

Terminal window
curl -s -X POST http://localhost.localstack.cloud:4566/_extension/anthropic/v1/messages \
-H "Content-Type: application/json" \
-H "x-api-key: test" \
-d '{"model":"claude-sonnet-4-5-20250929","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}'

Step 7: Test with the Anthropic Python SDK

Create anthropic_test.py in the project root:

from anthropic import Anthropic
client = Anthropic(
base_url="http://anthropic.localhost.localstack.cloud:4566",
api_key="test"
)
message = client.messages.create(
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Hello, Claude",
}
],
model="claude-sonnet-4-5-20250929",
)
print(message.content[0].text)

We set base_url to the subdomain route and api_key to any non-empty string (the mock doesn’t check auth).

Install the Anthropic SDK and run it:

Terminal window
pip install anthropic
python3 anthropic_test.py

Output:

Terminal window
Administration feel quite provide interesting loss type.

The text is random Faker output, which is expected. What matters is that the SDK deserialized the response without errors: the Message object, TextBlock content, and all fields came through correctly.

Step 8: Publish the extension (optional)

To share the extension, publish it to PyPI. The generated Makefile includes a publish target:

Terminal window
make publish

You’ll need a PyPI API token configured for twine.

Once published, others can install it with:

Terminal window
localstack extensions install localstack-extension-anthropic

Or from a Git repository directly:

Terminal window
localstack extensions install "git+https://github.com/harshcasper/localstack-extension-anthropic.git#egg=localstack-extension-anthropic"

You can also add it to the Extensions Library on the LocalStack Web Application for a shareable install link.

Conclusion

We built a LocalStack Extension that mocks Anthropic’s Messages API. The same pattern works for any third-party HTTP API: define a Rolo route handler, wire it into the gateway through update_gateway_routes, and point your SDK at the LocalStack endpoint.

The mock here is intentionally bare-bones. You could extend it to handle streaming responses, message batches, or return proper error codes. Returning deterministic responses based on the input prompt would also be useful for integration tests.

For more extension examples and templates (including a React UI template), check the LocalStack Extensions repository.


Harsh Mishra
Harsh Mishra
Engineer at LocalStack
Harsh Mishra is an Engineer at LocalStack and AWS Community Builder. Harsh has previously worked at HackerRank, Red Hat, and Quansight, and specialized in DevOps, Platform Engineering, and CI/CD pipelines.