Follow the workflow steps in order — do not skip any step. Create the checklist first, then execute each step and explicitly mark it done with evidence. Each step's output feeds into the next, so skipping steps produces wrong migrations.
If you cannot complete a step due to missing info or tool failure, you must:
- record the step as ❌ blocked,
- explain exactly what is missing / what failed,
- stop (do not proceed to later steps).
请按顺序遵循工作流步骤——请勿跳过任何步骤。首先创建检查清单,然后执行每个步骤并明确标记完成状态及证据。每个步骤的输出会作为下一个步骤的输入,因此跳过步骤会导致迁移错误。
若因信息缺失或工具故障无法完成某一步骤,您必须:
- 将该步骤标记为 ❌ 受阻,
- 准确说明缺失的内容 / 故障情况,
- 停止操作(请勿继续后续步骤)。
Required output structure
要求的输出结构
Your response should contain these sections in this order:
- Step 0 Results: Metabase Version Detection
- Step 0.1: Migration Plan Checklist
- Step 1 Results: Project Scan + Docs Fetch
- Step 2 Results: Static Embed Analysis & Web Component Mapping
- Step 3: Migration Plan
- Step 4: Applied Code Changes
- Step 5: Validation
- Step 6: Final Summary
Each step section should end with a status line:
Steps are sequential — do not start a step until the previous one is ✅ complete.
您的回复应包含以下部分,且顺序如下:
- 步骤0结果:Metabase版本检测
- 步骤0.1:迁移计划检查清单
- 步骤1结果:项目扫描 + 文档获取
- 步骤2结果:静态嵌入分析与Web组件映射
- 步骤3:迁移计划
- 步骤4:已应用的代码变更
- 步骤5:验证
- 步骤6:最终总结
每个步骤部分的结尾应包含状态行:
步骤需按顺序执行——前一个步骤标记为 ✅ complete 后,才能开始下一个步骤。
Evidence requirements
证据要求
- Step 0: Metabase version detected (source: Docker tag, env var, or user answer).
- Step 1: every matched file path, every static embed location, JWT signing code, layout/head file, Metabase config variables, fetched docs listing.
- Step 2: per embed — parsed iframe URL, content type, token variable, hash params, mapped web component with attributes.
- Step 3: the complete file-by-file change plan with exact old/new code.
- Step 4: per file — what was changed and exact diffs applied.
- Step 5: each validation check's pass/fail result with evidence.
- 步骤0:检测到的Metabase版本(来源:Docker标签、环境变量或用户回答)。
- 步骤1:所有匹配的文件路径、所有静态嵌入位置、JWT签名代码、布局/头部文件、Metabase配置变量、获取的文档列表。
- 步骤2:每个嵌入内容——解析后的iframe URL、内容类型、令牌变量、哈希参数、映射后的带属性Web组件。
- 步骤3:完整的逐文件变更计划,包含准确的新旧代码。
- 步骤4:每个文件——变更内容及应用的准确差异。
- 步骤5:每个验证检查的通过/失败结果及证据。
Architectural conformance
架构一致性
Follow the app's existing architecture, template engine, layout/partial system, code style, and route patterns. Do not switch paradigms (e.g., templates to inline HTML or vice versa). If the app has middleware for shared template variables, prefer that over duplicating across route handlers.
The web component must be rendered using the
same delivery mechanism as the static iframe it replaces. If the iframe was rendered by a server-side template (EJS, Jinja, ERB, Blade, etc.), the web component should be rendered by the same template. If the iframe was returned as inline HTML from a route handler (e.g.,
), the web component should be returned the same way. If the iframe was in a static HTML file, the web component goes in that same file. Do not move rendering from one layer to another — the migration should be a drop-in replacement at the same point in the rendering pipeline.
Token delivery must use the same mechanism as the original static embedding. If the JWT was rendered server-side into the HTML (e.g.,
<iframe src=".../${token}">`)
), the migrated web component should receive its token the same way — rendered server-side into the
token
<metabase-dashboard token="${token}">
). If the JWT was fetched client-side via
fetch()
fetch()` for the token. Do not change the delivery mechanism — just change what is delivered (raw token instead of full iframe URL).
遵循应用现有的架构、模板引擎、布局/局部系统、代码风格和路由模式。请勿切换范式(例如,从模板转为内联HTML,或反之)。若应用有用于共享模板变量的中间件,优先使用该中间件,而非在路由处理器中重复代码。
Web组件必须使用与它所替换的静态iframe
相同的交付机制。若iframe由服务器端模板(EJS、Jinja、ERB、Blade等)渲染,则Web组件也应由同一模板渲染。若iframe是从路由处理器返回的内联HTML(例如,
),则Web组件也应通过相同方式返回。若iframe位于静态HTML文件中,则Web组件应放在同一文件中。请勿在不同层级间转移渲染逻辑——迁移应在渲染流程的相同位置进行无缝替换。
令牌交付必须使用与原静态嵌入相同的机制。若JWT是在服务器端渲染到HTML中的(例如,
<iframe src=".../${token}">\
),则迁移后的Web组件应通过相同方式接收令牌——在服务器端渲染到
属性中(例如,
<metabase-dashboard token="${token}">
)。若JWT是通过客户端
获取的,则继续使用
获取令牌。请勿更改交付机制——仅更改交付的内容(原始令牌而非完整的iframe URL)。
This migration touches code that handles
and signed JWTs. Rules:
- Never output literal secret values — if you encounter a hardcoded secret key or token string in the code, reference it by variable name only (e.g., "the secret in "), never echo the value itself
- In code diffs and summaries, use variable references (, ) — not the resolved values
- If you find hardcoded secrets, flag them to the user and recommend moving them to environment variables as part of the migration
- 绝不要输出字面量密钥值——若在代码中遇到硬编码的密钥或令牌字符串,仅引用其变量名(例如,"中的密钥"),绝不要回显值本身
- 在代码差异和总结中,使用变量引用(、)——而非解析后的值
- 若发现硬编码密钥,需向用户标记该问题,并建议在迁移过程中将其移至环境变量中
- Maximize parallelism within each step. Use parallel Grep/Glob/Read calls in single messages wherever possible.
- Do not use sub-agents for project scanning — results need to stay in the main context for cross-referencing in later steps.
- Do not parse repo branches, commits, PRs, or issues.
- 在每个步骤内最大化并行性。尽可能在单个消息中使用并行的Grep/Glob/Read调用。
- 不要使用子代理进行项目扫描——结果需保留在主上下文中,以便后续步骤进行交叉引用。
- 不要解析仓库分支、提交、PR或问题。
This skill converts static (signed) iframe embedding to guest embeds (web-component-based via
). Both approaches use the same authentication model — signed JWTs with
— so the backend signing logic is preserved. The migration changes how the signed content is delivered: from iframes with JWT-in-URL to web components with a
attribute.
The consumer's app may be written in any backend language (Node.js, Python, Ruby, PHP, Java, Go, .NET, etc.) with any template engine. Keep instructions language-agnostic unless a specific language is detected in Step 1.
本技能将静态(签名)iframe嵌入转换为访客嵌入(基于
的Web组件)。两种方式使用相同的认证模型——通过
签名的JWT——因此后端签名逻辑会被保留。迁移仅更改签名内容的交付方式:从URL中包含JWT的iframe改为带有
属性的Web组件。
消费者应用可使用任何后端语言编写(Node.js、Python、Ruby、PHP、Java、Go、.NET等),搭配任何模板引擎。除非在步骤1中检测到特定语言,否则保持说明与语言无关。
What this skill handles
本技能处理的内容
- Replacing signed elements (, ) with web components (
<metabase-dashboard token="...">
, <metabase-question token="...">
)
- Adding the script tag (exactly once at app layout level)
- Adding with (exactly once at app layout level)
- Mapping iframe hash parameters (, ) to web component attributes
- Preserving the existing JWT signing logic — the backend still signs tokens with using the same payload
- Converting how the signed token reaches the frontend (from iframe URL path to template variable passed as attribute)
- Mapping locked in the JWT to attribute where applicable
- Removing references if present
- 将签名的元素(、)替换为Web组件(
<metabase-dashboard token="...">
、<metabase-question token="...">
)
- 添加脚本标签(在应用布局层仅添加一次)
- 添加带有的(在应用布局层仅添加一次)
- 将iframe哈希参数(、)映射为Web组件属性
- 保留现有的JWT签名逻辑——后端仍使用相同的负载,通过签名令牌
- 修改签名令牌到达前端的方式(从iframe URL路径改为作为属性传递的模板变量)
- 在适用时,将JWT中的锁定映射为属性
- 移除引用(若存在)
What this skill does not handle
本技能不处理的内容
- Migrating to SSO-based modular embedding (with user accounts) — this skill targets guest embedding only
- 迁移到基于SSO的模块化嵌入(带有用户账户)——本技能仅针对访客嵌入
How guest embeds differs from static iframe embedding
访客嵌入与静态iframe嵌入的区别
The auth model is the
same — both use
to sign JWTs with
. What changes is how the embed is rendered:
| Aspect | Static embedding (iframe) | Guest embeds (web component) |
|---|
| Element | <iframe src="/embed/dashboard/{JWT}#params">
| <metabase-dashboard token="{JWT}">
|
| Token delivery | Baked into iframe URL path | Passed as attribute |
| Config | None (iframe is self-contained) | window.metabaseConfig = { isGuest: true, instanceUrl: "..." }
|
| Script | Optional | Required |
| Appearance | Hash params () | Component attributes () |
| Locked params | In JWT field | Same JWT field (unchanged) |
| Secret key | | Same |
Guest embeds support additional attributes (e.g., downloads, drill-through, hidden parameters) not available in static embedding. Consult the fetched docs for the full list of available attributes for the target version.
认证模型
相同——均使用
对包含
的JWT进行签名。差异在于嵌入内容的渲染方式:
| 方面 | 静态嵌入(iframe) | 访客嵌入(Web组件) |
|---|
| 元素 | <iframe src="/embed/dashboard/{JWT}#params">
| <metabase-dashboard token="{JWT}">
|
| 令牌交付 | 嵌入在iframe URL路径中 | 作为属性传递 |
| 配置 | 无(iframe是自包含的) | window.metabaseConfig = { isGuest: true, instanceUrl: "..." }
|
| 脚本 | 可选的 | 必填的 |
| 外观 | 哈希参数() | 组件属性() |
| 锁定参数 | 在JWT的字段中 | 相同的JWT字段(无变化) |
| 密钥 | | 相同的 |
访客嵌入支持静态嵌入不具备的额外属性(例如,下载、钻取、隐藏参数)。请参考获取的文档,了解目标版本支持的完整属性列表。
Allowed documentation sources
允许的文档来源
Fetch the version-specific
using this URL:
https://www.metabase.com/docs/v0.{VERSION}/llms-embedding-full.txt
The version in the URL uses the format
(normalize: strip leading
or
, drop patch — e.g.,
→
→ URL uses
). This single file contains all embedding documentation for that version, optimized for LLM consumption.
Other constraints:
- No GitHub PRs/issues or npm pages
- Do not follow changelog links to GitHub or guess URLs
https://www.metabase.com/docs/v0.{VERSION}/llms-embedding-full.txt
URL中的版本格式为
(标准化:去除前导的
或
,去掉补丁版本——例如,
→
→ URL使用
)。该文件包含该版本的所有嵌入文档,针对LLM使用进行了优化。
其他限制:
- 禁止使用GitHub PR/问题或npm页面
- 请勿跟随变更日志链接跳转到GitHub或猜测URL
AskUserQuestion triggers
AskUserQuestion触发条件
Use AskUserQuestion and halt until answered if:
- The Metabase instance URL cannot be determined from project code or environment variables
- The backend language cannot be determined
- The Metabase instance version cannot be determined from the project code
- No layout/head file can be identified (unclear where to inject embed.js)
- Multiple layout files exist and it is unclear which one(s) to use
若出现以下情况,使用AskUserQuestion并暂停操作直至获得回答:
- 无法从项目代码或环境变量中确定Metabase实例URL
- 无法确定后端语言
- 无法从项目代码中确定Metabase实例版本
- 无法识别布局/头部文件(不清楚应在何处注入embed.js)
- 存在多个布局文件,且无法确定应使用哪个/哪些文件
Pre-workflow steps
工作流前置步骤
Migration Plan Checklist
迁移计划检查清单
Create a checklist to track progress. In Claude Code, use TaskCreate/TaskUpdate tools:
- Step 0: Detect Metabase version
- Step 1: Scan project + fetch target version docs
- Step 2: Analyze static embeds and map to web components (using docs)
- Step 3: Plan migration changes
- Step 4: Apply code changes
- Step 5: Validate changes
- Step 6: Final summary
创建检查清单以跟踪进度。在Claude Code中,使用TaskCreate/TaskUpdate工具:
- 步骤0:检测Metabase版本
- 步骤1:扫描项目 + 获取目标版本文档
- 步骤2:分析静态嵌入并映射到Web组件(使用文档)
- 步骤3:制定迁移变更计划
- 步骤4:应用代码变更
- 步骤5:验证变更
- 步骤6:最终总结
Step 0: Detect Metabase version
步骤0:检测Metabase版本
Before anything else, determine the Metabase version. Grep the project for Docker image tags (
,
metabase/metabase-enterprise:v
),
, or version references. If undetected, AskUserQuestion (options:
,
,
). Abort if < v53 (modular embedding not available). Record the version.
在开始任何操作之前,先确定Metabase版本。在项目中搜索Docker镜像标签(
、
metabase/metabase-enterprise:v
)、
或版本引用。若未检测到,使用AskUserQuestion(选项:
、
、
)。若版本低于v53则终止(模块化嵌入不可用)。记录版本。
Step 1: Scan the project + fetch docs
步骤1:扫描项目 + 获取文档
Perform the project scan and doc fetch concurrently — they are independent. Use parallel tool calls within a single message wherever there are no dependencies.
并行执行项目扫描和文档获取——两者相互独立。只要没有依赖关系,就在单个消息中使用并行工具调用。
1a: Fetch target version docs
1a:获取目标版本文档
Fetch
for the target version (see "Allowed documentation sources" for URL format). These docs are the authoritative source for web component attributes,
options, and guest embedding configuration for the target version. Use them in Step 2 for mapping instead of relying on hardcoded tables alone.
Launch this concurrently with the project scan steps below.
获取目标版本的
(请参考“允许的文档来源”中的URL格式)。这些文档是Web组件属性、
选项和访客嵌入配置的权威来源,适用于目标版本。在步骤2中进行映射时,请使用这些文档,而非仅依赖硬编码表格。
与下方的项目扫描步骤同时启动此操作。
1b: Identify backend language and framework
1b:识别后端语言和框架
- Check for dependency/build files (, , , , , , etc.).
- Identify the template engine and record the language and framework.
- 检查依赖/构建文件(、、、、、等)。
- 识别模板引擎并记录语言和框架。
1c: Check for existing modular embedding setup
1c:检查现有模块化嵌入设置
Grep for these patterns (in parallel) to detect if the app already has modular embedding configured:
- — existing embed.js script tag
- — existing config assignment
Record whether each is already present and where. If both already exist (e.g., the app uses modular embedding alongside static embedding), Steps 3a and 3b will skip adding them.
并行搜索以下模式,以检测应用是否已配置模块化嵌入:
- —— 现有的embed.js脚本标签
- —— 现有的配置赋值
记录各项是否已存在及其位置。若两者均已存在(例如,应用同时使用模块化嵌入和静态嵌入),步骤3a和3b将跳过添加操作。
1d: Find ALL static embedding code
1d:查找所有静态嵌入代码
Use Grep to search for all of these patterns (in parallel):
- in all files — static embed dashboard URLs
- in all files — static embed question URLs
- in all template/HTML/JSX/view files — the embed elements
- or
METABASE_EMBEDDING_SECRET_KEY
— the signing secret
- near or — JWT payload structure
- — optional auto-resize script
For each file with a match, read the entire file.
使用Grep并行搜索以下所有模式:
- 所有文件中的 —— 静态嵌入仪表板URL
- 所有文件中的 —— 静态嵌入问题URL
- 所有模板/HTML/JSX/视图文件中的 —— 嵌入元素
- 或
METABASE_EMBEDDING_SECRET_KEY
—— 签名密钥
- 靠近或的 —— JWT负载结构
- —— 可选的自动调整大小脚本
对于每个匹配的文件,读取整个文件内容。
1e: Find JWT signing code
1e:查找JWT签名代码
Use Grep to search for all of these patterns (in parallel):
- or or or or or
- or
- combined with (the static embed JWT payload shape)
For each matching file, read the entire file.
使用Grep并行搜索以下所有模式:
对于每个匹配的文件,读取整个文件内容。
1f: Find the layout/head file(s)
1f:查找布局/头部文件
Find the single file (or common code path) where the HTML
section is defined — this is where
and
will be injected (unless already present per Step 1c).
Search for:
- or or in template/view files
- Layout/wrapper patterns: , , , , , , ,
- If the app builds HTML via inline strings in server code (e.g., ), identify where the content is generated
定义HTML
部分的单个文件(或通用代码路径)——这是注入
和
的位置(除非步骤1c检测到它们已存在)。
搜索:
- 模板/视图文件中的或或
- 布局/包装器模式:、、、、、、、
- 若应用通过服务器代码中的内联字符串构建HTML(例如,),识别内容的生成位置
1g: Find Metabase configuration
1g:查找Metabase配置
Grep for
and
prefixed variables. Record every Metabase-related variable name and where it is read.
搜索前缀为
和
的变量。记录每个Metabase相关变量的名称及其读取位置。
Output: Structured Project Inventory
输出:结构化项目清单
Compile all findings into:
Backend: {language}, {framework}, {template engine}
Metabase config:
- Site URL variable: {name} (read at {file}:{line})
- Secret key variable: {name} (read at {file}:{line})
- Other variables: ...
Layout/head file: {path}:{line range}
Static embeds found: {count}
- {file}:{line} — {brief description} (dashboard/question, ID: {id})
- ...
JWT signing: {file}:{line} — {library used}
JWT payload: resource type={dashboard|question}, params={list or "none"}
iframeResizer: {present|not present}
Existing modular embedding: {embed.js: yes/no, metabaseConfig: yes/no}
将所有发现整理为:
后端:{language}, {framework}, {template engine}
Metabase配置:
- 站点URL变量:{name}(读取位置:{file}:{line})
- 密钥变量:{name}(读取位置:{file}:{line})
- 其他变量:...
布局/头部文件:{path}:{line range}
找到的静态嵌入:{count}
- {file}:{line} —— {简要描述}(仪表板/问题,ID:{id})
- ...
JWT签名:{file}:{line} —— {使用的库}
JWT负载:资源类型={dashboard|question}, 参数={列表或"无"}
iframeResizer:{存在|不存在}
现有模块化嵌入:{embed.js: 是/否, metabaseConfig: 是/否}
Step 2: Analyze static embeds and map to web components (ONLY after Step 1 ✅)
步骤2:分析静态嵌入并映射到Web组件(仅在步骤1 ✅完成后执行)
Use the documentation fetched in Step 1a as the authoritative reference for web component attributes,
options, and guest embedding behavior. The hardcoded tables below are fallbacks — if the docs describe additional attributes or different behavior for the target version, prefer the docs.
For each static embed found in Step 1:
使用步骤1a中获取的文档作为Web组件属性、
选项和访客嵌入行为的权威参考。下方的硬编码表格仅作为备选——若文档描述了目标版本支持的额外属性或不同行为,优先遵循文档。
对于步骤1中找到的每个静态嵌入:
2a: Parse the signed iframe URL
2a:解析签名的iframe URL
Extract from the iframe
attribute:
- Metabase base URL: may come from env var, constant, or be hardcoded
- Resource type: or (from the path)
- Resource ID: the numeric ID from the JWT field (e.g.,
resource: { dashboard: 10 }
)
- Locked parameters: any in the JWT payload (e.g.,
params: { category: ["Gadget"] }
)
- Hash parameters: appearance customization after (e.g.,
#titled=true&bordered=false
)
- iframeResizer usage: whether is called on this iframe
- Metabase基础URL:可能来自环境变量、常量或硬编码
- 资源类型:或(来自路径)
- 资源ID:JWT字段中的数字ID(例如,
resource: { dashboard: 10 }
)
- 锁定参数:JWT负载中的任何(例如,
params: { category: ["Gadget"] }
)
- 哈希参数:后的外观自定义参数(例如,
#titled=true&bordered=false
)
- iframeResizer使用情况:是否对该iframe调用了
2b: Map content type to web component
2b:将内容类型映射到Web组件
| Static embed URL pattern | Modular Web Component | Required Attribute |
|---|
| | |
| | |
The
attribute receives the same signed JWT that was previously baked into the iframe URL. The backend signing code stays the same — only the delivery mechanism changes.
If the token was built dynamically in a template (e.g.,
src="<%= metabaseUrl %>/embed/dashboard/<%= token %>"
), extract the token variable and pass it as the
attribute (e.g.,
).
属性接收之前嵌入在iframe URL中的相同签名JWT。后端签名代码保持不变——仅交付机制更改。
若令牌是在模板中动态构建的(例如,
src="<%= metabaseUrl %>/embed/dashboard/<%= token %>"
),提取令牌变量并将其作为
属性传递(例如,
)。
2c: Map hash parameters
2c:映射哈希参数
Parameters that map to web component attributes:
| Static embedding hash params | Guest embeds equivalent |
|---|
| on the component |
| No direct equivalent — drop (web components have no border chrome) |
| No direct equivalent — drop (handled by Metabase instance config) |
| Use window.metabaseConfig.theme
instead (if supported by version) |
映射到Web组件属性的参数:
| 静态嵌入哈希参数 | 访客嵌入等效项 |
|---|
| 组件上的 |
| 无直接等效项——移除(Web组件无边框) |
| 无直接等效项——移除(由Metabase实例配置处理) |
| 改用window.metabaseConfig.theme
(若版本支持) |
2d: Map locked and editable parameters
2d:映射锁定和可编辑参数
Locked parameters (in JWT
field) — no change needed. They remain in the JWT and continue to work the same way. The signed token already contains them.
Editable parameters — if the static embed allowed users to interact with filters, these can now be set as defaults via the
attribute:
html
<metabase-dashboard
token="{JWT}"
initial-parameters='{"category":["Doohickey","Gizmo"]}'
></metabase-dashboard>
sets default filter values that the user can change. This is a new capability not available in static iframe embedding.
锁定参数(JWT的
字段)——无需更改。它们仍保留在JWT中,功能保持不变。签名令牌已包含这些参数。
可编辑参数——若静态嵌入允许用户与过滤器交互,现在可通过
属性设置默认值:
html
<metabase-dashboard
token="{JWT}"
initial-parameters='{"category":["Doohickey","Gizmo"]}'
></metabase-dashboard>
设置用户可修改的过滤器默认值。这是静态iframe嵌入不具备的新功能。
2e: Output Migration Mapping Table
2e:输出迁移映射表
For each static embed, output:
embed #{n}: {file}:{line}
Old: {iframe HTML or signing + iframe code}
Content type: {dashboard|question}
Token variable: {template expression for the signed JWT}
Locked params: {in JWT — no change needed}
Hash params: {list or "none"}
Dropped params: {list}
Mapped attributes: {list}
New: {exact replacement web component HTML}
对于每个静态嵌入,输出:
嵌入 #{n}: {file}:{line}
旧代码:{iframe HTML或签名+iframe代码}
内容类型:{dashboard|question}
令牌变量:{签名JWT的模板表达式}
锁定参数:{在JWT中——无需更改}
哈希参数:{列表或"无"}
移除的参数:{列表}
映射的属性:{列表}
新代码:{准确的替换Web组件HTML}
Step 3: Plan migration changes (ONLY after Step 2 ✅)
步骤3:制定迁移变更计划(仅在步骤2 ✅完成后执行)
Create a complete file-by-file change plan covering all areas below. Every change should be specified with the target file, the old code, and the new code.
创建完整的逐文件变更计划,涵盖以下所有方面。每个变更应指定目标文件、旧代码和新代码。
3a: metabaseConfig — exactly once per app
3a:metabaseConfig——每个应用仅配置一次
Skip this step if Step 1c found an existing assignment. If it exists but is missing
, add that field to the existing config instead of creating a new one.
- Target: the layout/head file identified in Step 1f
- Location: inside , before the embed.js script tag (the config must be set before embed.js loads)
- Code to add:
html
<script>
window.metabaseConfig = {
isGuest: true,
instanceUrl: "{METABASE_SITE_URL}",
};
</script>
- is required — it tells embed.js to use guest (signed token) mode instead of SSO mode.
- should be rendered dynamically using the project's template expression syntax.
- Locale: If a parameter was found in any static embed hash, add to the config object.
- Consult the fetched docs (Step 1a) for any additional options supported by the target version (e.g., , ).
- should be set exactly once.
**若步骤1c检测到已存在
赋值,则跳过此步骤。**若已存在但缺少
,则向现有配置中添加该字段,而非创建新配置。
- 目标:步骤1f中识别的布局/头部文件
- 位置:内部,在embed.js脚本标签之前(配置必须在embed.js加载前设置)
- 需添加的代码:
html
<script>
window.metabaseConfig = {
isGuest: true,
instanceUrl: "{METABASE_SITE_URL}",
};
</script>
- 是必填项——它告知embed.js使用访客(签名令牌)模式而非SSO模式。
- 应使用项目的模板表达式语法动态渲染。
- 语言环境:若在任何静态嵌入哈希中发现参数,向配置对象添加。
- 参考获取的文档(步骤1a),了解目标版本支持的其他选项(例如,、)。
- 应仅设置一次。
3b: embed.js script injection — exactly once per app
3b:embed.js脚本注入——每个应用仅注入一次
Skip this step if Step 1c found an existing embed.js script tag.
- Target: same layout/head file as 3a
- Location: inside , after the script (embed.js reads the config on load)
- Code to add:
html
<script defer src="{METABASE_SITE_URL}/app/embed.js"></script>
- should be rendered dynamically using the project's existing template expression syntax.
- Verify this will appear exactly once in the rendered HTML regardless of which page the user visits.
若步骤1c检测到已存在embed.js脚本标签,则跳过此步骤。
- 目标:与步骤3a相同的布局/头部文件
- 位置:内部,在脚本之后(embed.js在加载时读取配置)
- 需添加的代码:
html
<script defer src="{METABASE_SITE_URL}/app/embed.js"></script>
- 应使用项目现有的模板表达式语法动态渲染。
- 验证无论用户访问哪个页面,该脚本在渲染后的HTML中仅出现一次。
3c: Refactor backend token delivery
3c:重构后端令牌交付
The backend already has JWT signing code that produces the token. Currently it builds a full iframe URL (
/embed/dashboard/{token}#params
). The signing logic stays — but how the token reaches the frontend changes:
- Before: Backend builds full iframe URL string, passes to template, template renders
- After: Backend passes just the signed token to the template, template renders
<metabase-dashboard token="{token}">
For each signing location found in Step 1d:
- Keep the JWT signing call (
jwt.sign(payload, METABASE_SECRET_KEY)
) unchanged
- Remove the URL construction code that prepended
{baseUrl}/embed/dashboard/
and appended hash params
- Pass the raw token string to the template context instead of the full URL
If the signing happens inline in the template handler (not in a shared function), the change is local to that handler.
后端已有生成令牌的JWT签名代码。当前它构建完整的iframe URL(
/embed/dashboard/{token}#params
)。签名逻辑保持不变——但令牌到达前端的方式更改:
- 之前:后端构建完整的iframe URL字符串,传递给模板,模板渲染
- 之后:后端仅将签名令牌传递给模板,模板渲染
<metabase-dashboard token="{token}">
对于步骤1d中找到的每个签名位置:
- 保持JWT签名调用(
jwt.sign(payload, METABASE_SECRET_KEY)
)不变
- 移除添加
{baseUrl}/embed/dashboard/
前缀和哈希参数后缀的URL构建代码
- 将原始令牌字符串传递给模板上下文,而非完整URL
若签名是在模板处理器中内联进行的(而非在共享函数中),则变更仅针对该处理器。
3d: iframe replacement plan
3d:iframe替换计划
For EACH iframe from Step 2e's Migration Mapping Table:
- Specify: file path, exact old code to replace, exact new code
- The new web component uses where is the template expression for the signed JWT
- Map hash parameters to component attributes per Step 2c
- Preserve styling: Transfer the iframe's sizing directly to the web component element — no wrapper needed:
- If the iframe had / HTML attributes or inline , apply them directly to the web component (e.g.,
<metabase-dashboard token="..." style="width:800px;height:600px">
)
- If the iframe was styled via CSS classes, apply those classes directly to the web component
- If the iframe was inside a container that already controls sizing, no extra styling needed — the web component will fill that container
- If the iframe used for auto-height, drop it — web components handle their own sizing
- Remove any calls associated with this iframe
针对步骤2e迁移映射表中的每个iframe:
- 指定:文件路径、要替换的准确旧代码、准确的新代码
- 新Web组件使用,其中是签名JWT的模板表达式
- 按照步骤2c将哈希参数映射为组件属性
- 保留样式:将iframe的尺寸直接转移到Web组件元素——无需包装:
- 若iframe有/HTML属性或内联,直接应用到Web组件(例如,
<metabase-dashboard token="..." style="width:800px;height:600px">
)
- 若iframe通过CSS类设置样式,将这些类直接应用到Web组件
- 若iframe位于已控制尺寸的容器中,则无需额外样式——Web组件将填充该容器
- 若iframe使用实现自动高度,则移除它——Web组件会自行处理尺寸
- 移除与此iframe关联的任何调用
3e: Dead code removal
3e:移除无用代码
After replacing iframes, identify and remove:
- URL construction code that built
/embed/dashboard/{token}#params
or /embed/question/{token}#params
strings
- script tag and any calls
- Hash parameter string construction (e.g.,
const mods = "titled=true&bordered=false"
)
- Any helper functions that were only used for building static embed iframe URLs
Do not remove:
- JWT signing code (
jwt.sign(payload, METABASE_SECRET_KEY)
) — still used
- env var — still used
- JWT library imports — still used
- Any code used by other parts of the application
替换iframe后,识别并移除:
- 构建
/embed/dashboard/{token}#params
或/embed/question/{token}#params
字符串的URL构建代码
- 脚本标签及任何调用
- 哈希参数字符串构建代码(例如,
const mods = "titled=true&bordered=false"
)
- 仅用于构建静态嵌入iframe URL的任何辅助函数
请勿移除:
- JWT签名代码(
jwt.sign(payload, METABASE_SECRET_KEY)
)——仍在使用
- 环境变量——仍在使用
- JWT库导入——仍在使用
- 应用其他部分使用的任何代码
3f: Metabase admin configuration notes (manual steps for the user)
3f:Metabase管理员配置说明(用户需执行的手动步骤)
List these as part of the plan — they will be included in the final summary:
- Enable modular embedding: Admin > Embedding > toggle "Enable modular embedding"
- Enable guest embedding: Admin > Embedding > ensure "Guest embedding" (or "Static embedding" in older UI) is enabled. The existing static embedding secret key is reused.
- Configure CORS origins: Admin > Embedding > Security > add the host app's domain (e.g., ). This is new — static iframe embedding did not require CORS configuration.
将这些内容作为计划的一部分列出——它们将包含在最终总结中:
- 启用模块化嵌入:管理员 > 嵌入 > 切换“启用模块化嵌入”
- 启用访客嵌入:管理员 > 嵌入 > 确保“访客嵌入”(旧版UI中为“静态嵌入”)已启用。现有的静态嵌入密钥将被复用。
- 配置CORS源:管理员 > 嵌入 > 安全 > 添加宿主应用的域名(例如,)。这是新要求——静态iframe嵌入无需CORS配置。
Step 4: Apply code changes (ONLY after Step 3 ✅)
步骤4:应用代码变更(仅在步骤3 ✅完成后执行)
Apply all changes from Step 3 in this order:
- First: Add assignment and embed.js script tag to the layout/head file (Step 3a + 3b, config before embed.js)
- Second: Refactor backend token delivery — keep signing, remove URL construction (Step 3c)
- Third: Replace each iframe with its web component (Step 3d), one file at a time
- Fourth: Remove dead code — iframeResizer, URL builders (Step 3e)
Constraints:
- Use the Edit tool with precise / for every change
- Do not add new package dependencies — modular embedding requires only the embed.js script served by the Metabase instance
- Do not change or remove — it is still used for signing
- If a file requires multiple edits, apply them top-to-bottom to avoid offset issues
按以下顺序应用步骤3中的所有变更:
- 首先:向布局/头部文件添加赋值和embed.js脚本标签(步骤3a + 3b,配置在embed.js之前)
- 其次:重构后端令牌交付——保留签名逻辑,移除URL构建(步骤3c)
- 第三:将每个iframe替换为对应的Web组件(步骤3d),逐个文件进行
- 第四:移除无用代码——iframeResizer、URL构建器(步骤3e)
约束条件:
- 使用Edit工具,为每个变更提供精确的 /
- 不要添加新的包依赖——模块化嵌入仅需Metabase实例提供的embed.js脚本
- 不要更改或移除——它仍用于签名
- 若一个文件需要多次编辑,从上到下应用以避免偏移问题
Step 5: Validate changes (ONLY after Step 4 ✅)
步骤5:验证变更(仅在步骤4 ✅完成后执行)
Perform all of these checks. Each check should have an explicit pass/fail result.
执行以下所有检查。每个检查应具有明确的通过/失败结果。
5a: No remaining static embed iframes
5a:无剩余的静态嵌入iframe
Use Grep to search for
and
across all project files (excluding
,
, lockfiles).
Pass criteria: zero static embed URL constructions found (the pattern may still appear in comments — verify these are not live code).
使用Grep在所有项目文件中搜索
和
(排除
、
、锁定文件)。
通过标准:未找到任何静态嵌入URL构建代码(该模式可能仍出现在注释中——需验证这些不是生效代码)。
5b: embed.js appears exactly once
5b:embed.js仅出现一次
Use Grep to search for
across all project files (excluding
,
).
Pass criteria: exactly ONE occurrence in the layout/head file.
5c: window.metabaseConfig is set exactly once
5c:window.metabaseConfig仅设置一次
Use Grep to search for
across all project files (excluding
,
).
Pass criteria: exactly ONE occurrence with
.
5d: JWT signing code is preserved
5d:JWT签名代码保留
Read the JWT signing file(s). Verify:
- (or equivalent) call still exists
- is still read from environment
- JWT payload still contains and fields
Pass criteria: signing logic intact.
读取JWT签名文件。验证:
- (或等效调用)仍然存在
- 仍从环境中读取
- JWT负载仍包含和字段
通过标准:签名逻辑完整。
5e: No remaining iframeResizer references
5e:无剩余的iframeResizer引用
Use Grep to search for
and
across all project files.
Pass criteria: zero references remain (or only in unrelated code).
5f: Spot-check modified files
5f:抽查修改后的文件
Read each modified file and verify:
- Web components have attribute with correct template expression
- Template syntax is valid (no unclosed tags, correct expressions)
- Dead code identified in Step 3e has been removed
Pass criteria: all checks pass.
If ANY check fails:
- Fix the issue immediately
- Re-run the specific check
- If unable to fix after 3 attempts, mark Step 5 ❌ blocked and report which check failed and why
读取每个修改后的文件并验证:
- Web组件具有属性,且模板表达式正确
- 模板语法有效(无未闭合标签、表达式正确)
- 步骤3e中识别的无用代码已被移除
通过标准:所有检查通过。
若任何检查失败:
- 立即修复问题
- 重新运行该特定检查
- 若尝试3次仍无法修复,标记步骤5 ❌ 受阻,并报告哪个检查失败及原因
Step 6: Output summary
步骤6:输出总结
Organize the final output into these sections:
- Changes applied: list every file modified and a one-line description of each change
- Web component mapping: table showing each old signed iframe → new web component:
| File | Old | New |
|---|---|---|
| views/analytics.ejs | <iframe src="/embed/dashboard/{token}#titled=true"> | <metabase-dashboard token="{token}" with-title="true"> |
- What stayed the same: JWT signing logic, , locked parameters in JWT field
- Dropped parameters: list of static embed hash parameters that were dropped, with brief explanation
- New capabilities available: features now accessible that weren't in static iframe embedding:
- attribute for editable filter defaults
- attribute for enabling downloads (Pro/Enterprise)
- Better mobile responsiveness (web components adapt to container)
- Manual steps required (Metabase admin configuration from Step 3f):
- Enable modular embedding
- Ensure guest embedding is enabled (reuses existing secret key)
- Configure CORS origins (new requirement)
- Behavioral differences the user should be aware of:
- Web components expand to fill their container — if the iframe had fixed dimensions, verify the container provides appropriate sizing
- The appearance option is no longer available — web components render without a frame
- Auto-refresh () is no longer controlled per embed — configure it in Metabase instance settings instead
将最终输出整理为以下部分:
- 已应用的变更:列出每个修改的文件及每个变更的单行描述
- Web组件映射:表格展示每个旧签名iframe → 新Web组件:
| 文件 | 旧代码 | 新代码 |
|---|---|---|
| views/analytics.ejs | <iframe src="/embed/dashboard/{token}#titled=true"> | <metabase-dashboard token="{token}" with-title="true"> |
- 未变更的内容:JWT签名逻辑、、JWT字段中的锁定参数
- 移除的参数:列出静态嵌入哈希中被移除的参数,并简要说明原因
- 新增可用功能:静态iframe嵌入不具备的新功能:
- 属性用于设置可编辑过滤器默认值
- 属性用于启用下载(专业版/企业版)
- 更好的移动端响应性(Web组件适配容器)
- 需执行的手动步骤(来自步骤3f的Metabase管理员配置):
- 启用模块化嵌入
- 确保访客嵌入已启用(复用现有密钥)
- 配置CORS源(新要求)
- 用户应注意的行为差异:
- Web组件会扩展以填充容器——若iframe有固定尺寸,请验证容器提供了合适的尺寸
- 不再提供外观选项——Web组件渲染时无框架
- 自动刷新()不再按嵌入内容单独控制——改为在Metabase实例设置中配置
Doc fetching:
- If fetching returns 404, verify the Metabase version number and retry. If still failing, mark Step 1 ❌ blocked.
Validation:
- If any validation check in Step 5 fails after 3 fix attempts, mark Step 5 ❌ blocked and report which check failed and why.
- If AskUserQuestion is not answered, remain blocked on that step — do not guess or proceed with assumptions.
文档获取:
- 若获取返回404,验证Metabase版本号并重试。若仍失败,标记步骤1 ❌ 受阻。
验证:
- 若步骤5中的任何验证检查在3次修复尝试后仍失败,标记步骤5 ❌ 受阻,并报告哪个检查失败及原因。
- 若AskUserQuestion未得到回答,保持该步骤受阻状态——请勿猜测或假设继续操作。",