Skip to content

API Reference

This comprehensive API reference covers all classes, methods, and functions available in Flet-Easy. Use this as your complete guide for building applications with Flet-Easy.

Core Classes

FletEasy

flet_easy.FletEasy

Main application class for Flet-Easy.

Configure the app with:

  • route_prefix : Route prefix different from /.
  • route_init : Initial route, default is /.
  • route_login : Route for redirect when using protected routes.
  • on_keyboard : Enable on_keyboard event (default: False).
  • on_resize : Enable on_resize event (default: False).
  • secret_key : Configure JWT or client storage with SecretKey.
  • auto_logout : Auto-logout with JWT expiry (default: False).
  • path_views : Folder path for auto-discovered page files.
  • logger : Enable logger (default: False).

Example:

import flet as ft
import flet_easy as fs

app = fs.FletEasy(
    route_prefix="/FletEasy",
    route_init="/FletEasy/home",
)


@app.page("/home", title="Home", page_clear=True)
async def index_page(data: fs.Datasy):
    return ft.View(
        data.route_init,
        controls=[ft.Text("Home", size=40)],
    )


app.run()

Source code in src/flet_easy/core/app.py
class FletEasy:
    """Main application class for Flet-Easy.

    Configure the app with:

    * `route_prefix` : Route prefix different from `/`.
    * `route_init` : Initial route, default is `/`.
    * `route_login` : Route for redirect when using protected routes.
    * `on_keyboard` : Enable on_keyboard event (default: False).
    * `on_resize` : Enable on_resize event (default: False).
    * `secret_key` : Configure JWT or client storage with `SecretKey`.
    * `auto_logout` : Auto-logout with JWT expiry (default: False).
    * `path_views` : Folder path for auto-discovered page files.
    * `logger` : Enable logger (default: False).

    Example:
    ```python
    import flet as ft
    import flet_easy as fs

    app = fs.FletEasy(
        route_prefix="/FletEasy",
        route_init="/FletEasy/home",
    )


    @app.page("/home", title="Home", page_clear=True)
    async def index_page(data: fs.Datasy):
        return ft.View(
            data.route_init,
            controls=[ft.Text("Home", size=40)],
        )


    app.run()
    ```
    """

    __self: Optional["FletEasy"] = None

    # ─── Initialization ───────────────────────────────────────────────

    def __init__(
        self,
        route_prefix: Optional[str] = None,
        route_init: str = "/",
        route_login: Optional[str] = None,
        on_resize: bool = False,
        on_keyboard: bool = False,
        secret_key: Optional[SecretKey] = None,
        auto_logout: bool = False,
        path_views: Optional[Path] = None,
        logger: bool = False,
        use_error_boundary: bool = True,
    ) -> None:
        if logger:
            import logging

            LoggingFletEasy.enable(logging.DEBUG)
        self._logger = get_logger("FletEasy")

        self.__route_prefix: str = route_prefix or ""
        self.__route_init: str = route_init
        self.__route_login: Optional[str] = route_login
        self.__path_views: Optional[Union[str, Path]] = path_views
        self.__on_resize: bool = on_resize
        self.__on_keyboard: bool = on_keyboard
        self.__secret_key: Optional[SecretKey] = secret_key
        self.__auto_logout: bool = auto_logout
        self.__use_error_boundary: bool = use_error_boundary

        self.__page_404: Optional[Pagesy] = None
        self.__middlewares: Optional[deque[MiddlewareItem]] = None
        self.__middlewares_after: Optional[deque[MiddlewareItem]] = None

        self.__pages: deque[Pagesy] = deque()
        self.__view_data: Optional[Callable[[Datasy], Viewsy]] = None
        self.__config_login: Optional[Callable[[Datasy], bool]] = None
        self.__view_config: Optional[Callable[[Page], None]] = None
        self.__config_event: Optional[Callable[[Datasy], None]] = None

        FletEasy.__self = self
        self.__fsx: Optional[FletEasyX] = None
        self.__pagesys = automatic_routing(str(self.__path_views)) if self.__path_views else None

    # ─── App Lifecycle ────────────────────────────────────────────────

    def __pre_config(self, page: Page) -> None:
        """Configure and start the routing engine."""

        # Add automatic routing pages before initializing FletEasyX
        # so they are included in the exact_routes_map
        if self.__pagesys:
            self.add_pages(self.__pagesys)
            self.__pagesys = None

        fsx = FletEasyX(
            page=page,
            route_prefix=self.__route_prefix,
            route_init=self.__route_init,
            route_login=self.__route_login,
            page_404=self.__page_404,
            pages=self.__pages,
            view_data=self.__view_data,
            config_login=self.__config_login,
            view_config=self.__view_config,
            config_event=self.__config_event,
            middlewares=self.__middlewares,
            middlewares_after=self.__middlewares_after,
            on_resize=self.__on_resize,
            on_keyboard=self.__on_keyboard,
            secret_key=self.__secret_key,
            auto_logout=self.__auto_logout,
            use_error_boundary=self.__use_error_boundary,
        )

        self.__fsx = fsx

        fsx.run()

    def start(self, page: Page) -> None:
        """Start the app in the main function."""
        self._logger.debug("enabling connection with flet app")
        self.__pre_config(page)

    def get_app(self) -> Callable[[Page], None]:
        """Return the app function main."""
        self._logger.debug("getting app from fletEasy to export")

        def main(page: Page) -> None:
            self.__pre_config(page)

        return main

    def run(
        self,
        name: str = "",
        host: Optional[str] = None,
        port: int = 0,
        view: Optional[AppView] = AppView.FLET_APP,
        assets_dir: str = "assets",
        upload_dir: Optional[str] = None,
        web_renderer: WebRenderer = WebRenderer.CANVAS_KIT,
        route_url_strategy: Union[RouteUrlStrategy, str] = RouteUrlStrategy.PATH,
        export_asgi_app: bool = False,
        fastapi: bool = False,
        **kwargs: Any,
    ) -> Any:
        """Execute the app. Supports async, fastapi, and export_asgi_app."""
        main = self.get_app()

        if fastapi:
            warn(
                "Avoid using the 'fastapi' parameter in the 'run()' method, "
                "instead use the 'get_app()' method.",
                category=DeprecationWarning,
                stacklevel=2,
            )
            return main

        try:
            self._logger.debug("running app from fletEasy with flet")
            app_kwargs: dict[str, Any] = {
                "name": name,
                "host": host,
                "port": port,
                "view": view,
                "assets_dir": assets_dir,
                "upload_dir": upload_dir,
                "web_renderer": web_renderer,
                "export_asgi_app": export_asgi_app,
                **kwargs,
            }
            if route_url_strategy is not None:
                app_kwargs["route_url_strategy"] = route_url_strategy

            return app(
                main,
                **app_kwargs,
            )
        except RuntimeError:
            raise FletEasyError(
                "If you are using fastapi from flet, set the 'fastapi = True' "
                "parameter of the run() method."
            )

    # ─── Route Registration ───────────────────────────────────────────

    @classmethod
    def page(
        cls,
        route: str,
        title: Optional[str] = None,
        index: Optional[int] = None,
        page_clear: bool = False,
        share_data: bool = False,
        protected_route: bool = False,
        custom_params: Optional[dict[str, Any]] = None,
        middleware: Middleware = None,
        cache: bool = False,
    ) -> Callable:
        """Decorator to add a new page to the app.

        Args:
            route: URL path, e.g. `'/home'`.
            title: Page title (optional).
            index: Page index for NavigationBar (optional).
            page_clear: Remove previous views (optional).
            share_data: Share data between pages (optional).
            protected_route: Protect with login (optional).
            custom_params: Custom URL parameter validators (optional).
            middleware: Page-level middleware (optional).
            cache: Preserve page state when navigating (optional).
        """
        self = cls.__self
        if self is None:
            raise ConfigurationError("FletEasy instance not initialized.")

        def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
            pagesy = Pagesy(
                route=normalize_route(self.__route_prefix, route),
                view=func,
                title=title,
                index=index,
                clear=page_clear,
                share_data=share_data,
                protected_route=protected_route,
                custom_params=custom_params,
                middleware=middleware,
                cache=cache,
            )
            self.__pages.append(pagesy)

            # Back-reference for reverse decorator order (@ft.component on top)
            setattr(func, "__flet_easy_pagesy__", pagesy)  # noqa: B010

            self._logger.debug("Adding page: %s", self.__pages[-1])
            return func

        return decorator

    def page_404(
        self,
        route: Optional[str] = None,
        title: Optional[str] = None,
        page_clear: bool = False,
    ) -> Callable:
        """Decorator to add a custom 404 page.

        Args:
            route: URL path for the 404 page (optional).
            title: Page title (default: 'Flet-Easy 404').
            page_clear: Remove previous views (optional).
        """

        def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
            self.__page_404 = Pagesy(
                normalize_route(self.__route_prefix, route or ""),
                func,
                title or "Flet-Easy 404",
                clear=page_clear,
            )
            self._logger.debug("Adding page 404: %s", self.__page_404)
            return func

        return decorator

    def add_pages(self, group_pages: Union[list[AddPagesy], AddPagesy]) -> None:
        """Add pages from other files.

        Example:
        ```python
        app.add_pages([index, test, contador, login, task])
        ```
        """
        group_pages = group_pages if isinstance(group_pages, list) else [group_pages]

        try:
            for page in group_pages:
                if self.__route_prefix:
                    self.__pages.extend(page._add_pages(self.__route_prefix))
                else:
                    self.__pages.extend(page._add_pages())

                self._logger.debug("Adding group of pages: %d: %s", len(group_pages), page)
        except Exception as e:
            raise AddPagesError("Add pages error in route: ", e)

    def add_routes(self, add_views: list[Pagesy]) -> None:
        """Add routes without the use of decorators.

        Example:
        ```python
        app.add_routes(
            add_views=[
                fs.Pagesy("/hi", index_page, True),
                fs.Pagesy("/counter", counter_page),
            ]
        )
        ```
        """
        if not add_views:
            raise ConfigurationError("add view (add_view) in 'add_routes'.")

        for page in add_views:
            if self.__route_prefix:
                page.route = normalize_route(self.__route_prefix, page.route)

            self.__pages.append(page)
            self._logger.debug("Add routes: %s", page)

    # ─── Configuration Decorators ─────────────────────────────────────

    def view(self, func: Callable[[Datasy], Viewsy]) -> Callable[[Datasy], Viewsy]:
        """Decorator to add custom view controls (appbar, navigation, etc).

        The decorated function receives `data:fs.Datasy` and returns `fs.Viewsy`.
        """
        self.__view_data = func
        self._logger.debug("Adding view: %s", self.__view_data)
        return func

    def config(self, func: Callable[[Page], None]) -> Callable[[Page], None]:
        """Decorator to add custom page configuration (theme, etc).

        The decorated function receives `page:ft.Page` and returns nothing.
        """
        self.__view_config = func
        self._logger.debug("Adding config: %s", self.__view_config)
        return func

    def login(self, func: Callable[[Datasy], bool]) -> Callable[[Datasy], bool]:
        """Decorator to add login configuration for protected routes.

        The decorated function receives `data:fs.Datasy` and must return a boolean.
        """
        self.__config_login = func
        self._logger.debug("Adding login: %s", self.__config_login)
        return func

    def config_event_handler(self, func: Callable[[Datasy], None]) -> Callable[[Datasy], None]:
        """Decorator to add page event handler settings."""
        self.__config_event = func
        self._logger.debug("Adding config event: %s", self.__config_event)
        return func

    # ─── Middleware ────────────────────────────────────────────────────

    def add_middleware(
        self,
        *middleware: MiddlewareItem,
    ) -> None:
        """Add global middleware.

        Accepts classes inheriting from `fs.MiddlewareRequest` or functions
        that receive `data:fs.Datasy`. Can be added individually or as a list.

        **More info:** https://daxexs.github.io/flet-easy/latest/middleware/#general-application
        """
        if not middleware:
            raise MiddlewareError(
                "No middleware provided to 'add_middleware'. "
                "Pass at least one middleware function or MiddlewareRequest class."
            )

        items = middleware[0] if isinstance(middleware[0], list) else list(middleware)

        self.__middlewares = deque()
        self.__middlewares_after = deque()

        for m in items:
            if isinstance(m, type) and issubclass(m, MiddlewareRequest):
                self.__middlewares.append(m)
                self.__middlewares_after.append(m)
            elif callable(m):
                self.__middlewares.append(cast(MiddlewareItem, m))
            else:
                raise MiddlewareError(
                    f"Middleware '{m}' must be a class inheriting from "
                    f"MiddlewareRequest or a callable function."
                )

        self._logger.debug("Add middlewares in method 'add_middleware': %s", items)

__init__(route_prefix=None, route_init='/', route_login=None, on_resize=False, on_keyboard=False, secret_key=None, auto_logout=False, path_views=None, logger=False, use_error_boundary=True)

Source code in src/flet_easy/core/app.py
def __init__(
    self,
    route_prefix: Optional[str] = None,
    route_init: str = "/",
    route_login: Optional[str] = None,
    on_resize: bool = False,
    on_keyboard: bool = False,
    secret_key: Optional[SecretKey] = None,
    auto_logout: bool = False,
    path_views: Optional[Path] = None,
    logger: bool = False,
    use_error_boundary: bool = True,
) -> None:
    if logger:
        import logging

        LoggingFletEasy.enable(logging.DEBUG)
    self._logger = get_logger("FletEasy")

    self.__route_prefix: str = route_prefix or ""
    self.__route_init: str = route_init
    self.__route_login: Optional[str] = route_login
    self.__path_views: Optional[Union[str, Path]] = path_views
    self.__on_resize: bool = on_resize
    self.__on_keyboard: bool = on_keyboard
    self.__secret_key: Optional[SecretKey] = secret_key
    self.__auto_logout: bool = auto_logout
    self.__use_error_boundary: bool = use_error_boundary

    self.__page_404: Optional[Pagesy] = None
    self.__middlewares: Optional[deque[MiddlewareItem]] = None
    self.__middlewares_after: Optional[deque[MiddlewareItem]] = None

    self.__pages: deque[Pagesy] = deque()
    self.__view_data: Optional[Callable[[Datasy], Viewsy]] = None
    self.__config_login: Optional[Callable[[Datasy], bool]] = None
    self.__view_config: Optional[Callable[[Page], None]] = None
    self.__config_event: Optional[Callable[[Datasy], None]] = None

    FletEasy.__self = self
    self.__fsx: Optional[FletEasyX] = None
    self.__pagesys = automatic_routing(str(self.__path_views)) if self.__path_views else None

page(route, title=None, index=None, page_clear=False, share_data=False, protected_route=False, custom_params=None, middleware=None, cache=False)classmethod

Decorator to add a new page to the app.

Parameters:

NameTypeDescriptionDefault
routestr

URL path, e.g. '/home'.

required
titleOptional[str]

Page title (optional).

None
indexOptional[int]

Page index for NavigationBar (optional).

None
page_clearbool

Remove previous views (optional).

False
share_databool

Share data between pages (optional).

False
protected_routebool

Protect with login (optional).

False
custom_paramsOptional[dict[str, Any]]

Custom URL parameter validators (optional).

None
middlewareMiddleware

Page-level middleware (optional).

None
cachebool

Preserve page state when navigating (optional).

False
Source code in src/flet_easy/core/app.py
@classmethod
def page(
    cls,
    route: str,
    title: Optional[str] = None,
    index: Optional[int] = None,
    page_clear: bool = False,
    share_data: bool = False,
    protected_route: bool = False,
    custom_params: Optional[dict[str, Any]] = None,
    middleware: Middleware = None,
    cache: bool = False,
) -> Callable:
    """Decorator to add a new page to the app.

    Args:
        route: URL path, e.g. `'/home'`.
        title: Page title (optional).
        index: Page index for NavigationBar (optional).
        page_clear: Remove previous views (optional).
        share_data: Share data between pages (optional).
        protected_route: Protect with login (optional).
        custom_params: Custom URL parameter validators (optional).
        middleware: Page-level middleware (optional).
        cache: Preserve page state when navigating (optional).
    """
    self = cls.__self
    if self is None:
        raise ConfigurationError("FletEasy instance not initialized.")

    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
        pagesy = Pagesy(
            route=normalize_route(self.__route_prefix, route),
            view=func,
            title=title,
            index=index,
            clear=page_clear,
            share_data=share_data,
            protected_route=protected_route,
            custom_params=custom_params,
            middleware=middleware,
            cache=cache,
        )
        self.__pages.append(pagesy)

        # Back-reference for reverse decorator order (@ft.component on top)
        setattr(func, "__flet_easy_pagesy__", pagesy)  # noqa: B010

        self._logger.debug("Adding page: %s", self.__pages[-1])
        return func

    return decorator

view(func)

Decorator to add custom view controls (appbar, navigation, etc).

The decorated function receives data:fs.Datasy and returns fs.Viewsy.

Source code in src/flet_easy/core/app.py
def view(self, func: Callable[[Datasy], Viewsy]) -> Callable[[Datasy], Viewsy]:
    """Decorator to add custom view controls (appbar, navigation, etc).

    The decorated function receives `data:fs.Datasy` and returns `fs.Viewsy`.
    """
    self.__view_data = func
    self._logger.debug("Adding view: %s", self.__view_data)
    return func

login(func)

Decorator to add login configuration for protected routes.

The decorated function receives data:fs.Datasy and must return a boolean.

Source code in src/flet_easy/core/app.py
def login(self, func: Callable[[Datasy], bool]) -> Callable[[Datasy], bool]:
    """Decorator to add login configuration for protected routes.

    The decorated function receives `data:fs.Datasy` and must return a boolean.
    """
    self.__config_login = func
    self._logger.debug("Adding login: %s", self.__config_login)
    return func

config_event_handler(func)

Decorator to add page event handler settings.

Source code in src/flet_easy/core/app.py
def config_event_handler(self, func: Callable[[Datasy], None]) -> Callable[[Datasy], None]:
    """Decorator to add page event handler settings."""
    self.__config_event = func
    self._logger.debug("Adding config event: %s", self.__config_event)
    return func

run(name='', host=None, port=0, view=AppView.FLET_APP, assets_dir='assets', upload_dir=None, web_renderer=WebRenderer.CANVAS_KIT, route_url_strategy=RouteUrlStrategy.PATH, export_asgi_app=False, fastapi=False, **kwargs)

Execute the app. Supports async, fastapi, and export_asgi_app.

Source code in src/flet_easy/core/app.py
def run(
    self,
    name: str = "",
    host: Optional[str] = None,
    port: int = 0,
    view: Optional[AppView] = AppView.FLET_APP,
    assets_dir: str = "assets",
    upload_dir: Optional[str] = None,
    web_renderer: WebRenderer = WebRenderer.CANVAS_KIT,
    route_url_strategy: Union[RouteUrlStrategy, str] = RouteUrlStrategy.PATH,
    export_asgi_app: bool = False,
    fastapi: bool = False,
    **kwargs: Any,
) -> Any:
    """Execute the app. Supports async, fastapi, and export_asgi_app."""
    main = self.get_app()

    if fastapi:
        warn(
            "Avoid using the 'fastapi' parameter in the 'run()' method, "
            "instead use the 'get_app()' method.",
            category=DeprecationWarning,
            stacklevel=2,
        )
        return main

    try:
        self._logger.debug("running app from fletEasy with flet")
        app_kwargs: dict[str, Any] = {
            "name": name,
            "host": host,
            "port": port,
            "view": view,
            "assets_dir": assets_dir,
            "upload_dir": upload_dir,
            "web_renderer": web_renderer,
            "export_asgi_app": export_asgi_app,
            **kwargs,
        }
        if route_url_strategy is not None:
            app_kwargs["route_url_strategy"] = route_url_strategy

        return app(
            main,
            **app_kwargs,
        )
    except RuntimeError:
        raise FletEasyError(
            "If you are using fastapi from flet, set the 'fastapi = True' "
            "parameter of the run() method."
        )

add_pages(group_pages)

Add pages from other files.

Example:

app.add_pages([index, test, contador, login, task])

Source code in src/flet_easy/core/app.py
def add_pages(self, group_pages: Union[list[AddPagesy], AddPagesy]) -> None:
    """Add pages from other files.

    Example:
    ```python
    app.add_pages([index, test, contador, login, task])
    ```
    """
    group_pages = group_pages if isinstance(group_pages, list) else [group_pages]

    try:
        for page in group_pages:
            if self.__route_prefix:
                self.__pages.extend(page._add_pages(self.__route_prefix))
            else:
                self.__pages.extend(page._add_pages())

            self._logger.debug("Adding group of pages: %d: %s", len(group_pages), page)
    except Exception as e:
        raise AddPagesError("Add pages error in route: ", e)

Quick Reference:

import flet_easy as fs

app = fs.FletEasy(
    route_prefix="/app",
    route_init="/app/home",
    route_login="/app/login",
    on_keyboard=True,
    on_resize=True,
    secret_key=fs.SecretKey("your-key"),
    auto_logout=True,
    logger=True
)

@app.page("/home")
def home_page(data: fs.Datasy):
    return ft.View("/home", controls=[...])

app.run()

Datasy

flet_easy.Datasy

Bases: AuthMixin

Core data object passed to route handlers in Flet-Easy.

Provides access to the Flet Page instance, URL parameters, routing state, client storage, and authentication utilities.

Attributes: * page : The active Flet Page instance. * url_params : A dictionary of parsed parameters from the current URL. * view : The View object configured by the @app.view decorator. * route_prefix : The configured application route prefix. * route_init : The initial application route. * route_login : The configured login route for protected endpoints. * share : Interface for storing and retrieving values in the client session. * on_keyboard_event : Contains keyboard event data if enabled. * on_resize : Contains window resize event data if enabled. * logout : Closes the active session in client storage. * login : Creates a new session in client storage. * go / go_route: Navigates to a new application route. * go_back : Navigates back to the previous route. * go_navigation_bar : Handles navigation bar index updates. * history_routes : History of visited routes in the current session. * route : The current application route. * redirect : Returns a Redirect object to bypass the current view (useful in middleware). * page_reload : Reloads the current route. * dynamic_control : Registers a dynamically updatable control for the current route. * confirm_pop : Triggers the ViewPopEvent manually.

Source code in src/flet_easy/core/data.py
class Datasy(AuthMixin):
    """Core data object passed to route handlers in Flet-Easy.

    Provides access to the Flet `Page` instance, URL parameters, routing state,
    client storage, and authentication utilities.

    Attributes:
    * `page` : The active Flet `Page` instance.
    * `url_params` : A dictionary of parsed parameters from the current URL.
    * `view` : The `View` object configured by the `@app.view` decorator.
    * `route_prefix` : The configured application route prefix.
    * `route_init` : The initial application route.
    * `route_login` : The configured login route for protected endpoints.
    * `share` : Interface for storing and retrieving values in the client session.
    * `on_keyboard_event` : Contains keyboard event data if enabled.
    * `on_resize` : Contains window resize event data if enabled.
    * `logout` : Closes the active session in client storage.
    * `login` : Creates a new session in client storage.
    * `go` / `go_route`: Navigates to a new application route.
    * `go_back` : Navigates back to the previous route.
    * `go_navigation_bar` : Handles navigation bar index updates.
    * `history_routes` : History of visited routes in the current session.
    * `route` : The current application route.
    * `redirect` : Returns a `Redirect` object to bypass the current view (useful in middleware).
    * `page_reload` : Reloads the current route.
    * `dynamic_control` : Registers a dynamically updatable control for the current route.
    * `confirm_pop` : Triggers the `ViewPopEvent` manually.
    """

    __slots__ = (
        "__page",
        "__url_params",
        "__view",
        "__route_prefix",
        "__route_init",
        "__route_login",
        "__share",
        "__on_keyboard_event",
        "__on_resize",
        "__route",
        "_run_go",
        "__history_routes",
        "_dynamic_control",
        "__secret_key",
        "__auto_logout",
        "_sleep_auth",
        "_key_login",
        "_login_done",
        "_shared_preferences",
        "_use_client_storage",
    )

    def __init__(
        self,
        page: Page,
        route_prefix: str,
        route_init: str,
        route_login: Optional[str],
        secret_key: Optional[SecretKey],
        auto_logout: bool,
        page_on_keyboard: Keyboardsy,
        page_on_resize: Resizesy,
        go: Callable[..., Awaitable[None]],
    ) -> None:
        self.__page = page
        self.__url_params: dict[str, Any] = {}
        self.__view: Optional[Union[Viewsy, View]] = None
        self.__route_prefix = route_prefix
        self.__route_init = route_init
        self.__route_login = route_login
        if NEW_FLET_VERSION:
            self.__share = SharedPreferencesEdit(prefix="fs-share:")
            self._shared_preferences = SharedPreferencesEdit()
        else:
            self.__share = SessionStorageEdit(page)
            self._shared_preferences = SessionStorageEdit(page)

        self.__on_keyboard_event = page_on_keyboard
        self.__on_resize: Resizesy = page_on_resize
        self.__route: Optional[str] = None
        self._run_go = go
        self.__history_routes: deque[tuple[str, Optional[int]]] = deque()
        self._dynamic_control: dict[str, list[tuple[Control, Callable[[Control], None]]]] = {}

        self.__secret_key = secret_key
        self.__auto_logout: bool = auto_logout
        self._sleep_auth: int = 1
        self._key_login: Optional[str] = None
        self._login_done: bool = False
        # Cached once per session — storage API never changes at runtime
        self._use_client_storage: bool = hasattr(page, "client_storage")

        _logger.debug(
            "Using SharedPreferences (Flet >= 0.80)"
            if NEW_FLET_VERSION
            else "Using SessionStorage (Flet < 0.80)"
        )

    @property
    def page(self) -> Page:
        return self.__page

    @page.setter
    def page(self, page: Page):
        self.__page = page

    @property
    def history_routes(self) -> deque[tuple[str, Optional[int]]]:
        return self.__history_routes

    @property
    def url_params(self) -> dict[str, Any]:
        return self.__url_params

    @url_params.setter
    def url_params(self, url_params: dict[str, Any]):
        self.__url_params = url_params

    @property
    def view(self) -> Optional[Union[Viewsy, View]]:
        return self.__view

    @view.setter
    def view(self, view: Union[Viewsy, View]):
        self.__view = view

    @property
    def route_prefix(self) -> str:
        return self.__route_prefix

    @route_prefix.setter
    def route_prefix(self, route_prefix: str):
        self.__route_prefix = route_prefix

    @property
    def route_init(self) -> str:
        return self.__route_init

    @route_init.setter
    def route_init(self, route_init: str):
        self.__route_init = route_init

    @property
    def route_login(self) -> Optional[str]:
        return self.__route_login

    @route_login.setter
    def route_login(self, route_login: str):
        self.__route_login = route_login

    @property
    def share(self) -> Union[SessionStorageEdit, SharedPreferencesEdit]:
        return self.__share

    # events
    @property
    def on_keyboard_event(self) -> Keyboardsy:
        return self.__on_keyboard_event

    @on_keyboard_event.setter
    def on_keyboard_event(self, on_keyboard_event: Keyboardsy):
        self.__on_keyboard_event = on_keyboard_event

    @property
    def on_resize(self) -> Resizesy:
        return self.__on_resize

    @on_resize.setter
    def on_resize(self, on_resize: Resizesy):
        self.__on_resize = on_resize

    @property
    def key_login(self) -> Optional[str]:
        return self._key_login

    @property
    def auto_logout(self) -> bool:
        return self.__auto_logout

    @property
    def secret_key(self) -> Optional[SecretKey]:
        return self.__secret_key

    @property
    def route(self) -> Optional[str]:
        return self.__route

    @route.setter
    def route(self, route: str):
        self.__route = route

    """ Page go  """

    def go(self, route: str) -> Callable[[Any], None]:
        """To change the application path, it is important for better validation to avoid using `page.go()`."""

        return lambda _=None: self.go_route(route)

    def go_route(self, route: Union[str, int]) -> None:
        """To change the application path, it is important for better validation to avoid using `page.go()`."""
        self.page.run_task(self._run_go, route)

    def go_navigation_bar(self, e: ControlEvent) -> None:
        """Handles navigation bar changes. Use this method in the on_change event of
        'ft.NavigationBar' or 'ft.CupertinoNavigationBar' controls."""
        index = getattr(e.control, "selected_index", None)
        if index is not None:
            self.page.run_task(self._run_go, index)

    def redirect(self, route: str) -> Redirect:
        """Useful if you do not want to access a route that has already been sent."""
        return Redirect(route)

    def go_back(self, e: Optional[ControlEvent] = None) -> None:
        """Go back to the previous route."""

        if len(self.history_routes) > 1:
            self.history_routes.pop()
            route, index = self.history_routes.pop()

            if index is not None and self.view and self.view.navigation_bar:
                self.view.navigation_bar.selected_index = index

            self.page.run_task(self._run_go, route)
        else:
            _logger.warning("go_back: called with no navigation history to go back to.")

    def page_reload(self) -> None:
        """Use this method to reload the page, restores the default values of the page"""
        self.page.run_task(self._run_go, self.page.route, page_reload=True)

    def dynamic_control(self, control: Control, func_update: Callable[[Control], None]) -> None:
        """Adds dynamic control to the page, allowing real-time updates when caching is enabled on the page."""
        self._dynamic_control.setdefault(self.page.route, []).append((control, func_update))

    def confirm_pop(self, e: ViewPopEvent) -> None:
        """Confirm pop view"""
        self.go_back()
        if hasattr(e.view, "confirm_pop"):
            _ = getattr(e.view, "confirm_pop")(False)  # noqa: B009

pagepropertywritable

url_paramspropertywritable

viewpropertywritable

route_prefixpropertywritable

route_initpropertywritable

route_loginpropertywritable

shareproperty

on_keyboard_eventpropertywritable

on_resizepropertywritable

history_routesproperty

routepropertywritable

go(route)

To change the application path, it is important for better validation to avoid using page.go().

Source code in src/flet_easy/core/data.py
def go(self, route: str) -> Callable[[Any], None]:
    """To change the application path, it is important for better validation to avoid using `page.go()`."""

    return lambda _=None: self.go_route(route)

go_back(e=None)

Go back to the previous route.

Source code in src/flet_easy/core/data.py
def go_back(self, e: Optional[ControlEvent] = None) -> None:
    """Go back to the previous route."""

    if len(self.history_routes) > 1:
        self.history_routes.pop()
        route, index = self.history_routes.pop()

        if index is not None and self.view and self.view.navigation_bar:
            self.view.navigation_bar.selected_index = index

        self.page.run_task(self._run_go, route)
    else:
        _logger.warning("go_back: called with no navigation history to go back to.")

go_navigation_bar(e)

Handles navigation bar changes. Use this method in the on_change event of 'ft.NavigationBar' or 'ft.CupertinoNavigationBar' controls.

Source code in src/flet_easy/core/data.py
def go_navigation_bar(self, e: ControlEvent) -> None:
    """Handles navigation bar changes. Use this method in the on_change event of
    'ft.NavigationBar' or 'ft.CupertinoNavigationBar' controls."""
    index = getattr(e.control, "selected_index", None)
    if index is not None:
        self.page.run_task(self._run_go, index)

redirect(route)

Useful if you do not want to access a route that has already been sent.

Source code in src/flet_easy/core/data.py
def redirect(self, route: str) -> Redirect:
    """Useful if you do not want to access a route that has already been sent."""
    return Redirect(route)

Quick Reference:

def my_page(data: fs.Datasy):
    # Immediate Navigation / Redirection
    data.go_route("/other-page") # or data.redirect("/other-page")
    data.go_back()

    # Authentication
    data.login("token", "jwt-value")
    data.logout("token", next_route="/login")

    # JWT decoding (replaces fs.decode / fs.decode_async)
    decoded = data.decode_jwt("token")           # sync
    # decoded = await data.decode_jwt_async("token")  # async

    # Data sharing
    data.share.set("key", "value")
    value = data.share.get("key")

    # Page access
    data.page.title = "New Title"
    data.page.update()

Pagesy

flet_easy.Pagesy

To add pages, it requires the following parameters: * route: text string of the url, for example('/task'). * view: Stores the page function. * title : Define the title of the page. * index : Define the index of the page, use in controls like ft.NavigationBar and ft.CupertinoNavigationBar. * clear: Removes the pages from the page.views list of flet. (optional) * share_data : It is a boolean value, which is useful if you want to share data between pages, in a more restricted way. (optional) * protected_route: Protects the route of the page, according to the configuration of the login decorator of the FletEasy class. (optional) * custom_params: To add validation of parameters in the custom url using a list, where the key is the name of the parameter validation and the value is the custom function that must report a boolean value. * middleware : It acts as an intermediary between different software components, intercepting and processing requests and responses. They allow adding functionalities to an application in a flexible and modular way. (optional) * cache: Boolean that preserves page state when navigating. Controls retain their values instead of resetting. (Optional)

Example:

Pagesy("/test/{id:d}/user/{name:l}", test_page, protected_route=True)

Source code in src/flet_easy/core/pages.py
class Pagesy:
    """To add pages, it requires the following parameters:
    * `route`: text string of the url, for example(`'/task'`).
    * `view`: Stores the page function.
    * `title` : Define the title of the page.
    * `index` : Define the index of the page, use in controls like `ft.NavigationBar` and `ft.CupertinoNavigationBar`.
    * `clear`: Removes the pages from the `page.views` list of flet. (optional)
    * `share_data` : It is a boolean value, which is useful if you want to share data between pages, in a more restricted way. (optional)
    * `protected_route`: Protects the route of the page, according to the configuration of the `login` decorator of the `FletEasy` class. (optional)
    * `custom_params`: To add validation of parameters in the custom url using a list, where the key is the name of the parameter validation and the value is the custom function that must report a boolean value.
    * `middleware` : It acts as an intermediary between different software components, intercepting and processing requests and responses. They allow adding functionalities to an application in a flexible and modular way. (optional)
    * `cache`: Boolean that preserves page state when navigating. Controls retain their values instead of resetting. (Optional)

    Example:
    ```python
    Pagesy("/test/{id:d}/user/{name:l}", test_page, protected_route=True)
    ```
    """

    __slots__ = (
        "route",
        "view",
        "title",
        "index",
        "clear",
        "share_data",
        "protected_route",
        "custom_params",
        "middleware",
        "cache",
        "_is_component",
        "_middlewares_request",
        # Cached signature flags — computed once on first render, reused every navigation
        "_view_has_params",
        "_build_has_params",
    )

    def __init__(
        self,
        route: str,
        view: ViewHandler,
        title: Optional[str] = None,
        index: Optional[int] = None,
        clear: bool = False,
        share_data: bool = False,
        protected_route: bool = False,
        custom_params: Optional[dict[str, Callable[..., bool]]] = None,
        middleware: Middleware = None,
        cache: bool = False,
    ):
        self.route = route
        self.view = view
        self.title = title
        self.index = index
        self.clear = clear
        self.share_data = share_data
        self.protected_route = protected_route
        self.custom_params = custom_params
        self.middleware: Middleware = middleware
        self.cache: bool = cache
        self._is_component: bool = getattr(view, "__is_component__", False) or (
            isinstance(view, type)
            and getattr(getattr(view, "build", None), "__is_component__", False)
        )
        self._middlewares_request: deque[MiddlewareRequest] = deque()
        # None = not yet computed, True/False = cached result
        self._view_has_params: Optional[bool] = None
        self._build_has_params: Optional[bool] = None

    def _get_view_has_params(self) -> bool:
        """Cached check: does the view function/class __init__ accept parameters?"""
        if self._view_has_params is None:
            try:
                self._view_has_params = bool(signature(self.view).parameters)
            except (ValueError, TypeError):
                self._view_has_params = False
        return self._view_has_params

    def _get_build_has_params(self, build_fn: Any) -> bool:
        """Cached check: does the build() method accept parameters beyond self?

        Caching per Pagesy is safe because all instances of the same class share the
        same build() signature (we only check declared params, not self/instance state).
        """
        if self._build_has_params is None:
            try:
                self._build_has_params = bool(signature(build_fn).parameters)
            except (ValueError, TypeError):
                self._build_has_params = False
        return self._build_has_params

    def _valid_middlewares_request(self) -> bool:
        return bool(self._middlewares_request)

    def _process_middleware(self, item: MiddlewareItem) -> None:
        """Process and validate middleware handlers."""

        if not isinstance(self.middleware, deque):
            if self.middleware is None:
                self.middleware = deque[MiddlewareItem]()
            elif isinstance(self.middleware, (list, tuple, set)):
                self.middleware = deque[MiddlewareItem](
                    cast(Iterable[MiddlewareItem], self.middleware)
                )
            else:
                self.middleware = deque[MiddlewareItem]([self.middleware])

        middleware_deque = cast(deque[MiddlewareItem], self.middleware)

        if isinstance(item, MiddlewareRequest) or (
            isinstance(item, type) and issubclass(item, MiddlewareRequest)
        ):
            # Store as-is (class or instance) — instantiation happens lazily in the router
            # because MiddlewareRequest._data is only set after FletEasyX.__init__
            self._middlewares_request.append(cast(MiddlewareRequest, item))
            middleware_deque.append(item)
        elif callable(item):
            middleware_deque.append(item)
        else:
            raise TypeError(
                f"Class '{getattr(item, '__name__', type(item).__name__)}' must inherit from MiddlewareRequest class or be a function",
            )

    def _check_middleware(self, middleware: Middleware) -> None:
        if middleware is None and self.middleware is None:
            return

        # Capture original page-level middleware before resetting
        original_page_middleware = self.middleware

        def _to_iter(m: Middleware) -> Iterable[MiddlewareItem]:
            if m is None:
                return ()
            if isinstance(m, (list, tuple, set, deque)):
                return cast(Iterable[MiddlewareItem], m)
            return (m,)

        # Reset so _process_middleware starts fresh
        self.middleware = deque[MiddlewareItem]()
        self._middlewares_request = deque()

        # Chain original page middleware first, then group-level middleware
        for m in chain(_to_iter(original_page_middleware), _to_iter(middleware)):
            try:
                self._process_middleware(m)
            except (TypeError, AssertionError) as e:
                raise ConfigurationError(f"Invalid middleware configuration: {str(e)}")

    def __repr__(self) -> str:
        return f"Pagesy(route={self.route}, view={self.view}, title={self.title}, index={self.index}, clear={self.clear}, share_data={self.share_data}, protected_route={self.protected_route}, custom_params={self.custom_params}, middleware={self.middleware}, cache={self.cache})"

__init__(route, view, title=None, index=None, clear=False, share_data=False, protected_route=False, custom_params=None, middleware=None, cache=False)

Source code in src/flet_easy/core/pages.py
def __init__(
    self,
    route: str,
    view: ViewHandler,
    title: Optional[str] = None,
    index: Optional[int] = None,
    clear: bool = False,
    share_data: bool = False,
    protected_route: bool = False,
    custom_params: Optional[dict[str, Callable[..., bool]]] = None,
    middleware: Middleware = None,
    cache: bool = False,
):
    self.route = route
    self.view = view
    self.title = title
    self.index = index
    self.clear = clear
    self.share_data = share_data
    self.protected_route = protected_route
    self.custom_params = custom_params
    self.middleware: Middleware = middleware
    self.cache: bool = cache
    self._is_component: bool = getattr(view, "__is_component__", False) or (
        isinstance(view, type)
        and getattr(getattr(view, "build", None), "__is_component__", False)
    )
    self._middlewares_request: deque[MiddlewareRequest] = deque()
    # None = not yet computed, True/False = cached result
    self._view_has_params: Optional[bool] = None
    self._build_has_params: Optional[bool] = None

Quick Reference:

from flet_easy import Pagesy

page = Pagesy(
    route="/user/{id:d}",
    view=user_view_function,
    title="User Profile",
    protected_route=True,
    middleware=[auth_middleware],
    cache=True,
    share_data=True
)

AddPagesy (Sub-Routes)

flet_easy.AddPagesy

This class allows you to add pages from other files to the main Flet-Easy class.

Requires the following parameters: - route_prefix: A text string that will be joined to the URL of the page decorator, for example ('/users'). This will encompass all URLs in this class. (Optional) - middleware: A list of middlewares to be added to the page. (Optional)

Example:

users = fs.AddPagesy(route_prefix="/user")


@users.page("/task")
async def task_page(data: fs.Datasy):
    page = data.page
    page.title = "Task"
    return ft.View(
        route="/users/task",
        controls=[ft.Text("Task")],
    )

Source code in src/flet_easy/core/pages.py
class AddPagesy:
    """This class allows you to add pages from other files to the main `Flet-Easy` class.

    Requires the following parameters:
    - **route_prefix:** A text string that will be joined to the URL of the `page` decorator, for example (`'/users'`). This will encompass all URLs in this class. (Optional)
    - **middleware:** A list of middlewares to be added to the page. (Optional)

    **Example:**
    ```python
    users = fs.AddPagesy(route_prefix="/user")


    @users.page("/task")
    async def task_page(data: fs.Datasy):
        page = data.page
        page.title = "Task"
        return ft.View(
            route="/users/task",
            controls=[ft.Text("Task")],
        )
    ```
    """

    __slots__ = ("route_prefix", "middleware", "__pages", "_finalized")

    def __init__(
        self,
        route_prefix: Optional[str] = None,
        middleware: Middleware = None,
    ):
        self.route_prefix = route_prefix.rstrip("/") if route_prefix else None
        self.middleware = middleware
        self.__pages: deque[Pagesy] = deque()
        self._finalized: bool = False

    def page(
        self,
        route: str,
        title: Optional[str] = None,
        index: Optional[int] = None,
        page_clear: bool = False,
        share_data: bool = False,
        protected_route: bool = False,
        custom_params: Optional[dict[str, Any]] = None,
        middleware: Middleware = None,
        cache: bool = False,
    ) -> Callable[..., Any]:
        """Decorator for adding pages with configuration."""

        def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
            pagesy = Pagesy(
                route=normalize_route(self.route_prefix, route),
                view=func,
                title=title,
                index=index,
                clear=page_clear,
                share_data=share_data,
                protected_route=protected_route,
                custom_params=custom_params,
                middleware=middleware,
                cache=cache,
            )
            self.__pages.append(pagesy)

            # Back-reference for reverse decorator order (@ft.component on top)
            setattr(func, "__flet_easy_pagesy__", pagesy)  # noqa: B010

            return func

        return decorator

    def _add_pages(self, route: Optional[str] = None) -> deque[Pagesy]:
        """Add pages with optional route prefix override.

        Idempotent: safe to call multiple times — subsequent calls are no-ops
        to prevent route prefixes from being compounded.
        """
        if self._finalized:
            return self.__pages

        for page in self.__pages:
            page._check_middleware(self.middleware)

            if route:
                page.route = normalize_route(route, page.route)

        self._finalized = True
        return self.__pages

    def __repr__(self) -> str:
        return f"AddPagesy(route_prefix={self.route_prefix}, middleware={self.middleware}, number_pages={len(self.__pages)}, pages={self.__pages})"

page(route, title=None, index=None, page_clear=False, share_data=False, protected_route=False, custom_params=None, middleware=None, cache=False)

Decorator for adding pages with configuration.

Source code in src/flet_easy/core/pages.py
def page(
    self,
    route: str,
    title: Optional[str] = None,
    index: Optional[int] = None,
    page_clear: bool = False,
    share_data: bool = False,
    protected_route: bool = False,
    custom_params: Optional[dict[str, Any]] = None,
    middleware: Middleware = None,
    cache: bool = False,
) -> Callable[..., Any]:
    """Decorator for adding pages with configuration."""

    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
        pagesy = Pagesy(
            route=normalize_route(self.route_prefix, route),
            view=func,
            title=title,
            index=index,
            clear=page_clear,
            share_data=share_data,
            protected_route=protected_route,
            custom_params=custom_params,
            middleware=middleware,
            cache=cache,
        )
        self.__pages.append(pagesy)

        # Back-reference for reverse decorator order (@ft.component on top)
        setattr(func, "__flet_easy_pagesy__", pagesy)  # noqa: B010

        return func

    return decorator

Quick Reference:

from flet_easy import AddPagesy, Pagesy

pages = AddPagesy([
    Pagesy("/home", home_view),
    Pagesy("/about", about_view),
    Pagesy("/contact", contact_view)
])

app.add_pages(pages)

Declarative Class Components (v0.3.0+)

Flet-Easy naturally integrates class-based views with Flet's Native Component System. You can write your view logic within a class, decorate the build() method with @ft.component, and self.data will automatically be injected upon initialization.

Quick Reference:

import flet as ft
import flet_easy as fs

@app.page("/profile")
class ProfilePage:
    @ft.component
    def build(self):
        return ft.View(
            controls=[
                ft.Text("Profile"),
                ft.ElevatedButton("Go Home", on_click=self.data.go("/"))
            ]
        )

Viewsy

flet_easy.Viewsy

Bases: View

Source code in src/flet_easy/ui/controls.py
class Viewsy(View):
    pass

Quick Reference:

from flet_easy import Viewsy
import flet as ft

@app.view
def main_view(data: fs.Datasy):
    return Viewsy(
        appbar=ft.AppBar(title=ft.Text("My App")),
        drawer=ft.NavigationDrawer(...),
        bgcolor=ft.Colors.GREY_50,
        padding=ft.padding.all(20)
    )

Authentication & Security

SecretKey

flet_easy.SecretKeydataclass

Correctly add the secret key in the FletEasy class parameter.

Source code in src/flet_easy/security/config.py
@dataclass
class SecretKey:
    """Correctly add the secret key in the `FletEasy` class parameter."""

    algorithm: str = "HS256"
    secret: Optional[str] = None
    pem_key: Optional[PemKey] = None
    Jwt: bool = False

EasyKey

flet_easy.EasyKey

A utility class to easily generate cryptographic keys for JWT tokens. Supports HS256 and RS256 algorithms.

Example:
import flet_easy as fs

key = fs.EasyKey()

# --- HS256
SECRET_KEY = key.secret_key()

# --- RS256
PRIVATE_KEY = key.private_key()
PUBLIC_KEY = key.public_key()
Source code in src/flet_easy/security/jwt.py
class EasyKey:
    """A utility class to easily generate cryptographic keys for JWT tokens.
    Supports HS256 and RS256 algorithms.

    ### Example:
    ```python
    import flet_easy as fs

    key = fs.EasyKey()

    # --- HS256
    SECRET_KEY = key.secret_key()

    # --- RS256
    PRIVATE_KEY = key.private_key()
    PUBLIC_KEY = key.public_key()
    ```
    """

    def __init__(self) -> None:
        self.public: Any = None
        self.private: Any = None

    def _generate_keys(self) -> None:
        if self.private is None or self.public is None:
            self.public, self.private = newkeys(2048)

    def private_key(self) -> str:
        self._generate_keys()
        assert self.private is not None
        return self.private.save_pkcs1().decode("utf-8")

    def public_key(self) -> str:
        self._generate_keys()
        assert self.public is not None
        return self.public.save_pkcs1().decode("utf-8")

    def secret_key(self) -> bytes:
        return token_bytes(64).hex().encode("utf-8")

Quick Reference:

from flet_easy import EasyKey, SecretKey

# Generate keys
key_gen = EasyKey()
secret = key_gen.secret_key()  # For HS256
private_key = key_gen.private_key()  # For RS256
public_key = key_gen.public_key()  # For RS256

# Use with FletEasy
app = fs.FletEasy(secret_key=SecretKey(secret))

JWT Functions

[!warning] Deprecated since v0.4.0 fs.decode() and fs.decode_async() are deprecated. Use data.decode_jwt() / data.decode_jwt_async() (methods on Datasy) instead.

flet_easy.Datasy.decode_jwt(key)

Decode JWT

Parameters

  • key: Key to store the value used in the login method of Datasy.
Source code in src/flet_easy/security/auth.py
def decode_jwt(self, key: str) -> Union[dict[str, Any], bool]:
    """Decode JWT

    ## Parameters
    - key: Key to store the value used in the `login` method of `Datasy`.
    """
    try:
        return self.page.run_task(self.decode_jwt_async, key).result(timeout=5)
    except TimeoutError as e:
        raise LoginError("Decode error, using decode_async:", e)

flet_easy.Datasy.decode_jwt_async(key_login)async

Decode JWT asynchronously

Parameters

  • key: Key to store the value used in the login method of Datasy.
Source code in src/flet_easy/security/auth.py
async def decode_jwt_async(self, key_login: str) -> Union[dict[str, Any], bool]:
    """Decode JWT asynchronously

    ## Parameters
    - key: Key to store the value used in the `login` method of `Datasy`.
    """
    jwt_token = await self._storage_get_async(key_login)

    try:
        self._key_login = key_login

        self._evaluate_secret_key()

        if self.secret_key is None:
            raise SecretKeyError("SecretKey is not configured.")

        if jwt_token is None:
            return False

        if self.auto_logout and not self._login_done:
            identity = self._get_client_identity()
            if identity:
                client_ip, client_user_agent = identity
                self.page.pubsub.send_others_on_topic(
                    client_ip + client_user_agent, Msg("updateLogin", value=self._login_done)
                )

        if self.secret_key.algorithm is None:
            raise AlgorithmJwtError("Algorithm not configured in SecretKey.")

        decode = _decode_payload(
            jwt=jwt_token,
            secret_key=self._active_key,
            algorithms=self.secret_key.algorithm,
        )

        # It checks if there is a logout time, if there is a logout task running
        if decode.get("exp") and not self._login_done and self.auto_logout:
            self._create_task_login_update(decode)
        return decode

    except (ExpiredSignatureError, InvalidKeyError):
        self.logout(key_login)
        return False
    except DecodeError as e:
        self.logout(key_login)
        raise LogoutError(
            "Decoding error, possibly there is a double use of the storage 'key', Secret key invalid! or ",
            e,
        )

flet_easy.decode(key_login, data)

decodes the jwt and updates the browser sessions.

Parameters to use:
  • key_login : key used to store data in the client, also used in the login method of Datasy.
  • data : Instance object of the Datasy class.
Source code in src/flet_easy/security/jwt.py
@deprecated("Use the 'data.decode_jwt' method instead.", version="0.4.0")
def decode(key_login: str, data: Datasy) -> Union[dict[str, Any], bool]:  # noqa: UP007
    """decodes the jwt and updates the browser sessions.

    ### Parameters to use:
    * `key_login` : key used to store data in the client, also used in the `login` method of `Datasy`.
    * `data` : Instance object of the `Datasy` class.
    """
    return data.decode_jwt(key_login)

flet_easy.decode_async(key_login, data)async

"decodes the jwt and updates the browser sessions.

Parameters to use:
  • key_login : key used to store data in the client, also used in the login method of Datasy.
  • data : Instance object of the Datasy class.
Source code in src/flet_easy/security/jwt.py
@deprecated("Use the 'data.decode_jwt_async' method instead.", version="0.4.0")
async def decode_async(key_login: str, data: Datasy) -> Union[dict[str, Any], bool]:  # noqa: UP007
    """ "decodes the jwt and updates the browser sessions.

    ### Parameters to use:
    * `key_login` : key used to store data in the client, also used in the `login` method of `Datasy`.
    * `data` : Instance object of the `Datasy` class.
    """
    return await data.decode_jwt_async(key_login)

flet_easy.encode_HS256(payload, secret_key, time_expiry=None)

Source code in src/flet_easy/security/config.py
def encode_HS256(  # noqa: N802
    payload: dict[str, Any], secret_key: str, time_expiry: Optional[timedelta] = None
) -> str:
    payload = _time_exp(time_expiry, payload)
    return encode(
        payload=payload,
        key=secret_key,
        algorithm="HS256",
    )

flet_easy.encode_RS256(payload, private, time_expiry=None)

Source code in src/flet_easy/security/config.py
def encode_RS256(  # noqa: N802
    payload: dict[str, Any], private: str, time_expiry: Optional[timedelta] = None
) -> str:
    payload = _time_exp(time_expiry, payload)
    return encode(
        payload=payload,
        key=private,
        algorithm="RS256",
    )

Quick Reference:

from flet_easy import SecretKey
import flet_easy as fs

# --- New API (v0.4.0+) ---
@app.login
async def login_required(data: fs.Datasy) -> bool:
    return await data.decode_jwt_async(key_login="login")

# --- Deprecated (will be removed) ---
# value = fs.decode(key_login="login", data=data)

Event Handling

Keyboardsy

flet_easy.Keyboardsy

Class that manages keyboard input values.

Methods:

NameDescription
add_control

Add functions to be executed on key press (supports async).

key

Returns the key value.

shift

Returns the shift state.

ctrl

Returns the ctrl state.

alt

Returns the alt state.

meta

Returns the meta state.

test

Returns a message of all keyboard input values.

Source code in src/flet_easy/ui/controls.py
class Keyboardsy:
    """Class that manages keyboard input values.

    Methods:
        add_control(function): Add functions to be executed on key press (supports async).
        key(): Returns the key value.
        shift(): Returns the shift state.
        ctrl(): Returns the ctrl state.
        alt(): Returns the alt state.
        meta(): Returns the meta state.
        test(): Returns a message of all keyboard input values.
    """

    __slots__ = ("__call", "__controls", "__current_route", "__current_controls")

    def __init__(self, call: Optional[KeyboardEvent] = None) -> None:
        self.__call: Optional[KeyboardEvent] = call
        self.__controls: dict[str, list[Any]] = {}
        self.__current_route: Optional[str] = None
        self.__current_controls: list[Any] = []

    @property
    def current_route(self) -> Optional[str]:
        return self.__current_route

    @current_route.setter
    def current_route(self, value: str) -> None:
        self.__current_route = value
        if value not in self.__controls:
            self.__controls[value] = []
        self.__current_controls = self.__controls[value]

    @property
    def call(self) -> Optional[KeyboardEvent]:
        return self.__call

    @call.setter
    def call(self, call: KeyboardEvent) -> None:
        self.__call = call

    def _controls(self) -> bool:
        return bool(self.__current_controls)

    def clear(self) -> None:
        self.__current_controls.clear()

    def add_control(self, function: Callable[..., Any]) -> None:
        """Method to add functions to be executed by pressing a key `(supports async, if the app is one)`."""
        self.__current_controls.append(function)

    async def _run_controls(self) -> None:
        """Execute all registered keyboard control functions."""
        if not self.__current_controls:
            return

        for control_func in self.__current_controls:
            try:
                if iscoroutinefunction(control_func):
                    await control_func()
                else:
                    control_func()
            except Exception as e:
                raise KeyBoardEventError(
                    f"Error executing keyboard control in function: {control_func} - {e}"
                )

    def key(self) -> str:
        assert self.__call is not None, "call must be set before reading keyboard event properties"
        return self.__call.key

    def shift(self) -> bool:
        assert self.__call is not None, "call must be set before reading keyboard event properties"
        return self.__call.shift

    def ctrl(self) -> bool:
        assert self.__call is not None, "call must be set before reading keyboard event properties"
        return self.__call.ctrl

    def alt(self) -> bool:
        assert self.__call is not None, "call must be set before reading keyboard event properties"
        return self.__call.alt

    def meta(self) -> bool:
        assert self.__call is not None, "call must be set before reading keyboard event properties"
        return self.__call.meta

    def test(self) -> str:
        assert self.__call is not None, "call must be set before reading keyboard event properties"
        c = self.__call
        return f"Key: {c.key}, Shift: {c.shift}, Control: {c.ctrl}, Alt: {c.alt}, Meta: {c.meta}"

add_control(function)

Method to add functions to be executed by pressing a key (supports async, if the app is one).

Source code in src/flet_easy/ui/controls.py
def add_control(self, function: Callable[..., Any]) -> None:
    """Method to add functions to be executed by pressing a key `(supports async, if the app is one)`."""
    self.__current_controls.append(function)

key()

Source code in src/flet_easy/ui/controls.py
def key(self) -> str:
    assert self.__call is not None, "call must be set before reading keyboard event properties"
    return self.__call.key

shift()

Source code in src/flet_easy/ui/controls.py
def shift(self) -> bool:
    assert self.__call is not None, "call must be set before reading keyboard event properties"
    return self.__call.shift

ctrl()

Source code in src/flet_easy/ui/controls.py
def ctrl(self) -> bool:
    assert self.__call is not None, "call must be set before reading keyboard event properties"
    return self.__call.ctrl

alt()

Source code in src/flet_easy/ui/controls.py
def alt(self) -> bool:
    assert self.__call is not None, "call must be set before reading keyboard event properties"
    return self.__call.alt

meta()

Source code in src/flet_easy/ui/controls.py
def meta(self) -> bool:
    assert self.__call is not None, "call must be set before reading keyboard event properties"
    return self.__call.meta

test()

Source code in src/flet_easy/ui/controls.py
def test(self) -> str:
    assert self.__call is not None, "call must be set before reading keyboard event properties"
    c = self.__call
    return f"Key: {c.key}, Shift: {c.shift}, Control: {c.ctrl}, Alt: {c.alt}, Meta: {c.meta}"

Quick Reference:

@app.config_event_handler
def handle_events(data: fs.Datasy):
    if data.on_keyboard_event:
        key = data.on_keyboard_event.key()
        if key == "Escape":
            data.go("/home")
        elif key == "F1":
            data.go("/help")

Resizesy

flet_easy.Resizesy

For the manipulation of the on_resize event of flet.

Attributes:

NameTypeDescription
eOptional[ControlEvent]

Returns ControlEvent event, each time the height and width changes.

pagePage

Returns the Page instance.

heightOptional[Union[float, int]]

Returns the updated height value.

widthOptional[Union[float, int]]

Returns the updated width value.

heightX(pct)Optional[Union[float, int]]

Calculate percentage of page height (1-100).

widthX(pct)Optional[Union[float, int]]

Calculate percentage of page width (1-100).

margin_yUnion[float, int]

Y-axis margin value.

margin_xUnion[float, int]

X-axis margin value.

Source code in src/flet_easy/ui/controls.py
class Resizesy:
    """For the manipulation of the `on_resize` event of flet.

    Attributes:
        e: Returns `ControlEvent` event, each time the height and width changes.
        page: Returns the `Page` instance.
        height: Returns the updated height value.
        width: Returns the updated width value.
        heightX(pct): Calculate percentage of page height (1-100).
        widthX(pct): Calculate percentage of page width (1-100).
        margin_y: Y-axis margin value.
        margin_x: X-axis margin value.
    """

    __slots__ = ("__page", "__height", "__width", "__margin_y", "__margin_x", "__e")

    def __init__(self, page: Page) -> None:
        self.__page = page
        self.__height: Optional[Union[float, int]] = page.height
        self.__width: Optional[Union[float, int]] = page.width
        self.__margin_y: Union[float, int] = 0
        self.__margin_x: Union[float, int] = 0
        self.__e: Optional[ControlEvent] = None

    @property
    def page(self) -> Page:
        return cast(Page, self.__page)

    @property
    def e(self) -> Optional[ControlEvent]:
        return self.__e

    @e.setter
    def e(self, e: ControlEvent) -> None:
        self.__e = e
        self.__page = e.page
        self.__height = float(self.page.height or 0) - self.__margin_y
        self.__width = float(self.page.width or 0) - self.__margin_x

    @property
    def margin_y(self) -> Union[float, int]:
        return self.__margin_y

    @margin_y.setter
    def margin_y(self, value: int) -> None:
        """Enter a value that subtracts the margin of the page, so that 100% of the page can be occupied."""
        self.__margin_y = value * 2

    @property
    def margin_x(self) -> Union[float, int]:
        return self.__margin_x

    @margin_x.setter
    def margin_x(self, value: int) -> None:
        """Enter a value that subtracts the margin of the page, so that 100% of the page can be occupied."""
        self.__margin_x = value * 2

    @property
    def height(self) -> Optional[Union[float, int]]:
        return self.__height

    @property
    def width(self) -> Optional[Union[float, int]]:
        return self.__width

    def height_x(self, height: int) -> float:
        """Function to calculate the percentage of high that will be used in the page (1-100)."""
        current_height = float(self.page.height or 0) - self.margin_y
        if height < 100:
            return current_height * (height / 100)
        return current_height

    def width_x(self, width: int) -> float:
        """Function to calculate the percentage of width that will be used in the page (1-100)."""
        current_width = float(self.page.width or 0) - self.margin_x
        if width < 100:
            return current_width * (width / 100)
        return current_width

    def heightX(self, height: int) -> float:  # noqa: N802
        """Deprecated: Use height_x instead."""
        warn("heightX is deprecated, use height_x instead", DeprecationWarning, stacklevel=2)
        return self.height_x(height)

    def widthX(self, width: int) -> float:  # noqa: N802
        """Deprecated: Use width_x instead."""
        warn("widthX is deprecated, use width_x instead", DeprecationWarning, stacklevel=2)
        return self.width_x(width)

widthproperty

heightproperty

Quick Reference:

@app.config_event_handler
def handle_events(data: fs.Datasy):
    if data.on_resize:
        width = data.on_resize.width()
        height = data.on_resize.height()

        # Responsive layout adjustments
        if width < 600:
            # Mobile layout
            pass
        else:
            # Desktop layout
            pass

Responsive Design

ResponsiveControlsy

flet_easy.ResponsiveControlsy

Bases: Canvas

Allows the controls to adapt to the size of the app (responsive).

Parameters:

NameTypeDescriptionDefault
contentControl

Contains a flet control.

required
expandint

Space that will contain the content controller in the app.

required
resize_intervalint

Response time (optional).

1
on_resizecallable

Custom function executed on app resize (optional).

None
show_resizebool

Observe the size of the controller (optional).

False
show_resize_terminalbool

See the size in the terminal (optional).

False
Source code in src/flet_easy/ui/controls.py
class ResponsiveControlsy(Canvas):
    """Allows the controls to adapt to the size of the app (responsive).

    Parameters:
        content (Control): Contains a flet control.
        expand (int): Space that will contain the `content` controller in the app.
        resize_interval (int): Response time (optional).
        on_resize (callable): Custom function executed on app resize (optional).
        show_resize (bool): Observe the size of the controller (optional).
        show_resize_terminal (bool): See the size in the terminal (optional).
    """

    def __init__(
        self,
        content: Control,
        expand: int,
        resize_interval: int = 1,
        on_resize: Optional[Callable[..., Any]] = None,
        show_resize: bool = False,
        show_resize_terminal: bool = False,
        **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        self.content = content
        self.resize_interval = resize_interval
        self.resize_callback = on_resize
        self.expand = expand
        self.show_resize = show_resize
        self.show_resize_terminal = show_resize_terminal
        self.on_resize = self.__handle_canvas_resize

    def __handle_canvas_resize(self, e: Any) -> None:
        if self.resize_callback is not None:
            if iscoroutinefunction(self.resize_callback):
                getattr(self.page, "run_task", lambda *args: None)(self.resize_callback, e)
            else:
                self.resize_callback(e)

        elif self.show_resize:
            inner = getattr(self.content, "content", None)
            if inner:
                inner.value = f"{e.width} x {e.height}"
                self.update()
            else:
                cast(Any, self.content).content = Text(f"{e.width} x {e.height}")
                self.update()

        if self.show_resize_terminal:
            logger.debug("Canvas resize: %s x %s", e.width, e.height)

Quick Reference:

from flet_easy import ResponsiveControlsy
import flet as ft

responsive_text = ResponsiveControlsy(
    controls={
        "xs": ft.Text("Mobile", size=14),
        "sm": ft.Text("Tablet", size=16),
        "md": ft.Text("Desktop", size=18),
        "lg": ft.Text("Large Desktop", size=20),
    }
)

Reference System

Ref

flet_easy.Ref

Bases: Ref[T]

Get the reference of the control used by flet, linked to the created component. Similar to flet, but more reduced by getting the value of the control with (c).

Source code in src/flet_easy/ui/controls.py
class Ref(Ref[T]):
    """Get the reference of the control used by flet, linked to the created component.
    Similar to flet, but more reduced by getting the value of the control with (c)."""

    @property
    def c(self) -> T:
        current = super().current
        assert current is not None, "Ref.c accessed before control was mounted"
        return current

Quick Reference:

from flet_easy import Ref
import flet as ft

def my_page(data: fs.Datasy):
    text_ref = Ref[ft.TextField]()

    def handle_click(_):
        value = text_ref.current.value
        print(f"Input value: {value}")

    return ft.View(
        "/page",
        controls=[
            ft.TextField(ref=text_ref, label="Enter text"),
            ft.ElevatedButton("Get Value", on_click=handle_click)
        ]
    )

Middleware System

MiddlewareRequest

flet_easy.MiddlewareRequest

Source code in src/flet_easy/core/middleware.py
class MiddlewareRequest:
    _data: Optional[Datasy] = None

    def __init__(self) -> None:
        self._local_data: Optional[Datasy] = None

    @property
    def data(self) -> Optional[Datasy]:
        """Retrieve the context-local Datasy instance if set, otherwise fallback to class default."""
        val = current_data.get()
        if val is not None:
            return val
        if self._local_data is not None:
            return self._local_data
        return MiddlewareRequest._data

    @data.setter
    def data(self, value: Optional[Datasy]) -> None:
        self._local_data = value
        if value is not None:
            current_data.set(value)

    def before_request(self) -> None:
        pass

    def after_request(self) -> None:
        pass

before_request()

Source code in src/flet_easy/core/middleware.py
def before_request(self) -> None:
    pass

after_request()

Source code in src/flet_easy/core/middleware.py
def after_request(self) -> None:
    pass

Quick Reference:

from flet_easy import MiddlewareRequest, Redirect

class AuthMiddleware(MiddlewareRequest):
    def before_request(self):
        if not self.data.page.client_storage.get("auth_token"):
            return Redirect("/login")

    def after_request(self):
        # Log the request
        print(f"Accessed: {self.data.route}")

@app.page("/protected", middleware=[AuthMiddleware])
def protected_page(data: fs.Datasy):
    return ft.View("/protected", controls=[...])

Utility Classes

Redirect

flet_easy.Redirectdataclass

Source code in src/flet_easy/core/models.py
@dataclass
class Redirect:
    route: Optional[str] = None

Quick Reference:

from flet_easy import Redirect

def auth_middleware(data: fs.Datasy):
    if not data.page.client_storage.get("token"):
        return Redirect("/login")
    return None

Route Parameter Types

Flet-Easy supports several parameter types in dynamic routes:

TypeSyntaxDescriptionExample
Integer{name:d}Matches integers/user/{id:d}/user/123
String{name:str}Matches any string/category/{name:str}/category/electronics
Lowercase{name:l}Matches lowercase strings/tag/{slug:l}/tag/python-tips
Float{name:f}Matches floating point numbers/price/{amount:f}/price/19.99

Example Usage:

@app.page("/blog/{year:d}/{month:d}/{slug:str}")
def blog_post(data: fs.Datasy, year: int, month: int, slug: str):
    # year and month are automatically converted to int
    # slug remains as string
    post = get_blog_post(year, month, slug)
    return ft.View(f"/blog/{year}/{month}/{slug}", controls=[...])

Configuration Options

FletEasy Configuration

ParameterTypeDefaultDescription
route_prefixstr""Base prefix for all routes
route_initstr"/home"Initial route when app starts
route_loginstr | NoneNoneRedirect route for protected pages
on_keyboardboolFalseEnable keyboard event handling
on_resizeboolFalseEnable window resize events
secret_keySecretKey | NoneNoneSecret key for encryption and decoding JWTs across the application
auto_logoutboolFalseAuto-logout on JWT expiration or generic exceptions related to decoding
path_viewsstr | Path | NoneNoneDirectory for automatic page discovery (loads pages recursively)
loggerboolFalseEnable detailed logging. If True, Flet internal on_error events will be logged.
use_error_boundaryboolTrueShow a visual error screen with the traceback when rendering fails. Set to False in production.

Pagesy Configurations

ParameterTypeDefaultDescription
routestrRequiredURL pattern for the page
viewCallable | typeRequiredFunction or Class that returns a View (build())
titlestr | NoneNonePage title for browser
indexint | NoneNoneNavigation index for tabs/Navigation Bars
clearboolFalseClear navigation history upon landing
share_databoolFalseEnable data sharing via data.share
protected_routeboolFalseRequire authentication (@app.login)
custom_paramsDict[str, Callable] | NoneNoneCustom parameter validators
middlewaredeque | List | tuple | set | Callable | Type[MiddlewareRequest] | NoneNonePage-specific middlewares executed sequentially
cacheboolFalsePreserve page state (Controls, Inputs, Memory)

Error Handling

Common Exceptions

from flet_easy.exceptions import (
    FletEasyError,
    AddPagesError,
    LoginError,
    LogoutError,
    MiddlewareError
)

try:
    app = fs.FletEasy()
    app.run()
except FletEasyError as e:
    print(f"FletEasy error: {e}")
except Exception as e:
    print(f"Unexpected error: {e}")

Error Handling in Pages

@app.page("/api-demo")
def api_demo_page(data: fs.Datasy):
    try:
        # Your logic here
        result = fetch_api_data()
        return create_success_view(result)
    except Exception as e:
        # Show error to user
        data.page.show_snack_bar(
            ft.SnackBar(content=ft.Text(f"Error: {str(e)}"))
        )
        # Return error view
        return ft.View(
            "/api-demo",
            controls=[
                ft.Text("An error occurred", color=ft.Colors.RED),
                ft.ElevatedButton("Retry", on_click=data.go("/api-demo")),
                ft.ElevatedButton("Go Home", on_click=data.go("/home"))
            ]
        )

Performance Tips

Use Caching Wisely

# Cache expensive pages
@app.page("/reports", cache=True)
def reports_page(data: fs.Datasy):
    # Expensive computation
    return ft.View("/reports", controls=[...])

# Don't cache dynamic content
@app.page("/live-feed", cache=False)
def live_feed_page(data: fs.Datasy):
    # Real-time data
    return ft.View("/live-feed", controls=[...])

Optimize Page Updates

def update_multiple_controls(data: fs.Datasy):
    # Batch updates
    control1.value = "New Value 1"
    control2.value = "New Value 2"
    control3.value = "New Value 3"

    # Single update call
    data.page.update()

Use Lazy Loading

@app.page("/heavy-page")
def heavy_page(data: fs.Datasy):
    # Load heavy content only when needed
    def load_content(_):
        heavy_data = load_heavy_data()
        content_container.content = create_heavy_ui(heavy_data)
        data.page.update()

    content_container = ft.Container()

    return ft.View(
        "/heavy-page",
        controls=[
            ft.ElevatedButton("Load Content", on_click=load_content),
            content_container
        ]
    )

Migration Guide

From v0.1.x to v0.2.x

Breaking Changes:

  • update_loginlogin
  • logautlogout
  • Function parameters changed for login and config_event_handler decorators

Migration Example:

# Old (v0.1.x)
@app.login
def check_auth(page: ft.Page):
    return page.client_storage.get("token") is not None

# New (v0.2.x)
@app.login
def check_auth(data: fs.Datasy):
    return data.page.client_storage.get("token") is not None

This API reference provides comprehensive coverage of all Flet-Easy features. For detailed examples and tutorials, refer to the other sections of this documentation.