SkillAgentSearch skills...

Fastadmin

FastAdmin is an easy-to-use Admin Dashboard App for FastAPI/Flask/Django inspired by Django Admin.

Install / Use

/learn @vsdudakov/Fastadmin
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Admin Dashboard for FastAPI / Flask / Django

codecov License PyPi Python 3.12 Python 3.13

Demo

FastAdmin demo

<p align="center"> <a href="https://twitter.com/intent/tweet?text=Admin%20Dashboard%20For%20FastAPI&url=https://github.com/vsdudakov/fastadmin&hashtags=FastAPI,AdminDashboard"> <img alt="tweet" src="https://img.shields.io/twitter/url/https/twitter?label=Share%20on%20twitter&style=social" target="_blank" /> </a> </p>

Introduction

<a href='https://github.com/vsdudakov/fastadmin' target='_blank'>FastAdmin</a> is an easy-to-use admin dashboard for FastAPI, Django, and Flask, inspired by Django Admin.

FastAdmin is built with relationships in mind and admiration for Django Admin. Its design focuses on making it as easy as possible to configure your admin dashboard for FastAPI, Django, or Flask.

FastAdmin aims to be minimal, functional, and familiar.

Getting Started

If you have questions beyond this documentation, feel free to <a href='mailto:vsdudakov@gmail.com' target='_blank'>email us</a>.

Installation

Follow the steps below to set up FastAdmin:

Install the package with pip:

On zsh and macOS, use quotes: <code>pip install 'fastadmin[fastapi,django]'</code>


pip install fastadmin[fastapi,django]        # FastAPI with Django ORM
pip install fastadmin[fastapi,tortoise-orm]  # FastAPI with Tortoise ORM
pip install fastadmin[fastapi,pony]          # FastAPI with Pony ORM
pip install fastadmin[fastapi,sqlalchemy]    # FastAPI with SQLAlchemy (includes greenlet)
pip install fastadmin[django]                # Django with Django ORM
pip install fastadmin[django,pony]           # Django with Pony ORM
pip install fastadmin[flask,sqlalchemy]      # Flask with SQLAlchemy (includes greenlet)

Or install with Poetry:


poetry add 'fastadmin[fastapi,django]'
poetry add 'fastadmin[fastapi,tortoise-orm]'
poetry add 'fastadmin[fastapi,pony]'
poetry add 'fastadmin[fastapi,sqlalchemy]'
poetry add 'fastadmin[django]'
poetry add 'fastadmin[django,pony]'
poetry add 'fastadmin[flask,sqlalchemy]'

When using SQLAlchemy, the <code>greenlet</code> package is required (included in the <code>fastadmin[sqlalchemy]</code> extra).

Configure the required settings with environment variables:

You can add these variables to a <code>.env</code> file and load them with python-dotenv. See <a href='https://vsdudakov.github.io/fastadmin#settings'>all settings</a> in the full documentation.


export ADMIN_USER_MODEL=User
export ADMIN_USER_MODEL_USERNAME_FIELD=username
export ADMIN_SECRET_KEY=secret_key

Quick Examples

ORM setup (User, UserAttachment, actions, widgets)

Tortoise ORM

from tortoise import fields
from tortoise.models import Model


class User(Model):
    username = fields.CharField(max_length=255, unique=True)
    hash_password = fields.CharField(max_length=255)
    is_superuser = fields.BooleanField(default=False)
    is_active = fields.BooleanField(default=True)
    avatar_url = fields.TextField(null=True)


class UserAttachment(Model):
    user = fields.ForeignKeyField("models.User", related_name="attachments")
    attachment_url = fields.TextField()
from fastadmin import (
    TortoiseInlineModelAdmin,
    TortoiseModelAdmin,
    WidgetType,
    action,
    register,
    widget_action,
)
from fastadmin.models.schemas import (
    WidgetActionChartProps,
    WidgetActionInputSchema,
    WidgetActionResponseSchema,
    WidgetActionType,
)
from .models import User, UserAttachment


class UserAttachmentInline(TortoiseInlineModelAdmin):
    model = UserAttachment
    formfield_overrides = {
        "attachment_url": (WidgetType.UploadFile, {"required": True}),
    }

    async def upload_file(self, field_name: str, file_name: str, file_content: bytes) -> str:
        # save file to media directory or to s3/filestorage here
        return f"/media/{file_name}"


@register(User)
class UserAdmin(TortoiseModelAdmin):
    list_display = ("id", "username", "is_superuser", "is_active")
    inlines = (UserAttachmentInline,)

    formfield_overrides = {
        "avatar_url": (WidgetType.UploadImage, {"required": False}),
    }

    actions = ("activate", "deactivate")
    widget_actions = ("users_chart", "users_list")

    @action(description="Activate selected users")
    async def activate(self, ids: list[int]) -> None:
        await self.model_cls.filter(id__in=ids).update(is_active=True)

    @action(description="Deactivate selected users")
    async def deactivate(self, ids: list[int]) -> None:
        await self.model_cls.filter(id__in=ids).update(is_active=False)

    async def upload_file(self, field_name: str, file_name: str, file_content: bytes) -> str:
        # handle avatar_url uploads for User (and other file fields if needed)
        return f"/media/{file_name}"

    @widget_action(
        widget_action_type=WidgetActionType.ChartLine,
        widget_action_props=WidgetActionChartProps(x_field="x", y_field="y", series_field="series"),
        tab="Analytics",
        title="Users over time",
    )
    async def users_chart(self, payload: WidgetActionInputSchema) -> WidgetActionResponseSchema:
        return WidgetActionResponseSchema(
            data=[
                {"x": "2026-01-01", "y": 10, "series": "Active"},
                {"x": "2026-01-02", "y": 15, "series": "Active"},
                {"x": "2026-01-01", "y": 3, "series": "Inactive"},
                {"x": "2026-01-02", "y": 5, "series": "Inactive"},
            ]
        )

    @widget_action(
        widget_action_type=WidgetActionType.Action,
        tab="Data",
        title="Users list",
        description="Simple action widget that returns a table of users.",
    )
    async def users_list(self, payload: WidgetActionInputSchema) -> WidgetActionResponseSchema:
        return WidgetActionResponseSchema(
            data=[
                {"id": 1, "username": "alice"},
                {"id": 2, "username": "bob"},
            ]
        )

Django ORM

from django.db import models


class User(models.Model):
    username = models.CharField(max_length=255, unique=True)
    password = models.CharField(max_length=255)
    is_superuser = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    avatar_url = models.ImageField(null=True)


class UserAttachment(models.Model):
    user = models.ForeignKey(User, related_name="attachments", on_delete=models.CASCADE)
    attachment_url = models.FileField()
from fastadmin import (
    DjangoInlineModelAdmin,
    DjangoModelAdmin,
    WidgetType,
    action,
    register,
    widget_action,
)
from fastadmin.models.schemas import (
    WidgetActionArgumentProps,
    WidgetActionInputSchema,
    WidgetActionProps,
    WidgetActionResponseSchema,
    WidgetActionType,
)
from .models import User, UserAttachment


class UserAttachmentInline(DjangoInlineModelAdmin):
    model = UserAttachment
    formfield_overrides = {
        "attachment_url": (WidgetType.UploadFile, {"required": True}),
    }

    def upload_file(self, field_name: str, file_name: str, file_content: bytes) -> str:
        # save file to media directory or to s3/filestorage here
        return f"/media/{file_name}"


@register(User)
class UserAdmin(DjangoModelAdmin):
    list_display = ("id", "username", "is_superuser", "is_active")
    inlines = (UserAttachmentInline,)

    formfield_overrides = {
        "avatar_url": (WidgetType.UploadImage, {"required": False}),
    }

    actions = ("activate", "deactivate")
    widget_actions = ("users_summary", "users_chart")

    @action(description="Activate selected users")
    def activate(self, ids):
        self.model_cls.objects.filter(id__in=ids).update(is_active=True)

    @action(description="Deactivate selected users")
    def deactivate(self, ids):
        self.model_cls.objects.filter(id__in=ids).update(is_active=False)

    def upload_file(self, field_name: str, file_name: str, file_content: bytes) -> str:
        # handle avatar_url uploads for User (and other file fields if needed)
        return f"/media/{file_name}"

    @widget_action(
        widget_action_type=WidgetActionType.Action,
        widget_action_props=WidgetActionProps(
            arguments=[
                WidgetActionArgumentProps(
                    name="only_active",
                    widget_type=WidgetType.Switch,
                    widget_props={"required": False},
                )
            ]
        ),
        tab="Data",
        title="Users summary",
    )
    def users_summary(self, payload: WidgetActionInputSchema) -> WidgetActionResponseSchema:
        qs = self.model_cls.objects.filter(is_active=True) if payload.arguments.get("only_active") else self.model_cls.objects.all()
        return WidgetActionResponseSchema(
            data=[{"id": u.id, "username": u.username} for u in qs[:5]]
        )

    @widget_action(
        widget_action_type=WidgetActionType.ChartLine,
        widget_action_props=WidgetActionChartProps(x_field="label", y_f
View on GitHub
GitHub Stars300
CategoryData
Updated4h ago
Forks35

Languages

Python

Security Score

100/100

Audited on Mar 27, 2026

No findings