Why Flask AppBuilder plugins break in Airflow 3 and how to plan the port
A team with custom Airflow 2 UI plugins has to port them before Airflow 2 reaches end of life on April 22, 2026. The port means replacing each Flask AppBuilder (FAB) view, menu item and blueprint with something Airflow 3 still loads: a FastAPI app, or an external view served through the FAB provider's compatibility layer. Airflow 3 first released on April 22, 2025, and its current patch release is 3.3.2, so the target line is stable and well documented. Airflow 3 no longer uses the Flask webserver for its UI, so plugins that assumed a Flask request context will not load as written.
Start by inventorying every plugin class and the plugin keys it populates. The Airflow 3 upgrade guide prefers three keys: external_views for pages that open from the UI, fastapi_apps for custom API routes, and fastapi_root_middlewares for request handling that must sit at the root of the server. Decide per item whether it is a page, an API or a middleware, then decide whether it can be rewritten as a FastAPI app or must stay on FAB for now. Plugins that depend on Flask-specific objects, sessions or templates are the usual reason to use the compatibility layer. Confirm each choice against the plugin's actual code, not its menu label.
Sequence the work from a supported starting point. The upgrade guide requires Airflow 2.7 or later before upgrading and recommends moving to the latest 2.x first, so a team still on an older 2.x should reach that line, then test plugins there. Keep the plugin inventory, the target keys and the test results in version control so the port can be reviewed and rolled back with the rest of the upgrade. Plan a staging environment that loads every plugin, then check each page, menu entry and endpoint for both loading and authorization behavior before production.
Which plugin and security changes hit Airflow 3 upgrades
This table compares each plugin and security change that Airflow 3 introduces for custom UI code and login configuration, with the impact and the action each one needs. Read each row as a checklist item for your upgrade inventory, and treat any row that matches code you run today as work to scope before the upgrade starts.
| Change | Impact on Airflow 3 | Action needed |
|---|---|---|
| Flask AppBuilder views and menu items in plugins | Airflow 3 requires these to be converted to FastAPI apps or run through the FAB provider's compatibility layer | Inventory every plugin that registers views or menu items, then choose conversion or the compatibility layer for each |
| Blueprints in plugins | Blueprints are part of the Flask AppBuilder surface that must be moved to a supported Airflow 3 plugin key | List all blueprints, map each to a FastAPI app or a compatibility path, and test each page after the change |
| external_views plugin key | Preferred key for registering external UI links and views in Airflow 3 | Move menu-style and external page registrations to this key where the page does not need Flask rendering |
| fastapi_apps plugin key | Preferred key for serving custom FastAPI applications from the Airflow API server | Port custom endpoints to FastAPI apps and confirm they run under the API server, not the retired webserver |
| fastapi_root_middlewares plugin key | Preferred key for root-level middleware on the Airflow 3 API server | Review any middleware that touched Flask requests, port it, and verify request handling and authentication order |
| FAB provider compatibility layer | Lets FAB-based plugin code run in Airflow 3 through the FAB provider package | Install and pin the FAB provider, confirm which plugin components still depend on it, and plan its removal path |
| webserver_config.py security manager import | Imports from airflow.www.security must change to the FAB AirflowSecurityManagerOverride import | Update the import in every webserver_config.py file, then verify OAuth, OIDC or LDAP login in staging |
| Simple auth manager default | Airflow 3 uses the Simple auth manager unless configured otherwise, so FAB-based login behavior does not carry over by default | Confirm the auth_manager setting for each environment and record which login method each one uses |
| FabAuthManager and the FAB provider | Keeping FAB-based authentication requires the FAB provider and FabAuthManager, and only one auth manager can be active at a time | Install the FAB provider, set auth_manager to FabAuthManager where FAB login is kept, and test role mapping |
| Auth route prefix under /auth | OAuth and other callback routes move under /auth, so existing identity provider redirect URLs stop matching | Update callback URLs in the identity provider for each environment before cutover and test sign-in end to end |
Choosing between a FastAPI rewrite and the FAB compatibility layer
A FastAPI rewrite fits plugins that expose their own pages, JSON endpoints or small internal tools that need to stay useful after the upgrade. Airflow 3 serves these as FastAPI apps registered through the fastapi_apps plugin key, so the code runs inside the API server rather than inside a Flask webserver. Developers rewrite routes, templates and request handling in FastAPI, then test each endpoint against the new API server.
- Developer skills: the team needs Python and FastAPI experience, plus enough familiarity with Airflow's authentication model to protect new routes.
- Security review: every new route needs its own authorization check, since the Flask security decorators from Airflow 2 do not carry over.
- Frontend work: Flask templates must be replaced or rebuilt, which is often the largest share of effort.
- What it leaves for later: the plugin gets a clean foundation, but the team owns future FastAPI and Airflow API changes.
Confirm with your developers which routes are still used, and whether each one needs a full UI or only an API.
External views and root middlewares fit plugins that mainly add a link, a menu entry or a small page that opens another internal tool. The external_views key adds navigation to an existing URL, and fastapi_root_middlewares lets you inspect or modify requests across the application. This route asks less of the plugin code, because the page itself can live in a separate service you already operate.
- Less code to port: menu items and links are usually simple declarations, so the change is mostly configuration.
- Separate service: the target page runs elsewhere, which adds a deployment and its own authentication path.
- Middleware risk: root middlewares run for every request, so a faulty one can affect the whole UI and the API. Test them under load and with failed upstream calls.
- What it leaves for later: a page that depends on Airflow session data may still need a rewrite once the external service is retired.
Check whether the linked tool can authenticate users through the same identity provider you use for Airflow.
Keeping Flask AppBuilder views through the FAB provider layer is the option that changes the least code during the upgrade. The FAB provider supplies the compatibility layer that lets Airflow 2 style views keep working in Airflow 3, which can keep a working plugin running while the team plans the rewrite. This path does not remove the need to plan, because the provider adds a dependency that must be installed, versioned and tested alongside Airflow.
- Dependency management: the FAB provider must be pinned to a version that matches your Airflow 3 release, and provider upgrades then follow their own cadence.
- Auth manager coupling: using the FAB auth manager requires the FAB provider and its auth manager setting, so the decision here also affects login.
- Limited future path: the layer is a bridge, so the plugin still needs a planned move to FastAPI.
- What it leaves for later: a documented retirement date for each compatibility-dependent plugin, with a named owner.
Confirm the supported provider version for your target Airflow release in the official upgrade guide before you commit to this route.
Most organizations end up with a mix of the three options, because plugins differ in size, owner and business value. A useful way to decide is to classify each plugin by what it does, who uses it and how much it depends on Flask. A link-only plugin often moves in a day of configuration work, while a plugin with custom forms and role logic usually needs a rewrite. The point of the split is to record the choice and its reason, so later reviewers see why each plugin was kept, moved or rebuilt.
- Inventory: list every plugin, its menu items, blueprints and owner, including plugins installed by teams outside the platform group.
- Usage data: ask which pages are opened in a typical month, since unused views may be removed rather than ported.
- Decision record: note the chosen path, the reason and the review date for each plugin.
- Testing: every path needs a login test, since authentication changes affect all three options.
- Quote scope: ask a partner to price each plugin separately so you can compare the options.
Review the decision record with the security team before the upgrade window is set.
What to inventory and back up before porting plugins
Porting plugins goes wrong most often when the team has not written down what the Airflow 2 environment actually does. The cards below cover the inventory, the security configuration, the database backup, the staging copy and the dependency list that together make the port testable and reversible.
Inventory every plugin component
List each plugin under the plugins folder and record every appBuilderView, appBuilderMenuItem, flask_blueprint and appBuilderSecurityView it registers. Note which menu items appear for which roles and which URLs users or other systems call. Each item becomes a row in the port plan with a target: a FastAPI app, an external view, or the FAB provider compatibility layer. Confirm the list with the people who use the UI, because undocumented pages are common.
Record the security manager configuration
Copy the current webserver_config.py and any custom security manager class, including role definitions, permission overrides and imports from airflow.www.security. Record every OAuth, OIDC or LDAP setting, including client IDs, redirect URIs, group mappings and the login callback path. Airflow 3 moves auth routes under /auth, so the callback URLs registered with your identity provider will need a planned change and a test login.
Take a consistent metadata database backup
Pause DAG scheduling and wait for running tasks to finish before dumping the metadata database, so the backup reflects one point in time. Use your PostgreSQL or MySQL native backup tooling, and store the dump outside the Airflow host. Test a restore into a scratch database and confirm that the plugin-related tables and connections are present. Ask your DBA who owns restore authority and how long a restore takes in your environment.
Build a staging environment that matches production
Stand up a staging Airflow with the same executor, database engine, identity provider and plugin folder layout as production, using a restored copy of the metadata database. Include the same network paths to identity providers and internal UIs that plugins link to. Staging that differs in auth or executor settings will hide the failures you need to find, such as broken login redirects or menu entries that fail to render.
List Python dependencies per plugin
For each plugin, list every import, including Flask extensions, HTTP clients, database drivers and internal libraries, and pin the versions present in production. Flag any import of Flask-specific objects or Airflow modules that move or disappear in Airflow 3. Keep this list next to the Ruff AIR findings for the same plugin so that code and runtime risks are reviewed together. Ask whether any plugin depends on a package that has no Airflow 3 compatible release.
How to sequence the plugin port and the Airflow 3 upgrade
The sequence moves the deployment from a working Airflow 2 setup to Airflow 3 with each FAB plugin, the security manager config and login flows verified in staging before production changes.
Move to the latest Airflow 2.x first
Upgrade the staging copy of your Airflow 2 deployment to the latest 2.x patch line before touching plugins or DAG code. The upgrade guide asks for Airflow 2.7 or later as the starting point and recommends the latest 2.x first, so confirm your current version against that floor. Run your existing DAG test suite and a manual login on the 2.x build. Done means staging runs on the latest 2.x with all DAGs parsing and the current UI working. Rollback point: production stays on its current release until this stage passes.
Inventory every plugin and custom view
List each plugin in your plugins folder with its menu items, Flask AppBuilder views, blueprints, and any custom templates or static assets. Record which ones are actively used by operators and which can be retired. Note any plugin that depends on the Airflow metadata database directly, since that is a separate Airflow 3 change. Your team or DeployOpen can produce this inventory as a written document. Done means every plugin has an owner, a usage note, and a planned path: convert to a FastAPI app, move to external_views, or run through the FAB provider compatibility layer.
Run Ruff AIR checks on DAGs and plugins
Run Ruff with the AIR301 and AIR302 rules across the DAG repository and the plugins directory on the 2.x staging build. These rules flag Airflow 3 breaking changes that are visible in code, so the output becomes your work list for DAG-side fixes. Review safe and unsafe fixes before applying them, and keep the results in version control. Done means the check runs in CI with a recorded baseline of findings, and each finding is either fixed or assigned. Rollback point: the check changes only code in a branch, so reverting the branch restores the prior state.
Convert or wrap each plugin in staging
For each plugin in the inventory, choose the Airflow 3 target. Convert views and menu items to FastAPI apps registered under fastapi_apps, use external_views for links and embedded pages, and add fastapi_root_middlewares where a root middleware was needed. Where a plugin relies on Flask AppBuilder features that cannot move yet, keep it running through the FAB provider compatibility layer and document why. Build and load the plugins in an Airflow 3 staging environment with the FAB provider installed only if needed. Done means every plugin loads without import errors, each menu item resolves to a working page, and the scheduler and API server logs show no plugin load failures.
Update webserver_config.py for the FAB security manager
Change custom webserver_config.py files to import AirflowSecurityManagerOverride from the FAB provider instead of airflow.www.security. Confirm that the FAB auth manager is configured explicitly, because Airflow 3 defaults to the Simple auth manager and allows only one auth manager at a time, set by the auth_manager option. Review any custom security manager methods against the FAB provider's version. Done means the staging API server starts with the intended auth manager, the config imports resolve, and no deprecated security imports remain in the repository.
Verify OAuth, OIDC or LDAP login and callback URLs
Test each login path your organization uses on the Airflow 3 staging environment, including role mapping for OAuth, OIDC, or LDAP users. Auth routes move under /auth in Airflow 3, so update the redirect or callback URLs registered with your identity provider and confirm each one matches exactly. Test a user with each role, a logout, and a session expiry. Done means each login method completes end to end, every role sees the expected menus and plugin pages, and the identity provider lists the updated callback URLs. Rollback point: keep the old callback URLs registered until production cutover, so existing sessions can still complete.
Promote the port to production with a rollback path
Schedule the production change after staging passes every prior check, and take a consistent backup of the metadata database before the upgrade starts. Deploy the Airflow 3 components, including the separate DAG processor and API server, along with the converted plugins and updated webserver_config.py. Confirm plugin pages, login, and DAG runs after cutover. Keep the Airflow 2 image and database snapshot available until the team signs off. Done means production runs Airflow 3 with all required plugins loading, login verified by real users, and a documented rollback procedure that the on-call team has reviewed.
How webserver_config.py and OAuth callbacks change after the upgrade
Custom webserver_config.py files must change their security manager import when you move to Airflow 3. The upgrade guide says they should switch from the old airflow.www.security import to the FAB provider's AirflowSecurityManagerOverride. If your plugin set or config sets a custom security manager, that import is the first edit to make. Check every environment that loads the file, including staging and any image build that copies it in. Then verify OAuth, OIDC or LDAP login end to end, because a file that imports cleanly can still fail at sign-in.
The reason the import matters is that Airflow 3 no longer assumes Flask-AppBuilder authentication. The Simple auth manager is the default, and only one auth manager can be active at a time, selected by the auth_manager option. Keeping the FAB-based login means installing the FAB provider and setting auth_manager to the FAB auth manager. Decide this before you edit the security manager, since a FAB-style webserver_config.py does nothing useful under the Simple auth manager. Confirm the exact provider version and auth manager path against the Airflow upgrade guide and the Airflow auth manager documentation for your target release.
Auth routes also move under /auth, so every OAuth callback URL registered with your identity provider must be updated to the new path before users can sign in. Keycloak, authentik, Entra ID, Okta and other providers each have their own place to set allowed redirect URIs, and a mismatch usually appears as a failed login rather than an obvious error. Make the change in the identity provider in the same window as the Airflow cutover, and keep the old URI registered until you have confirmed the new one works. Confirm the exact URI format, client settings and role or group mapping with your identity provider's documentation and your own administrators, because those settings are specific to each provider and each tenant.
What makes plugin conversions take longer
How does the number of plugins affect conversion effort?
Effort grows with the count of plugins, and each plugin adds its own discovery, port, test and sign-off work. A plugin that only registers a single menu item is a small change, while a plugin that adds several views, blueprints and background hooks needs separate analysis for each piece. Ask the partner to list every plugin, the plugin keys it uses and the Airflow 2 features it depends on before agreeing a fixed scope. Plugins that share helper modules should be grouped, because one shared change can affect several of them at once.
Why do Flask AppBuilder views and Jinja templates slow down a port?
Views built on Flask AppBuilder and templates written in Jinja must be rebuilt as FastAPI apps, moved to external views, or kept running through the FAB provider's compatibility layer, and each choice needs a different amount of rework. Templates that extend Airflow's own base pages or call internal helpers often break in ways that only show up in the browser, so they take manual review. Teams should inventory every template and decide per view whether it is rewritten, moved or retired before any code changes start.
How do custom security managers change the conversion timeline?
Custom security managers add work because a custom webserver_config.py must move from the airflow.www.security import to the FAB AirflowSecurityManagerOverride import, and any overridden methods must still match what Airflow 3 expects. Each override needs to be read against the FAB provider code in use, not the code it was copied from. Teams with heavily overridden role logic should budget time for reviewing permissions, since a mistake here can lock users out or grant too much access.
Does OAuth or LDAP login make a plugin upgrade harder?
Yes, because login flows are the part of the system that most often stops working silently after an upgrade. In Airflow 3, auth routes move under /auth, so OAuth callback URLs registered with the identity provider must be updated, and LDAP or OIDC settings should be retested end to end. Each provider adds a separate test path with real accounts, group mappings and role checks. Ask for the identity provider configuration to be included in the scope, along with the people who can change the provider's callback settings.
Why does sharing code between DAGs and plugins add risk?
Shared code adds risk because Airflow 3 no longer lets task code use database sessions or models directly, so any helper that reaches into the metadata database must be rewritten to use the REST API or Task SDK. A helper used by both a DAG and a plugin must be changed in a way that works for both, which means more design work and more regression testing. Map every shared module during discovery, since a change that looks local can break scheduled runs that the UI tests never touch.
How does thin test coverage change the work involved?
Thin test coverage increases the time needed because every converted plugin must be checked by hand, and regressions in menus, permissions and login are easy to miss. Teams with automated UI or API tests can compare behavior before and after the change, while teams without them need written test scripts, a staging environment with realistic roles and a sign-off step. Ask the partner what testing is included, which tests will be written during the engagement and what the client must provide, such as test accounts and a staging copy of the Airflow environment.
Can our own team convert the plugins without a partner?
A team with strong Python and Airflow experience can convert simple plugins in-house, especially if it already runs Airflow 3 in staging. The work becomes harder when plugins depend on Flask AppBuilder behavior, custom security managers or external login, because those areas need knowledge of both the FAB provider and the identity setup. Compare the internal effort with the written scope from a partner, including testing, rollback planning and the time needed to keep the upgrade running alongside normal delivery work.
How DeployOpen scopes and delivers Airflow plugin conversions
DeployOpen approaches Converting Flask AppBuilder plugins for Airflow 3 as a written, fixed-scope piece of work. We inventory each plugin, decide the target for every view, menu item and blueprint, then build, test and document the converted code inside your own infrastructure. Ongoing support is available as a retainer after handover.
How we scope
- We inventory every plugin, menu item, blueprint and webserver_config.py file in your Airflow 2 environment.
- The written scope states which plugins become FastAPI apps, external views or root middlewares, and which stay on the FAB provider layer.
- Scope also covers OAuth, OIDC or LDAP login checks and the OAuth callback URL changes under the /auth route.
How we build
- We port each plugin on a branch, keeping the Airflow 3 target and the FAB provider dependency explicit in your code.
- We update the security manager import in webserver_config.py to the FAB AirflowSecurityManagerOverride path.
- We test each converted view against a staging Airflow 3 deployment before any change reaches production.
How we hand over
- You receive the converted plugin code, configuration changes and a record of each view's migration decision.
- Documentation covers the login verification steps and the callback URLs your identity provider must accept.
- We walk your platform and development teams through the handover so they can maintain the plugins afterward.
How we support
- Ongoing support, upgrades and continuous testing are available as a retainer after the fixed-scope work ends.
- Retainer support can cover later Airflow 3 patch releases and changes to plugin code or the FAB provider.
- Ask your DeployOpen contact to confirm retainer terms in writing alongside the original scope.

