Set query_context on chart creation
diff --git a/superset/charts/data/api.py b/superset/charts/data/api.py
index b1d8305..34d3fca 100644
--- a/superset/charts/data/api.py
+++ b/superset/charts/data/api.py
@@ -37,6 +37,7 @@
 )
 from superset.charts.data.query_context_cache_loader import QueryContextCacheLoader
 from superset.charts.data.query_context_sidecar import (
+    DEFAULT_QUERY_CONTEXT_SIDECAR_TIMEOUT,
     fetch_query_context_from_sidecar,
     QueryContextSidecarError,
 )
@@ -78,7 +79,6 @@
 
 logger = logging.getLogger(__name__)
 
-DEFAULT_QUERY_CONTEXT_SIDECAR_TIMEOUT = 30
 MISSING_QUERY_CONTEXT_MESSAGE = (
     "Chart has no query context saved. Please save the chart again."
 )
diff --git a/superset/charts/data/query_context_sidecar.py b/superset/charts/data/query_context_sidecar.py
index c01e44e..e88ddcb 100644
--- a/superset/charts/data/query_context_sidecar.py
+++ b/superset/charts/data/query_context_sidecar.py
@@ -16,15 +16,64 @@
 # under the License.
 from __future__ import annotations
 
+import logging
 from typing import Any
 
 import requests
+from flask import current_app as app
+
+from superset.utils import json
+
+logger = logging.getLogger(__name__)
+
+DEFAULT_QUERY_CONTEXT_SIDECAR_TIMEOUT = 30
 
 
 class QueryContextSidecarError(Exception):
     """Raised when query context cannot be generated via sidecar."""
 
 
+def maybe_generate_query_context(model: Any, params_json: str | None) -> None:
+    """Best-effort generation of query_context via the sidecar service.
+
+    Sets ``model.query_context`` on success.  Failures are logged but never
+    re-raised so chart saves are not blocked.
+    """
+    sidecar_url = app.config.get("QUERY_CONTEXT_SIDECAR_URL")
+    if not sidecar_url or not params_json:
+        return
+
+    try:
+        form_data = json.loads(params_json)
+    except (TypeError, json.JSONDecodeError):
+        logger.warning("Could not parse chart params for sidecar query context")
+        return
+
+    timeout = app.config.get(
+        "QUERY_CONTEXT_SIDECAR_TIMEOUT",
+        DEFAULT_QUERY_CONTEXT_SIDECAR_TIMEOUT,
+    )
+
+    try:
+        result = fetch_query_context_from_sidecar(
+            sidecar_url=sidecar_url,
+            form_data=form_data,
+            timeout=timeout,
+        )
+        model.query_context = json.dumps(result)
+    except QueryContextSidecarError:
+        logger.warning(
+            "Failed to generate query context via sidecar for chart %s",
+            getattr(model, "id", "?"),
+        )
+    except Exception:
+        logger.warning(
+            "Unexpected error generating query context via sidecar for chart %s",
+            getattr(model, "id", "?"),
+            exc_info=True,
+        )
+
+
 def fetch_query_context_from_sidecar(
     *,
     sidecar_url: str,
diff --git a/superset/commands/chart/create.py b/superset/commands/chart/create.py
index 84b3aa2..0f4201a 100644
--- a/superset/commands/chart/create.py
+++ b/superset/commands/chart/create.py
@@ -24,6 +24,7 @@
 from marshmallow import ValidationError
 
 from superset import security_manager
+from superset.charts.data.query_context_sidecar import maybe_generate_query_context
 from superset.commands.base import BaseCommand, CreateMixin
 from superset.commands.chart.exceptions import (
     ChartCreateFailedError,
@@ -48,7 +49,12 @@
         self.validate()
         self._properties["last_saved_at"] = datetime.now()
         self._properties["last_saved_by"] = g.user
-        return ChartDAO.create(attributes=self._properties)
+        chart = ChartDAO.create(attributes=self._properties)
+
+        if not self._properties.get("query_context"):
+            maybe_generate_query_context(chart, self._properties.get("params"))
+
+        return chart
 
     def validate(self) -> None:
         exceptions = []
diff --git a/superset/commands/chart/update.py b/superset/commands/chart/update.py
index 25ca253..a1dbe1c 100644
--- a/superset/commands/chart/update.py
+++ b/superset/commands/chart/update.py
@@ -24,6 +24,7 @@
 from marshmallow import ValidationError
 
 from superset import security_manager
+from superset.charts.data.query_context_sidecar import maybe_generate_query_context
 from superset.commands.base import BaseCommand, UpdateMixin
 from superset.commands.chart.exceptions import (
     ChartForbiddenError,
@@ -70,7 +71,16 @@
             self._properties["last_saved_at"] = datetime.now()
             self._properties["last_saved_by"] = g.user
 
-        return ChartDAO.update(self._model, self._properties)
+        chart = ChartDAO.update(self._model, self._properties)
+
+        if (
+            "params" in self._properties
+            and not self._properties.get("query_context")
+            and not self._properties.get("query_context_generation")
+        ):
+            maybe_generate_query_context(chart, self._properties["params"])
+
+        return chart
 
     def _validate_new_dashboard_access(
         self, requested_dashboards: list[Dashboard], exceptions: list[Exception]
diff --git a/tests/unit_tests/charts/test_query_context_sidecar.py b/tests/unit_tests/charts/test_query_context_sidecar.py
index a2b836c..4ea0f59 100644
--- a/tests/unit_tests/charts/test_query_context_sidecar.py
+++ b/tests/unit_tests/charts/test_query_context_sidecar.py
@@ -16,6 +16,7 @@
 # under the License.
 from __future__ import annotations
 
+from typing import Any
 from unittest import mock
 
 import pytest
@@ -23,6 +24,7 @@
 
 from superset.charts.data.query_context_sidecar import (
     fetch_query_context_from_sidecar,
+    maybe_generate_query_context,
     QueryContextSidecarError,
 )
 
@@ -85,3 +87,128 @@
             form_data={"viz_type": "pie"},
             timeout=15,
         )
+
+
+# ---------------------------------------------------------------------------
+# Tests for maybe_generate_query_context
+# ---------------------------------------------------------------------------
+
+
+class _FakeApp:
+    """Minimal stand-in for the Flask app proxy used by the sidecar module."""
+
+    def __init__(self, config: dict[str, Any] | None = None) -> None:
+        self.config = config or {}
+
+
+@mock.patch(
+    "superset.charts.data.query_context_sidecar.app",
+    new=_FakeApp({}),
+)
+def test_maybe_generate_noop_when_no_sidecar_url() -> None:
+    model = mock.MagicMock()
+    maybe_generate_query_context(model, '{"viz_type": "pie"}')
+
+
+@mock.patch(
+    "superset.charts.data.query_context_sidecar.app",
+    new=_FakeApp({"QUERY_CONTEXT_SIDECAR_URL": "http://sidecar.internal"}),
+)
+def test_maybe_generate_noop_when_params_json_is_none() -> None:
+    model = mock.MagicMock()
+    maybe_generate_query_context(model, None)
+
+
+@mock.patch(
+    "superset.charts.data.query_context_sidecar.fetch_query_context_from_sidecar"
+)
+@mock.patch(
+    "superset.charts.data.query_context_sidecar.app",
+    new=_FakeApp(
+        {
+            "QUERY_CONTEXT_SIDECAR_URL": "http://sidecar.internal",
+            "QUERY_CONTEXT_SIDECAR_TIMEOUT": 10,
+        }
+    ),
+)
+def test_maybe_generate_sets_query_context_on_success(
+    mock_fetch: mock.MagicMock,
+) -> None:
+    mock_fetch.return_value = {"datasource": {"id": 1}, "queries": []}
+    model = mock.MagicMock()
+
+    maybe_generate_query_context(model, '{"viz_type": "pie"}')
+
+    mock_fetch.assert_called_once_with(
+        sidecar_url="http://sidecar.internal",
+        form_data={"viz_type": "pie"},
+        timeout=10,
+    )
+    assert model.query_context is not None
+
+
+@mock.patch(
+    "superset.charts.data.query_context_sidecar.fetch_query_context_from_sidecar"
+)
+@mock.patch(
+    "superset.charts.data.query_context_sidecar.app",
+    new=_FakeApp(
+        {
+            "QUERY_CONTEXT_SIDECAR_URL": "http://sidecar.internal",
+            "QUERY_CONTEXT_SIDECAR_TIMEOUT": 10,
+        }
+    ),
+)
+def test_maybe_generate_logs_on_sidecar_error(
+    mock_fetch: mock.MagicMock,
+    caplog: pytest.LogCaptureFixture,
+) -> None:
+    mock_fetch.side_effect = QueryContextSidecarError("boom")
+    model = mock.MagicMock()
+    model.id = 42
+
+    with caplog.at_level("WARNING"):
+        maybe_generate_query_context(model, '{"viz_type": "pie"}')
+
+    assert "Failed to generate query context" in caplog.text
+
+
+@mock.patch(
+    "superset.charts.data.query_context_sidecar.app",
+    new=_FakeApp({"QUERY_CONTEXT_SIDECAR_URL": "http://sidecar.internal"}),
+)
+def test_maybe_generate_logs_on_invalid_json(
+    caplog: pytest.LogCaptureFixture,
+) -> None:
+    model = mock.MagicMock()
+
+    with caplog.at_level("WARNING"):
+        maybe_generate_query_context(model, "not-valid-json{{{")
+
+    assert "Could not parse chart params" in caplog.text
+
+
+@mock.patch(
+    "superset.charts.data.query_context_sidecar.fetch_query_context_from_sidecar"
+)
+@mock.patch(
+    "superset.charts.data.query_context_sidecar.app",
+    new=_FakeApp(
+        {
+            "QUERY_CONTEXT_SIDECAR_URL": "http://sidecar.internal",
+            "QUERY_CONTEXT_SIDECAR_TIMEOUT": 10,
+        }
+    ),
+)
+def test_maybe_generate_logs_on_unexpected_error(
+    mock_fetch: mock.MagicMock,
+    caplog: pytest.LogCaptureFixture,
+) -> None:
+    mock_fetch.side_effect = RuntimeError("unexpected")
+    model = mock.MagicMock()
+    model.id = 99
+
+    with caplog.at_level("WARNING"):
+        maybe_generate_query_context(model, '{"viz_type": "pie"}')
+
+    assert "Unexpected error" in caplog.text