Coming from Flask-Admin
Because starlette-admin began as a port of Flask-Admin's concepts to the ASGI ecosystem, the migration process is highly direct. You still subclass a ModelView, configure it using class attributes, and register it on an Admin instance. Most of the transition involves renaming attributes and shifting from Flask's implicit request context to Starlette's explicit request object.
This guide maps the Flask-Admin API, attribute by attribute, to its starlette-admin equivalent.
Mental Model
| Flask-Admin Concept | starlette-admin Equivalent |
|---|---|
Admin(app, name="...") |
Admin(engine, title="...") then admin.mount_to(app) |
ModelView(Model, db.session) |
ModelView(Model); the Admin instance owns the engine and database sessions |
flask_admin.contrib.sqla |
starlette_admin.contrib.sqla |
flask_admin.contrib.mongoengine |
starlette_admin.contrib.mongoengine |
| peewee / pymongo backends | Beanie, Tortoise ORM, SQLModel, or a custom backend |
BaseView + @expose |
CustomView |
AdminIndexView |
Admin(index_view=...), DefaultIndexView |
Flask request context (flask.request) |
Explicit request: Request parameter on every hook |
| Sync methods | async methods (sync still works where callables are accepted) |
Setup
There is no template_mode switch. The UI uses Tabler (Bootstrap 5) with built-in dark mode support. You customize the appearance using a custom BaseTheme and template overrides.
List Page Attributes
| Flask-Admin | starlette-admin | Notes |
|---|---|---|
column_list |
fields |
Also drives detail and form pages. Use the exclude_fields_from_* attributes to define per-page variations. |
column_exclude_list |
exclude_fields_from_list |
|
column_labels |
label= |
Example: StringField("title", label="Headline") |
column_descriptions |
help_text= |
Applies to the specific field definition. |
column_formatters |
formatter= on the field |
Example: StringField("title", formatter={RequestAction.LIST: lambda request, value: value[:40]}). |
column_formatters_detail / export formatters |
The same formatter= dict, keyed by RequestAction |
One mapping covers list, detail, and export formatting; actions without an entry keep the raw value. |
column_type_formatters |
Per-field formatter=, or a custom field subclass |
There is no per-type registry; attach the formatter to each field, or subclass the field and reuse it. |
Model properties or callables in column_list |
ComputedField or getter= on any field |
Adds virtual columns, or redirects an existing field's value lookup, without a subclass. |
| Custom WTForms fields (value coercion) | parser= on the field |
Replaces the field's default form or import parsing per RequestAction. |
column_searchable_list |
searchable_fields |
|
column_filters |
searchable_fields combined with per-field filters= |
Replaces the flat filter list with a visual builder supporting nested AND/OR groups. |
column_sortable_list |
sortable_fields |
|
column_default_sort |
fields_default_sort |
Example: [("created_at", True)] for descending order. |
column_editable_list |
inline_editable_fields |
Allows clicking a cell to edit in place. |
page_size |
page_size |
|
can_set_page_size |
page_size_options |
Defaults to [10, 25, 50, 100]. Users select from these options. |
column_display_pk |
Include the primary key in fields |
|
column_details_list |
fields minus exclude_fields_from_detail |
The detail page is built in. No can_view_details opt-in is required. |
Form Attributes
| Flask-Admin | starlette-admin | Notes |
|---|---|---|
form_columns |
fields minus exclude_fields_from_create and exclude_fields_from_edit |
|
form_excluded_columns |
exclude_fields_from_create, exclude_fields_from_edit |
Provides separate visibility controls per form. |
form_overrides |
Explicit field instances in fields |
Example: fields = ["id", TextAreaField("bio")] |
form_args |
Constructor arguments on the field | Example: StringField("title", required=True, help_text="...") |
form_choices |
EnumField |
Example: EnumField("status", choices=[("draft", "Draft"), ("live", "Live")]) |
form_extra_fields |
Extra entries in fields |
Supports any field not backed by a database column, such as a ComputedField. |
form_widget_args |
Field attributes | Use attributes like read_only, disabled, or placeholder directly on the field. |
form_rules |
form_layout |
Replaces flat rules with robust fieldsets, tabs, and responsive grids. |
create_modal / edit_modal |
Not available | Create and edit views render as full pages. |
on_form_prefill |
before_edit hook |
Export and Import
Exporting to CSV and JSON is enabled by default. Row caps apply automatically, and spreadsheet formula escaping is available as an opt-in exporter setting. Importing, a feature Flask-Admin lacks entirely, includes a preview step with per-row validation and optional updates of existing records by primary key. See Export and Import.
Actions
from starlette_admin import ActionSelection, action, flash
class PostView(ModelView):
actions = ["publish", "delete"]
@action(
name="publish",
text="Publish",
confirmation="Publish selected posts?",
)
async def publish(self, request: Request, selection: ActionSelection) -> None:
for post in await selection.rows():
post.published = True
flash(request, "Posts published")
The handler receives an ActionSelection object instead of raw IDs. This object resolves rows lazily, exposes active filters, and works transparently with "select all matching" across multiple pages. Actions can also render a custom HTML form within the confirmation dialog. Additionally, @row_action and @link_row_action provide per-row operations without requiring custom column formatters.
Permissions and Access Control
Flask-Admin's can_* class flags translate to per-request methods in starlette-admin, allowing authorization decisions to depend dynamically on the logged-in user.
| Flask-Admin | starlette-admin | Notes |
|---|---|---|
is_accessible() |
is_accessible(request) |
Hides the view from the menu and blocks direct access. |
inaccessible_callback() |
Handled by the authentication flow | Unauthenticated requests redirect to the login page automatically. |
can_create = False |
def can_create(self, request): return False |
Follows the same pattern for can_edit and can_delete. |
can_view_details |
can_view_detail(request) |
The detail page exists by default. |
can_export |
can_export(request), plus can_import(request) |
|
| N/A | can_access_field(request, field) |
Controls field-level visibility per user. |
| N/A | is_action_allowed(request, name) |
Provides per-action authorization. |
Flask-Admin requires you to integrate Flask-Login manually. In contrast, starlette-admin provides an AuthProvider with a ready-made login page, requiring you only to implement the login, logout, and authenticate methods against your user datastore. It also offers an OAuthProvider for OIDC redirect flows. The authenticated user is accessible globally via request.state.admin_user.
Model Lifecycle Hooks
| Flask-Admin | starlette-admin |
|---|---|
on_model_change(form, model, is_created) |
before_create(request, data, obj) / before_edit(request, data, obj) |
after_model_change |
after_create / after_edit |
on_model_delete |
before_delete |
after_model_delete |
after_delete |
get_query / get_count_query |
get_list_query / get_count_query (Specific to the SQLAlchemy backend) |
handle_view_exception |
Raise FormValidationError or ActionFailed |
Beyond per-view hooks, the event system allows a single handler to observe every view. Flask-Admin has no equivalent feature.
from starlette_admin.events import AdminEvent, AfterCreateContext
async def audit(ctx: AfterCreateContext) -> None: ...
admin.events.on(AdminEvent.AFTER_CREATE, audit)
Custom Views and the Index Page
| Flask-Admin | starlette-admin | Notes |
|---|---|---|
BaseView + @expose("/") |
CustomView(menu_label=..., path=..., widget=...) |
Compose pages from widgets without writing raw templates. |
| Custom template rendering | CustomView subclass |
Offers full control over routes and responses when needed. |
AdminIndexView |
Admin(index_view=...) |
Build dashboards using StatWidget, ChartWidget, TableWidget, and layout widgets. |
MenuLink |
Link view |
Example: admin.add_link(Link(menu_label="Docs", url="https://...")) |
| Categories in the menu | DropDown view |
Groups views together in the sidebar. |
FileAdmin |
Not available | Replaced by file and image fields with local or S3 storage for attachments. There is no dedicated server file browser. |
Inline Models
Using an explicit class gives you the full ModelView configuration surface for each inline model. This includes field selection, validation, and composite foreign key support. See Inline Forms.
Internationalization
Flask-Admin depends on Flask-Babel and the surrounding Flask environment. starlette-admin uses a dedicated configuration object:
from starlette_admin import I18nConfig
admin = Admin(engine, i18n_config=I18nConfig(default_locale="fr"))
You configure timezone-aware datetime rendering similarly using TimezoneConfig. See Internationalization and Timezones.
What You Gain by Switching
- An async stack. Runs natively on FastAPI and Starlette, bringing full support for async SQLAlchemy, Beanie, and Tortoise ORM. Flask-Admin remains strictly synchronous.
- Security features built in. CSRF protection, upload filename sanitization, image content verification, and export row limits are enabled automatically when you instantiate the
Adminclass, and spreadsheet formula escaping can be enabled on the exporters. See Security. - Robust data import. Includes a built-in preview step that validates every row before writing, a feature Flask-Admin lacks entirely.
- A dashboard widget system. Construct index pages and custom views programmatically instead of relying on hand-written templates.
- Modern design. An actively maintained codebase utilizing a polished UI, built-in dark mode, and first-class type hinting.
What You Must Adapt To
- Explicit request objects. There is no ambient request context. Every hook and permission method receives the
requestobject explicitly as a parameter. - Async handlers. Hooks and actions are coroutines. You must avoid blocking calls inside them or offload them to separate threads.
- No
FileAdmin. If your workflow relies heavily on browsing the server file system directly, be aware that this specific feature does not exist in starlette-admin. - No create/edit modals. Forms are built as complete, standalone pages rather than popup modals.