cfn-handler
A modern, well-engineered Python library for writing AWS CloudFormation
Custom Resource lifecycle handlers. Inspired by — but not derived from —
the unmaintained aws-cloudformation/custom-resource-helper.
Why cfn-handler?
CloudFormation Custom Resources are AWS Lambda functions invoked during stack operations. Getting them right is harder than it looks: you must respond to a presigned URL within the Lambda timeout, handle CREATE/UPDATE/DELETE semantics correctly, manage long-running operations via polling, and never leave CloudFormation hanging.
cfn-handler does the boilerplate so you can focus on the resource logic.
from cfn_handler import CustomResource
resource = CustomResource()
@resource.create
def on_create(event, context):
# do work, return data dict
return {"Endpoint": "https://my.example.com"}
@resource.update
def on_update(event, context):
return {"Endpoint": "https://my.example.com"}
@resource.delete
def on_delete(event, context):
pass
def handler(event, context):
return resource(event, context)
That's the entire happy path. For long-running operations:
@resource.create
def on_create(event, context):
# kick off work; return None to defer
pass
@resource.poll_create
def on_poll_create(event, context):
# check status; return data when done, raise on failure
if check_ready():
return {"Endpoint": "https://my.example.com"}
# else: do nothing, library will reschedule
Installation
pip install cfn-handler
# or with uv
uv add cfn-handler
cfn-handler requires Python 3.10+ and has zero runtime dependencies.
Polling support uses boto3 lazily; boto3 ships preinstalled in the AWS
Lambda Python runtimes, so no extra install is needed there.
Or use the AWS Lambda Layer
Every release publishes a public Lambda Layer in ~17 commercial regions. To
use it, reference the ARN in your function definition — no pip install
during deploy, no vendoring into your function package:
# SAM
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: python3.12
Layers:
- arn:aws:lambda:us-east-1:<account-id>:layer:cfn-handler:N
Find the right ARN for your region in the latest release notes (per-region table) or the JSON manifest:
curl -fsSL https://github.com/igorlg/cfn-handler/releases/latest/download/layer-arns.json
See layer/README.md for SAM/CDK snippets, the alternative
deploy-it-yourself path, and the maintainer's account ID.
Comparison to crhelper
If you're coming from crhelper, the model will feel familiar:
| Feature | crhelper | cfn-handler |
|---|---|---|
| Lifecycle decorators | @helper.create, @helper.update, @helper.delete |
@resource.create, @resource.update, @resource.delete |
| Polling decorators | @helper.poll_create ... |
@resource.poll_create ... |
| Type hints | None | Full inline (py.typed) |
| Python versions | 3.6+ (last release 2020) | 3.10–3.14 |
| Build | setup.py |
pyproject.toml (PEP 621, hatchling) |
| Tests | unittest | pytest + hypothesis |
| Coverage gate | None | 95% line + branch |
| Type checkers | None | mypy strict + pyright strict |
| Releases | Manual | release-please + PyPI Trusted Publishing |
| Maintained | No (since 2020) | Yes |
The public API is intentionally similar — cfn-handler is a clean
re-implementation that carries forward the proven semantics and fixes 14
long-standing upstream issues that never merged. See CHANGELOG.md
for the full list.
Examples
Working SAM-deployable examples live in examples/:
examples/basic/— minimal Create/Update/Delete handler.examples/polled/— long-running operation with polling.examples/with-physical-id/— explicit physical resource id (replacement on update).examples/failing/— handler that fails, demonstrating FAILED-response semantics.
Testing your handlers
cfn-handler ships first-class testing helpers under cfn_handler.testing.
The dispatch flow runs in-process; no HTTP, no boto3, no moto setup
required for unit tests:
from cfn_handler import CustomResource
from cfn_handler.testing import assert_success, make_event
def test_my_create_handler():
resource = CustomResource()
@resource.create
def on_create(event, context):
return {"Endpoint": "https://x.example"}
replay = resource.replay(make_event())
assert_success(replay, data={"Endpoint": "https://x.example"})
Available surface:
CustomResource.replay(event, context=None)— execute the dispatch in-process, returning a structuredReplay(status, data, reason, payload, ...).make_event(...),make_context(...)— factories with safe defaults.assert_success,assert_failed,assert_deferred— assertion helpers with informative messages on failure.- pytest fixtures
cfn_create_event,cfn_update_event,cfn_delete_event,cfn_lambda_context— auto-discovered via thepytest11entry point; nopytest_pluginsdeclaration needed.
For long-running (polled) handlers, the first replay() returns
Replay(status="DEFERRED") and mutates the event with marker keys.
A second replay() with the mutated event resumes through the poll
handler — useful for testing both halves of a polled lifecycle without
provisioning EventBridge rules.
Project status
v1.0.0 — first stable release. Follows Semantic Versioning.
The public API surface is exactly what's exported from cfn_handler.__all__;
everything under cfn_handler._internal is implementation detail and may
change between minor versions.
Contributing
See CONTRIBUTING.md for the development workflow,
commit conventions, and lockfile policy. For contributors who use Nix, a
flake.nix provides a reproducible dev shell. The CI/release pipeline
itself is documented in docs/CI.md, including the
local-replay tooling and a postmortem of the v1.0.0 release failure.
License
Apache License 2.0 — see LICENSE and NOTICE.
This project is inspired by, but does not include code from, aws-cloudformation/custom-resource-helper. Both projects are licensed under Apache 2.0.
Metadata
Release files for cfn-handler 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| cfn_handler-1.3.0.tar.gz | 32.9 kB | With provenance | Details for cfn_handler-1.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| cfn_handler-1.3.0-py3-none-any.whl | Python 3 | none | any | Details for cfn_handler-1.3.0-py3-none-any.whl |
Total release size: 66.7 kB
Release files / cfn_handler-1.3.0.tar.gz
| Download URL | cfn_handler-1.3.0.tar.gz |
|---|---|
| Size | 32.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ddff3dea74843352ee4602a988fe4e6d24b214e50cc4f472729c3f1e4757900f
|
|
BLAKE2b-256 checksum How to use checksums |
39edba9072bdc96cb4d476bfd8350ac4252d385f6087fc97d75b600fe03d789b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 25, 2026.
Transparency logRelease files / cfn_handler-1.3.0-py3-none-any.whl
| Download URL | cfn_handler-1.3.0-py3-none-any.whl |
|---|---|
| Size | 33.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1df4a0dc0cd51b2ccb9623904707c923b56ef2f2ea7b488541962730787f1df1
|
|
BLAKE2b-256 checksum How to use checksums |
8b27e07b75aefdca796de2f27c3d4a016b7eb8955101de42b7d40884cf7f7503
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 25, 2026.
Transparency logSecurity for cfn-handler 1.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for cfn_handler-1.3.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 25, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.