You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/08/15 05:07:31 UTC

(superset) branch master updated: feat(embedded): Add setDataMask API method (#35754)

This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 69c4011a95a feat(embedded): Add setDataMask API method (#35754)
69c4011a95a is described below

commit 69c4011a95a5d2fbdbb5d360b188c313be06e17d
Author: Michael Gerber <mi...@nxt.engineering>
AuthorDate: Sat Aug 15 07:07:24 2026 +0200

    feat(embedded): Add setDataMask API method (#35754)
    
    Co-authored-by: Evan Rusackas <ev...@preset.io>
---
 superset-embedded-sdk/package-lock.json    | 506 +++++++++++++++++++++++++++++
 superset-embedded-sdk/package.json         |   1 +
 superset-embedded-sdk/src/index.test.ts    | 213 ++++++++++++
 superset-embedded-sdk/src/index.ts         |  22 ++
 superset-frontend/src/embedded/api.test.ts | 155 +++++++++
 superset-frontend/src/embedded/api.tsx     |  57 ++++
 superset-frontend/src/embedded/index.tsx   |   1 +
 7 files changed, 955 insertions(+)

diff --git a/superset-embedded-sdk/package-lock.json b/superset-embedded-sdk/package-lock.json
index 11c654b42b6..41ee4e641dd 100644
--- a/superset-embedded-sdk/package-lock.json
+++ b/superset-embedded-sdk/package-lock.json
@@ -19,6 +19,7 @@
         "@babel/preset-typescript": "7.29.7",
         "@types/node": "^25.4.0",
         "babel-loader": "^9.1.3",
+        "jsdom": "^26.1.0",
         "tscw-config": "^1.1.2",
         "typescript": "^5.9.3",
         "vitest": "^4.0.18",
@@ -26,6 +27,27 @@
         "webpack-cli": "^5.1.4"
       }
     },
+    "node_modules/@asamuzakjp/css-color": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
+      "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@csstools/css-calc": "^2.1.3",
+        "@csstools/css-color-parser": "^3.0.9",
+        "@csstools/css-parser-algorithms": "^3.0.4",
+        "@csstools/css-tokenizer": "^3.0.3",
+        "lru-cache": "^10.4.3"
+      }
+    },
+    "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
+      "version": "10.4.3",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+      "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+      "dev": true,
+      "license": "ISC"
+    },
     "node_modules/@babel/cli": {
       "version": "7.29.7",
       "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.29.7.tgz",
@@ -1656,6 +1678,121 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@csstools/color-helpers": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+      "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/csstools"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/csstools"
+        }
+      ],
+      "license": "MIT-0",
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@csstools/css-calc": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+      "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/csstools"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/csstools"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@csstools/css-parser-algorithms": "^3.0.5",
+        "@csstools/css-tokenizer": "^3.0.4"
+      }
+    },
+    "node_modules/@csstools/css-color-parser": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+      "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/csstools"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/csstools"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@csstools/color-helpers": "^5.1.0",
+        "@csstools/css-calc": "^2.1.4"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@csstools/css-parser-algorithms": "^3.0.5",
+        "@csstools/css-tokenizer": "^3.0.4"
+      }
+    },
+    "node_modules/@csstools/css-parser-algorithms": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+      "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/csstools"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/csstools"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@csstools/css-tokenizer": "^3.0.4"
+      }
+    },
+    "node_modules/@csstools/css-tokenizer": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+      "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/csstools"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/csstools"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/@discoveryjs/json-ext": {
       "version": "0.5.6",
       "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz",
@@ -2501,6 +2638,16 @@
         "acorn": "^8.14.0"
       }
     },
+    "node_modules/agent-base": {
+      "version": "7.1.4",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+      "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/ajv": {
       "version": "8.20.0",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
@@ -2868,6 +3015,34 @@
         "node": ">= 8"
       }
     },
+    "node_modules/cssstyle": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
+      "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@asamuzakjp/css-color": "^3.2.0",
+        "rrweb-cssom": "^0.8.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/data-urls": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+      "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-mimetype": "^4.0.0",
+        "whatwg-url": "^14.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/debug": {
       "version": "4.4.3",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -2886,6 +3061,13 @@
         }
       }
     },
+    "node_modules/decimal.js": {
+      "version": "10.6.0",
+      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+      "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/detect-libc": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -2915,6 +3097,19 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/entities": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+      "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
     "node_modules/envinfo": {
       "version": "7.13.0",
       "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz",
@@ -3316,6 +3511,60 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/html-encoding-sniffer": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+      "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-encoding": "^3.1.1"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/http-proxy-agent": {
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+      "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "agent-base": "^7.1.0",
+        "debug": "^4.3.4"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/https-proxy-agent": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+      "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "agent-base": "^7.1.2",
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/import-local": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz",
@@ -3438,6 +3687,13 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-potential-custom-element-name": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+      "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/isexe": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -3498,6 +3754,46 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/jsdom": {
+      "version": "26.1.0",
+      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
+      "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "cssstyle": "^4.2.1",
+        "data-urls": "^5.0.0",
+        "decimal.js": "^10.5.0",
+        "html-encoding-sniffer": "^4.0.0",
+        "http-proxy-agent": "^7.0.2",
+        "https-proxy-agent": "^7.0.6",
+        "is-potential-custom-element-name": "^1.0.1",
+        "nwsapi": "^2.2.16",
+        "parse5": "^7.2.1",
+        "rrweb-cssom": "^0.8.0",
+        "saxes": "^6.0.0",
+        "symbol-tree": "^3.2.4",
+        "tough-cookie": "^5.1.1",
+        "w3c-xmlserializer": "^5.0.0",
+        "webidl-conversions": "^7.0.0",
+        "whatwg-encoding": "^3.1.1",
+        "whatwg-mimetype": "^4.0.0",
+        "whatwg-url": "^14.1.1",
+        "ws": "^8.18.0",
+        "xml-name-validator": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "canvas": "^3.0.0"
+      },
+      "peerDependenciesMeta": {
+        "canvas": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/jsesc": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -3977,6 +4273,13 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/nwsapi": {
+      "version": "2.2.24",
+      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz",
+      "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/obug": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
@@ -4033,6 +4336,19 @@
         "node": ">=6"
       }
     },
+    "node_modules/parse5": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+      "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "entities": "^6.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/inikulin/parse5?sponsor=1"
+      }
+    },
     "node_modules/path-exists": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -4142,6 +4458,16 @@
         "node": "^10 || ^12 || >=14"
       }
     },
+    "node_modules/punycode": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/readdirp": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -4314,6 +4640,33 @@
         "@rolldown/binding-win32-x64-msvc": "1.1.3"
       }
     },
+    "node_modules/rrweb-cssom": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+      "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/saxes": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+      "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "xmlchars": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=v12.22.7"
+      }
+    },
     "node_modules/schema-utils": {
       "version": "4.3.3",
       "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
@@ -4447,6 +4800,13 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/symbol-tree": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+      "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/tapable": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
@@ -4592,6 +4952,26 @@
         "node": ">=14.0.0"
       }
     },
+    "node_modules/tldts": {
+      "version": "6.1.86",
+      "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
+      "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "tldts-core": "^6.1.86"
+      },
+      "bin": {
+        "tldts": "bin/cli.js"
+      }
+    },
+    "node_modules/tldts-core": {
+      "version": "6.1.86",
+      "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
+      "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/to-regex-range": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -4605,6 +4985,32 @@
         "node": ">=8.0"
       }
     },
+    "node_modules/tough-cookie": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
+      "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "tldts": "^6.1.32"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/tr46": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+      "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "punycode": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/tscw-config": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/tscw-config/-/tscw-config-1.1.2.tgz",
@@ -4931,6 +5337,19 @@
         "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
+    "node_modules/w3c-xmlserializer": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+      "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "xml-name-validator": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/watchpack": {
       "version": "2.5.1",
       "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
@@ -4944,6 +5363,16 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/webidl-conversions": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+      "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/webpack": {
       "version": "5.105.0",
       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz",
@@ -5070,6 +5499,44 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/whatwg-encoding": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+      "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+      "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "iconv-lite": "0.6.3"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/whatwg-mimetype": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+      "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/whatwg-url": {
+      "version": "14.2.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+      "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "tr46": "^5.1.0",
+        "webidl-conversions": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/which": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -5114,6 +5581,45 @@
       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
       "dev": true
     },
+    "node_modules/ws": {
+      "version": "8.21.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
+      "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/xml-name-validator": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+      "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/xmlchars": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+      "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/yallist": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
diff --git a/superset-embedded-sdk/package.json b/superset-embedded-sdk/package.json
index 4ae91a6a9ff..c6b05960189 100644
--- a/superset-embedded-sdk/package.json
+++ b/superset-embedded-sdk/package.json
@@ -43,6 +43,7 @@
     "@babel/preset-typescript": "7.29.7",
     "@types/node": "^25.4.0",
     "babel-loader": "^9.1.3",
+    "jsdom": "^26.1.0",
     "tscw-config": "^1.1.2",
     "typescript": "^5.9.3",
     "vitest": "^4.0.18",
diff --git a/superset-embedded-sdk/src/index.test.ts b/superset-embedded-sdk/src/index.test.ts
new file mode 100644
index 00000000000..ef85128a8aa
--- /dev/null
+++ b/superset-embedded-sdk/src/index.test.ts
@@ -0,0 +1,213 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// @vitest-environment jsdom
+
+import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
+import { Switchboard } from "@superset-ui/switchboard";
+import { embedDashboard } from "./index";
+
+vi.mock("@superset-ui/switchboard");
+
+function makeFakeJWT(claims: any) {
+  // not a valid jwt, but close enough for this code
+  const tokenifiedClaims = Buffer.from(JSON.stringify(claims)).toString(
+    "base64",
+  );
+  return `abc.${tokenifiedClaims}.xyz`;
+}
+
+describe("embedDashboard", () => {
+  let mountPoint: HTMLElement;
+  let mockSwitchboard: Switchboard;
+
+  beforeEach(() => {
+    mountPoint = document.createElement("div");
+    document.body.appendChild(mountPoint);
+
+    mockSwitchboard = {
+      emit: vi.fn(),
+      get: vi.fn(),
+      start: vi.fn(),
+      defineMethod: vi.fn(),
+    } as any;
+
+    // Constructor mocks must use `function`, since arrow functions cannot be
+    // invoked with `new`.
+    vi.mocked(Switchboard).mockImplementation(function () {
+      return mockSwitchboard;
+    } as any);
+
+    // Mock MessageChannel API
+    globalThis.MessageChannel = vi.fn(function (this: any) {
+      this.port1 = {};
+      this.port2 = {};
+    }) as any;
+
+    // Mock iframe load event and sandbox
+    const originalCreateElement = document.createElement.bind(document);
+    vi.spyOn(document, "createElement").mockImplementation(tagName => {
+      const element = originalCreateElement(tagName);
+      if (tagName === "iframe") {
+        // Mock sandbox DOMTokenList
+        (element as any).sandbox = {
+          add: vi.fn(),
+        };
+        // Mock contentWindow for postMessage
+        Object.defineProperty(element, "contentWindow", {
+          writable: true,
+          value: {
+            postMessage: vi.fn(),
+          },
+        });
+        setTimeout(() => {
+          element.dispatchEvent(new Event("load"));
+        }, 0);
+      }
+      return element;
+    });
+  });
+
+  afterEach(() => {
+    document.body.removeChild(mountPoint);
+    vi.restoreAllMocks();
+  });
+
+  test("setDataMask sends dataMask to iframe", async () => {
+    const fakeToken = makeFakeJWT({ exp: Date.now() / 1000 + 300 });
+    const mockFetchGuestToken = vi.fn().mockResolvedValue(fakeToken);
+    const testDataMask = {
+      "NATIVE_FILTER-1": {
+        filterState: {
+          value: ["value1", "value2"],
+        },
+      },
+    };
+
+    const dashboard = await embedDashboard({
+      id: "test-id",
+      supersetDomain: "https://superset.example.com",
+      mountPoint,
+      fetchGuestToken: mockFetchGuestToken,
+    });
+
+    dashboard.setDataMask(testDataMask);
+
+    expect(mockSwitchboard.get).toHaveBeenCalledWith("setDataMask", {
+      dataMask: testDataMask,
+    });
+  });
+
+  test("setDataMask sends empty dataMask", async () => {
+    const fakeToken = makeFakeJWT({ exp: Date.now() / 1000 + 300 });
+    const mockFetchGuestToken = vi.fn().mockResolvedValue(fakeToken);
+    const emptyDataMask = {};
+
+    const dashboard = await embedDashboard({
+      id: "test-id",
+      supersetDomain: "https://superset.example.com",
+      mountPoint,
+      fetchGuestToken: mockFetchGuestToken,
+    });
+
+    dashboard.setDataMask(emptyDataMask);
+
+    expect(mockSwitchboard.get).toHaveBeenCalledWith("setDataMask", {
+      dataMask: emptyDataMask,
+    });
+  });
+
+  test("setDataMask drops the change-trigger flags observeDataMask adds", async () => {
+    const fakeToken = makeFakeJWT({ exp: Date.now() / 1000 + 300 });
+    const mockFetchGuestToken = vi.fn().mockResolvedValue(fakeToken);
+    const observedMask = {
+      "NATIVE_FILTER-1": {
+        filterState: {
+          value: ["CA"],
+        },
+      },
+      crossFiltersChanged: false,
+      nativeFiltersChanged: true,
+    };
+
+    const dashboard = await embedDashboard({
+      id: "test-id",
+      supersetDomain: "https://superset.example.com",
+      mountPoint,
+      fetchGuestToken: mockFetchGuestToken,
+    });
+
+    dashboard.setDataMask(observedMask);
+
+    expect(mockSwitchboard.get).toHaveBeenCalledWith("setDataMask", {
+      dataMask: {
+        "NATIVE_FILTER-1": observedMask["NATIVE_FILTER-1"],
+      },
+    });
+  });
+
+  test("setDataMask sends complex dataMask with multiple filters", async () => {
+    const fakeToken = makeFakeJWT({ exp: Date.now() / 1000 + 300 });
+    const mockFetchGuestToken = vi.fn().mockResolvedValue(fakeToken);
+    const complexDataMask = {
+      "NATIVE_FILTER-1": {
+        filterState: {
+          value: ["CA", "NY"],
+        },
+      },
+      "NATIVE_FILTER-2": {
+        filterState: {
+          value: [2023, 2024],
+        },
+      },
+    };
+
+    const dashboard = await embedDashboard({
+      id: "test-id",
+      supersetDomain: "https://superset.example.com",
+      mountPoint,
+      fetchGuestToken: mockFetchGuestToken,
+    });
+
+    dashboard.setDataMask(complexDataMask);
+
+    expect(mockSwitchboard.get).toHaveBeenCalledWith("setDataMask", {
+      dataMask: complexDataMask,
+    });
+  });
+
+  test("setDataMask rejects when the embedded page does not support it", async () => {
+    const fakeToken = makeFakeJWT({ exp: Date.now() / 1000 + 300 });
+    const mockFetchGuestToken = vi.fn().mockResolvedValue(fakeToken);
+    vi.mocked(mockSwitchboard.get).mockRejectedValue(
+      new Error('Method "setDataMask" is not defined'),
+    );
+
+    const dashboard = await embedDashboard({
+      id: "test-id",
+      supersetDomain: "https://superset.example.com",
+      mountPoint,
+      fetchGuestToken: mockFetchGuestToken,
+    });
+
+    await expect(dashboard.setDataMask({})).rejects.toThrow(
+      'Method "setDataMask" is not defined',
+    );
+  });
+});
diff --git a/superset-embedded-sdk/src/index.ts b/superset-embedded-sdk/src/index.ts
index 07aeb500b13..7c9cf9d4ec2 100644
--- a/superset-embedded-sdk/src/index.ts
+++ b/superset-embedded-sdk/src/index.ts
@@ -115,6 +115,12 @@ export type EmbeddedDashboard = {
   getActiveTabs: () => Promise<string[]>;
   observeDataMask: (callbackFn: ObserveDataMaskCallbackFn) => void;
   getDataMask: () => Promise<Record<string, any>>;
+  /**
+   * Applies a data mask to the dashboard.
+   * Rejects if the embedded Superset page does not support `setDataMask`,
+   * so a version mismatch surfaces instead of silently doing nothing.
+   */
+  setDataMask: (dataMask: Record<string, any>) => Promise<void>;
   getChartStates: () => Promise<Record<string, any>>;
   getChartDataPayloads: (params?: {
     chartId?: number;
@@ -355,6 +361,21 @@ export async function embedDashboard({
     ourPort.get<string>("getDashboardPermalink", { anchor });
   const getActiveTabs = () => ourPort.get<string[]>("getActiveTabs");
   const getDataMask = () => ourPort.get<Record<string, any>>("getDataMask");
+  // `observeDataMask` hands the host a mask with the change-trigger booleans
+  // mixed in, so feeding that payload straight back into `setDataMask` is a
+  // natural thing for a host to do. Keep only the entries that look like a
+  // filter's mask, so those flags never reach the dashboard as filter ids.
+  // Sent with `get` rather than `emit` so the iframe acknowledges the call:
+  // an embedded page that predates `setDataMask` replies with an error instead
+  // of dropping the message silently.
+  const setDataMask = (dataMask: Record<string, any>) =>
+    ourPort.get<void>("setDataMask", {
+      dataMask: Object.fromEntries(
+        Object.entries(dataMask).filter(
+          ([, mask]) => typeof mask === "object" && mask !== null,
+        ),
+      ),
+    });
   const getChartStates = () =>
     ourPort.get<Record<string, any>>("getChartStates");
   const getChartDataPayloads = (params?: { chartId?: number }) =>
@@ -396,6 +417,7 @@ export async function embedDashboard({
     getActiveTabs,
     observeDataMask,
     getDataMask,
+    setDataMask,
     getChartStates,
     getChartDataPayloads,
     setThemeConfig,
diff --git a/superset-frontend/src/embedded/api.test.ts b/superset-frontend/src/embedded/api.test.ts
new file mode 100644
index 00000000000..e85a3b48185
--- /dev/null
+++ b/superset-frontend/src/embedded/api.test.ts
@@ -0,0 +1,155 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { DataMaskStateWithId } from '@superset-ui/core';
+
+// Mock factories must build their own jest.fn()s: jest.mock calls are hoisted
+// above this file's declarations, so a factory closing over a const would read
+// it before initialization.
+jest.mock('@apache-superset/core/utils', () => ({
+  logging: { debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
+}));
+
+jest.mock('../views/store', () => ({
+  store: { dispatch: jest.fn(), getState: jest.fn(), subscribe: jest.fn() },
+}));
+
+// eslint-disable-next-line import/first
+import { embeddedApi } from './api';
+// eslint-disable-next-line import/first
+import { updateDataMask } from '../dataMask/actions';
+
+const { logging: mockLogging } = jest.requireMock(
+  '@apache-superset/core/utils',
+);
+const { store: mockStore } = jest.requireMock('../views/store');
+const mockDispatch = mockStore.dispatch;
+const mockGetState = mockStore.getState;
+
+const nativeFilterMask = { filterState: { value: ['CA'] } };
+const crossFilterMask = { filterState: { value: [2024] } };
+
+// `dashboardInfo.id` is only set once HYDRATE_DASHBOARD lands, so it doubles as
+// the "dashboard is hydrated" signal setDataMask waits for.
+function stateWithFilters(filterIds: string[]) {
+  return {
+    dashboardInfo: { id: 1 },
+    dataMask: Object.fromEntries(filterIds.map(id => [id, { id }])),
+  };
+}
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+test('setDataMask dispatches an update for each known filter', () => {
+  mockGetState.mockReturnValue(
+    stateWithFilters(['NATIVE_FILTER-1', 'NATIVE_FILTER-2']),
+  );
+
+  embeddedApi.setDataMask({
+    dataMask: {
+      'NATIVE_FILTER-1': nativeFilterMask,
+      'NATIVE_FILTER-2': crossFilterMask,
+    } as unknown as DataMaskStateWithId,
+  });
+
+  expect(mockDispatch).toHaveBeenCalledTimes(2);
+  expect(mockDispatch).toHaveBeenCalledWith(
+    updateDataMask('NATIVE_FILTER-1', nativeFilterMask),
+  );
+  expect(mockDispatch).toHaveBeenCalledWith(
+    updateDataMask('NATIVE_FILTER-2', crossFilterMask),
+  );
+  expect(mockLogging.warn).not.toHaveBeenCalled();
+});
+
+test('setDataMask ignores filter ids the dashboard does not know', () => {
+  mockGetState.mockReturnValue(stateWithFilters(['NATIVE_FILTER-1']));
+
+  embeddedApi.setDataMask({
+    dataMask: {
+      'NATIVE_FILTER-1': nativeFilterMask,
+      'NATIVE_FILTER-from-another-dashboard': crossFilterMask,
+    } as unknown as DataMaskStateWithId,
+  });
+
+  expect(mockDispatch).toHaveBeenCalledTimes(1);
+  expect(mockDispatch).toHaveBeenCalledWith(
+    updateDataMask('NATIVE_FILTER-1', nativeFilterMask),
+  );
+  expect(mockLogging.warn).toHaveBeenCalledWith(
+    expect.stringContaining('unknown filter ids'),
+    'NATIVE_FILTER-from-another-dashboard',
+  );
+});
+
+test('setDataMask ignores the change-trigger flags observeDataMask emits', () => {
+  mockGetState.mockReturnValue(stateWithFilters(['NATIVE_FILTER-1']));
+
+  embeddedApi.setDataMask({
+    dataMask: {
+      'NATIVE_FILTER-1': nativeFilterMask,
+      crossFiltersChanged: false,
+      nativeFiltersChanged: true,
+    } as unknown as DataMaskStateWithId,
+  });
+
+  expect(mockDispatch).toHaveBeenCalledTimes(1);
+  expect(mockDispatch).toHaveBeenCalledWith(
+    updateDataMask('NATIVE_FILTER-1', nativeFilterMask),
+  );
+});
+
+test('setDataMask dispatches nothing when no filter id is known', () => {
+  mockGetState.mockReturnValue(stateWithFilters([]));
+
+  embeddedApi.setDataMask({
+    dataMask: {
+      'NATIVE_FILTER-1': nativeFilterMask,
+    } as unknown as DataMaskStateWithId,
+  });
+
+  expect(mockDispatch).not.toHaveBeenCalled();
+  expect(mockLogging.warn).toHaveBeenCalled();
+});
+
+test('setDataMask queues the mask until the dashboard hydrates', () => {
+  let notifyStoreSubscribers = () => {};
+  mockStore.subscribe.mockImplementation((listener: () => void) => {
+    notifyStoreSubscribers = listener;
+    return jest.fn();
+  });
+  mockGetState.mockReturnValue({ dataMask: {} });
+
+  embeddedApi.setDataMask({
+    dataMask: {
+      'NATIVE_FILTER-1': nativeFilterMask,
+    } as unknown as DataMaskStateWithId,
+  });
+
+  expect(mockDispatch).not.toHaveBeenCalled();
+  expect(mockLogging.warn).not.toHaveBeenCalled();
+
+  mockGetState.mockReturnValue(stateWithFilters(['NATIVE_FILTER-1']));
+  notifyStoreSubscribers();
+
+  expect(mockDispatch).toHaveBeenCalledWith(
+    updateDataMask('NATIVE_FILTER-1', nativeFilterMask),
+  );
+});
diff --git a/superset-frontend/src/embedded/api.tsx b/superset-frontend/src/embedded/api.tsx
index 8c0fb711ccc..284d2fdcdfd 100644
--- a/superset-frontend/src/embedded/api.tsx
+++ b/superset-frontend/src/embedded/api.tsx
@@ -17,12 +17,15 @@
  * under the License.
  */
 import { DataMaskStateWithId, JsonObject } from '@superset-ui/core';
+import { logging } from '@apache-superset/core/utils';
 import getBootstrapData from 'src/utils/getBootstrapData';
+import { batch } from 'react-redux';
 import { store } from '../views/store';
 import { getDashboardPermalink as getDashboardPermalinkUtil } from '../utils/urlUtils';
 import { DashboardChartStates } from '../dashboard/types/chartState';
 import { hasStatefulCharts } from '../dashboard/util/chartStateConverter';
 import { getChartDataPayloads as getChartDataPayloadsUtil } from './utils';
+import { updateDataMask } from '../dataMask/actions';
 
 const bootstrapData = getBootstrapData();
 
@@ -40,6 +43,7 @@ type EmbeddedSupersetApi = {
   getChartDataPayloads: (params?: {
     chartId?: number;
   }) => Promise<Record<string, JsonObject>>;
+  setDataMask: ({ dataMask }: { dataMask: DataMaskStateWithId }) => void;
 };
 
 const getScrollSize = (): Size => ({
@@ -83,6 +87,58 @@ const getActiveTabs = () => store?.getState()?.dashboardState?.activeTabs || [];
 
 const getDataMask = () => store?.getState()?.dataMask || {};
 
+const isDashboardHydrated = () => Boolean(store?.getState()?.dashboardInfo?.id);
+
+const applyDataMask = (dataMask: DataMaskStateWithId) => {
+  // The dashboard's own data mask holds an entry for every native filter and
+  // every cross-filter-capable chart, so it doubles as the set of filter ids
+  // this dashboard can accept. Anything else — a filter id from a different
+  // dashboard, or the change-trigger flags that `observeDataMask` emits
+  // alongside the mask — would otherwise be inserted as a bogus filter and
+  // treated as a globally scoped filter by the active-filter derivation.
+  const knownFilterIds = new Set(Object.keys(getDataMask()));
+  const entries = Object.entries(dataMask);
+  const applicable = entries.filter(([id]) => knownFilterIds.has(id));
+  const ignored = entries.filter(([id]) => !knownFilterIds.has(id));
+
+  if (ignored.length) {
+    logging.warn(
+      '[superset] setDataMask ignored unknown filter ids:',
+      ignored.map(([id]) => id).join(', '),
+    );
+  }
+
+  batch(() => {
+    applicable.forEach(([filterId, mask]) => {
+      store?.dispatch(updateDataMask(filterId, mask));
+    });
+  });
+};
+
+// A mask requested before the dashboard hydrates cannot be applied yet: the
+// store holds no filter entries to validate the ids against, and hydration
+// would replace anything dispatched in the meantime. Hold the request and
+// replay it once hydration lands.
+let queuedDataMask: DataMaskStateWithId | undefined;
+let unsubscribeFromHydration: (() => void) | undefined;
+
+const setDataMask = ({ dataMask }: { dataMask: DataMaskStateWithId }) => {
+  if (isDashboardHydrated()) {
+    applyDataMask(dataMask);
+    return;
+  }
+
+  queuedDataMask = { ...queuedDataMask, ...dataMask };
+  unsubscribeFromHydration ??= store?.subscribe(() => {
+    if (!isDashboardHydrated()) return;
+    unsubscribeFromHydration?.();
+    unsubscribeFromHydration = undefined;
+    const pending = queuedDataMask;
+    queuedDataMask = undefined;
+    if (pending) applyDataMask(pending);
+  });
+};
+
 const getChartStates = () =>
   store?.getState()?.dashboardState?.chartStates || {};
 
@@ -102,4 +158,5 @@ export const embeddedApi: EmbeddedSupersetApi = {
   getDataMask,
   getChartStates,
   getChartDataPayloads,
+  setDataMask,
 };
diff --git a/superset-frontend/src/embedded/index.tsx b/superset-frontend/src/embedded/index.tsx
index cbd2c281066..7d452f82946 100644
--- a/superset-frontend/src/embedded/index.tsx
+++ b/superset-frontend/src/embedded/index.tsx
@@ -298,6 +298,7 @@ window.addEventListener('message', function embeddedPageInitializer(event) {
     Switchboard.defineMethod('getActiveTabs', embeddedApi.getActiveTabs);
     Switchboard.defineMethod('getDataMask', embeddedApi.getDataMask);
     Switchboard.defineMethod('getChartStates', embeddedApi.getChartStates);
+    Switchboard.defineMethod('setDataMask', embeddedApi.setDataMask);
     Switchboard.defineMethod(
       'getChartDataPayloads',
       embeddedApi.getChartDataPayloads,