airflow-adapter
Airflow adapter pattern for v2/v3 API compatibility. Use when working with adapters, version detection, or adding new API methods that need to work across…
Builds Airflow 3.1+ plugins that embed FastAPI apps, custom UI pages, React components, middleware, macros, and operator links directly into the Airflow UI. Use when building anything custom inside Airflow 3.1+ that involves Python and a browser-facing interface - creating an
$ npx -y skills add astronomer/agents --skill airflow-plugins --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/airflow-pluginsContext preview
The summary Claude sees to decide when to auto-load this skill.
Builds Airflow 3.1+ plugins that embed FastAPI apps, custom UI pages, React components, middleware, macros, and operator links directly into the Airflow UI. Use when building anything custom inside Airflow 3.1+ that involves Python and a browser-facing interface - creating an
name: airflow-plugins description: Builds Airflow 3.1+ plugins that embed FastAPI apps, custom UI pages, React components, middleware, macros, and operator links directly into the Airflow UI. Use when building anything custom inside Airflow 3.1+ that involves Python and a browser-facing interface - creating an Airflow plugin, adding a custom UI page or nav entry, building FastAPI-backed endpoints inside Airflow, serving static assets from a plugin, embedding a React app, adding middleware to the API server, creating custom operator extra links, or calling the Airflow REST API from inside a plugin; also when AirflowPlugin, fastapi_apps, external_views, react_apps, or plugin registration come up.
Airflow 3 plugins let you embed FastAPI apps, React UIs, middleware, macros, operator buttons, and custom timetables directly into the Airflow process. No sidecar, no extra server.
> **CRITICAL**: Plugin components (fastapi_apps, react_apps, external_views) require **Airflow 3.1+**. **NEVER import `flask`, `flask_appbuilder`, or use `appbuilder_views` / `flask_blueprints`** — these are Airflow 2 patterns and will not work in Airflow 3. If existing code uses them, rewrite the entire registration block using FastAPI. > > **Security**: FastAPI plugin endpoints are **not automatically protected** by Airflow auth. If your endpoints need to be private, implement authentication explicitly using FastAPI's security utilities. > > **Restart required**: Changes to Python plugin files require restarting the API server. Static file changes (HTML, JS, CSS) are picked up immediately. Set `AIRFLOW__CORE__LAZY_LOAD_PLUGINS=False` during development to load plugins at startup rather than lazily. > > **Relative paths always**: In `external_views`, `href` must have no leading slash. In HTML and JavaScript, use relative paths for all assets and `fetch()` calls. Absolute paths break behind reverse proxies.
1. Am I using `fastapi_apps` / FastAPI — not `appbuilder_views` / Flask? 2. Are all HTML/JS asset paths and `fetch()` calls relative (no leading slash)? 3. Are all synchronous SDK or SQLAlchemy calls wrapped in `asyncio.to_thread()`? 4. Do the `static/` and `assets/` directories exist before the FastAPI app mounts them? 5. If the endpoint must be private, did I add explicit FastAPI authentication?
---
A single plugin class can register multiple component types at once.
| Component | What it does | Field | |-----------|-------------|-------| | Custom API endpoints | FastAPI app mounted in Airflow process | `fastapi_apps` | | Nav / page link | Embeds a URL as an iframe or links out | `external_views` | | React component | Custom React app embedded in Airflow UI | `react_apps` | | API middleware | Intercepts all Airflow API requests/responses | `fastapi_root_middlewares` | | Jinja macros | Reusable Python functions in DAG templates | `macros` | | Task instance button | Extra link button in task Detail view | `operator_extra_links` / `global_operator_extra_links` | | Custom timetable | Custom scheduling logic | `timetables` | | Event hooks | Listener callbacks for Airflow events | `listeners` |
---
Give each plugin its own subdirectory under `plugins/` — this keeps the Python file, static assets, and templates together and makes multi-plugin projects manageable:
plugins/
my-plugin/
plugin.py # AirflowPlugin subclass — auto-discovered by Airflow
static/
index.html
app.js
assets/
icon.svg`BASE_DIR = Path(__file__).parent` in `plugin.py` resolves to `plugins/my-plugin/` — static and asset paths will be correct relative to that. Create the subdirectory and any static/assets folders before starting Airflow, or `StaticFiles` will raise on import.
from pathlib import Path
from airflow.plugins_manager import AirflowPlugin
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
BASE_DIR = Path(__file__).parent
app = FastAPI(title="My Plugin")
# Both directories must exist before Airflow starts or FastAPI raises on import
app.mount("/static", StaticFiles(directory=BASE_DIR / "static"), name="static")
app.mount("/assets", StaticFiles(directory=BASE_DIR / "assets"), name="assets")
class MyPlugin(AirflowPlugin):
name = "my_plugin"
fastapi_apps = [
{
"app": app,
"url_prefix": "/my-plugin", # plugin available at {AIRFLOW_HOST}/my-plugin/
"name": "My Plugin",
}
]
external_views = [
{
"name": "My Plugin",
"href": "my-plugin/ui", # NO leading slash — breaks on Astro and reverse proxies
"destination": "nav", # see locations table below
"category": "browse", # nav bar category (nav destination only)
"url_route": "my-plugin", # unique route name (required for React apps)
"icon": "/my-plugin/static/icon.svg" # DOES use a leading slash — served by FastAPI
}
]| `destination` | Where it appears | |--------------|-----------------| | `"nav"` | Left navigation bar (also set `category`) | | `"dag"` | Extra tab on every Dag page | | `"dag_run"` | Extra tab on every Dag run page | | `"task"` | Extra tab on every task page | | `"task_instance"` | Extra tab on every task instance page |
Set `"category"` to place the link under a specific nav group: `"browse"`, `"admin"`, or omit for top-level.
`href` can be a relative path to an internal endpoint (`"my-plugin/ui"`) or a full external URL. A plugin with only `external_views` and no `fastapi_apps` is valid — no backend needed for a simple link or tab:
from airf
AI agent tooling for data engineering workflows. Includes an MCP server for Airflow, a CLI tool (af) for interacting with Airflow from your terminal, and skills that extend AI coding agents with specialized capabilities for working with Airflow and data
Airflow adapter pattern for v2/v3 API compatibility. Use when working with adapters, version detection, or adding new API methods that need to work across…
Builds human-in-the-loop (HITL) Airflow workflows - approval gates, form input, and human-driven branching. Use when a DAG needs a human in the loop - an…
Persists task and asset state across retries and DAG runs using Airflow 3.3's AIP-103 key/value stores (`task_state_store`, `asset_state_store`) and the…
Queries, manages, and troubleshoots Apache Airflow using the `af` CLI. Use when working with anything related to Airflow - a DAG, a DAG run, a task log, an…
Queries the data warehouse with SQL and answers business questions about data. Use when answering anything that needs warehouse data - counts, metrics, trends,…
Annotate Airflow tasks with data lineage using inlets and outlets. Use when the user wants to add lineage metadata to tasks, specify input/output datasets, or…