From 7de3f911ed217950bdc33103e30d1027c6a671b8 Mon Sep 17 00:00:00 2001 From: AnnMarueW Date: Sat, 26 Sep 2026 07:44:49 -0700 Subject: [PATCH 1/3] Extend include_pages_meta to regular apps --- dash/dash.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dash/dash.py b/dash/dash.py index ed1ed5ebc8..a39606f283 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -425,7 +425,7 @@ class Dash(ObsoleteChecker): `routing_callback_inputs={"language": Input("language", "value")}` NOTE: the keys "pathname_" and "search_" are reserved for internal use. - :param description: Sets a default description for meta tags on Dash pages (use_pages=True). + :param description: Sets a default description for meta tags. :param on_error: Global callback error handler to call when an exception is raised. Receives the exception object as first argument. @@ -491,7 +491,7 @@ def __init__( # pylint: disable=too-many-statements, too-many-branches assets_external_path: Optional[str] = None, eager_loading: bool = False, include_assets_files: bool = True, - include_pages_meta: bool = True, + include_pages_meta: Optional[bool] = None, url_base_pathname: Optional[str] = None, requests_pathname_prefix: Optional[str] = None, routes_pathname_prefix: Optional[str] = None, @@ -641,6 +641,9 @@ def __init__( # pylint: disable=too-many-statements, too-many-branches self.pages_folder = str(pages_folder) self.use_pages = (pages_folder != "pages") if use_pages is None else use_pages + if self.include_pages_meta is None: + self.include_pages_meta = self.use_pages + self.routing_callback_inputs = routing_callback_inputs or {} # keep title as a class property for backwards compatibility From bab9d1f2c3d9aa3e9f71ee6671c8ea12475ff069 Mon Sep 17 00:00:00 2001 From: AnnMarueW Date: Tue, 29 Sep 2026 15:45:26 -0700 Subject: [PATCH 2/3] Support social meta tags for regular apps --- dash/_pages.py | 51 +++++++++++++++++++++++++++++++++----------------- dash/dash.py | 45 ++++++++++++++++++++++++-------------------- 2 files changed, 59 insertions(+), 37 deletions(-) diff --git a/dash/_pages.py b/dash/_pages.py index be9d847309..89ed1fb3d1 100644 --- a/dash/_pages.py +++ b/dash/_pages.py @@ -381,23 +381,40 @@ def _path_to_page(path_id): def _page_meta_tags(app, request): - request_path = request.path - start_page, path_variables = _path_to_page(request_path.strip("/")) - - image = start_page.get("image", "") - if image: - image = app.get_asset_url(image) - assets_image_url = "".join([request.root, image.lstrip("/")]) if image else None - supplied_image_url = start_page.get("image_url") - image_url = supplied_image_url if supplied_image_url else assets_image_url - - title = start_page.get("title", app.title) - if callable(title): - title = title(**path_variables) if path_variables else title() - - description = start_page.get("description", "") - if callable(description): - description = description(**path_variables) if path_variables else description() + if not app.use_pages and not (app.description or app.image): + return [] + + title = app.title + description = app.description or "" + image = app.image + supplied_image_url = None + + if app.use_pages: + request_path = request.path + start_page, path_variables = _path_to_page(request_path.strip("/")) + + if start_page: + title = start_page.get("title", title) + description = start_page.get("description", description) + image = start_page.get("image") or image + supplied_image_url = start_page.get("image_url") + + if callable(title): + title = title(**path_variables) if path_variables else title() + + if callable(description): + description = description(**path_variables) if path_variables else description() + + if supplied_image_url: + image_url = supplied_image_url + elif image: + if image.startswith(("http://", "https://")): + image_url = image + else: + image = app.get_asset_url(image) + image_url = "".join([request.root, image.lstrip("/")]) + else: + image_url = None return [ {"name": "description", "content": description}, diff --git a/dash/dash.py b/dash/dash.py index a39606f283..8e25108455 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -284,9 +284,23 @@ class Dash(ObsoleteChecker): to be True. Default `None`. :type use_pages: boolean - :param include_pages_meta: Include the page meta tags for twitter cards. + :param include_pages_meta: Include the meta tags for social media cards. :type include_pages_meta: bool + :param title: Default ``Dash``. Configures the document.title + (the text that appears in a browser tab). + + :param update_title: Default ``Updating...``. Configures the document.title + (the text that appears in a browser tab) text when a callback is being run. + Set to None or '' if you don't want the document.title to change or if you + want to control the document.title through a separate component or + clientside callback. + + :param description: Sets a default description for meta tags. + + :param image: The default image used for social media cards. Can be the + name of an image in the assets folder or an image URL. + :param assets_url_path: The local urls for assets will be: ``requests_pathname_prefix + assets_url_path + '/' + asset_path`` where ``asset_path`` is the path to a file inside ``assets_folder``. @@ -399,15 +413,6 @@ class Dash(ObsoleteChecker): be called after the Flask server is attached. :type plugins: list of objects - :param title: Default ``Dash``. Configures the document.title - (the text that appears in a browser tab). - - :param update_title: Default ``Updating...``. Configures the document.title - (the text that appears in a browser tab) text when a callback is being run. - Set to None or '' if you don't want the document.title to change or if you - want to control the document.title through a separate component or - clientside callback. - :param background_callback_manager: Background callback manager instance to support the ``@callback(..., background=True)`` decorator. One of ``DiskcacheManager`` or ``CeleryManager`` currently supported. @@ -425,8 +430,6 @@ class Dash(ObsoleteChecker): `routing_callback_inputs={"language": Input("language", "value")}` NOTE: the keys "pathname_" and "search_" are reserved for internal use. - :param description: Sets a default description for meta tags. - :param on_error: Global callback error handler to call when an exception is raised. Receives the exception object as first argument. The callback_context can be used to access the original callback inputs, @@ -491,7 +494,11 @@ def __init__( # pylint: disable=too-many-statements, too-many-branches assets_external_path: Optional[str] = None, eager_loading: bool = False, include_assets_files: bool = True, - include_pages_meta: Optional[bool] = None, + include_pages_meta: bool = True, + title: str = "Dash", + update_title: str = "Updating...", + description: Optional[str] = None, + image: Optional[str] = None, url_base_pathname: Optional[str] = None, requests_pathname_prefix: Optional[str] = None, routes_pathname_prefix: Optional[str] = None, @@ -506,15 +513,12 @@ def __init__( # pylint: disable=too-many-statements, too-many-branches show_undo_redo: bool = False, extra_hot_reload_paths: Optional[Sequence[str]] = None, plugins: Optional[list] = None, - title: str = "Dash", - update_title: str = "Updating...", background_callback_manager: Optional[ Any ] = None, # Type should be specified if possible add_log_handler: bool = True, hooks: Optional[RendererHooks] = None, routing_callback_inputs: Optional[Dict[str, Union[Input, State]]] = None, - description: Optional[str] = None, on_error: Optional[Callable[[Exception], Any]] = None, use_async: Optional[bool] = None, health_endpoint: Optional[str] = None, @@ -612,6 +616,7 @@ def __init__( # pylint: disable=too-many-statements, too-many-branches update_title=update_title, include_pages_meta=include_pages_meta, description=description, + image=image, health_endpoint=health_endpoint, hide_all_callbacks=False, csrf_token_name=csrf_token_name, @@ -641,13 +646,12 @@ def __init__( # pylint: disable=too-many-statements, too-many-branches self.pages_folder = str(pages_folder) self.use_pages = (pages_folder != "pages") if use_pages is None else use_pages - if self.include_pages_meta is None: - self.include_pages_meta = self.use_pages - self.routing_callback_inputs = routing_callback_inputs or {} # keep title as a class property for backwards compatibility self.title = title + self.description = description + self.image = image # MCP (Model Context Protocol) configuration self._enable_mcp = get_combined_config("mcp_enabled", enable_mcp, False) @@ -1487,7 +1491,8 @@ def index(self, *_args, **_kwargs): # Refactored: direct access to global request adapter request = self.backend.request_adapter() - if self.use_pages and self.config.include_pages_meta and request: + # include social media meta tags + if self.config.include_pages_meta and request: metas = _page_meta_tags(self, request) + metas if self._favicon: From 60e60efa1fa3bd0c18c7609b095dcc0a40498ec9 Mon Sep 17 00:00:00 2001 From: AnnMarueW Date: Wed, 30 Sep 2026 07:30:27 -0700 Subject: [PATCH 3/3] lint --- dash/_pages.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/dash/_pages.py b/dash/_pages.py index 89ed1fb3d1..8eaa258d75 100644 --- a/dash/_pages.py +++ b/dash/_pages.py @@ -380,6 +380,20 @@ def _path_to_page(path_id): return {}, None +def _get_image_url(app, request, image, supplied_image_url): + if supplied_image_url: + return supplied_image_url + + if image: + if image.startswith(("http://", "https://")): + return image + + image = app.get_asset_url(image) + return "".join([request.root, image.lstrip("/")]) + + return None + + def _page_meta_tags(app, request): if not app.use_pages and not (app.description or app.image): return [] @@ -403,18 +417,11 @@ def _page_meta_tags(app, request): title = title(**path_variables) if path_variables else title() if callable(description): - description = description(**path_variables) if path_variables else description() + description = ( + description(**path_variables) if path_variables else description() + ) - if supplied_image_url: - image_url = supplied_image_url - elif image: - if image.startswith(("http://", "https://")): - image_url = image - else: - image = app.get_asset_url(image) - image_url = "".join([request.root, image.lstrip("/")]) - else: - image_url = None + image_url = _get_image_url(app, request, image, supplied_image_url) return [ {"name": "description", "content": description},