/metabase-data-app-actions
Use when a Metabase data app needs to trigger a write or mutation — submitting a form, updating a row, deleting an entry, running a saved action, or any "do something" interaction. Covers invoking an existing action via `useAction`, parameter typing, response handling, and the
$ npx -y skills add metabase/metabase --skill metabase-data-app-actions --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/metabase-data-app-actions
Context preview
The summary Claude sees to decide when to auto-load this skill.
Use when a Metabase data app needs to trigger a write or mutation — submitting a form, updating a row, deleting an entry, running a saved action, or any "do something" interaction. Covers invoking an existing action via `useAction`, parameter typing, response handling, and the
SKILL.md
metabase-data-app-actions.SKILL.mdname: metabase-data-app-actions
description: Use when a Metabase data app needs to trigger a write or mutation — submitting a form, updating a row, deleting an entry, running a saved action, or any "do something" interaction. Covers invoking an existing action via `useAction`, parameter typing, response handling, and the critical post-action refresh of any UI data the action may have changed.
Triggering actions from a Metabase data app
A Metabase **action** is a server-defined write operation against the data warehouse — either a basic CRUD operation (insert / update / delete) on a model, or a custom SQL command. Actions are configured ahead of time on the Metabase instance, with their parameters, model bindings, and permissions already set. A data app's job is to **invoke** an action with the right parameters when the user does something — clicking a button, submitting a form, confirming a destructive prompt.
The mental model
Actions belong to a model. They mutate that model's rows. Concretely:
- Every action has a parent model. In the schema it appears as `schema.models.<modelName>.actions.<actionName>`.
- Model entries are included here only to catalog actions. Do not render models as questions, pass model ids to `InteractiveQuestion`, or fetch model rows; use semantic-layer queries/questions for read views.
- An action's `type` is either `"implicit"` (CRUD on the model) or `"query"` (custom SQL the user authored).
- Implicit actions have an `implicitKind` that says what they do: `"row/create"`, `"row/update"`, `"row/delete"`, or `"bulk/*"` variants.
- Each action publishes a `parameters` list. Each parameter has a `slug` (the key the Data App sends), a `jsType` (`"string"` / `"number"` / `"Date"` / `"boolean"` / `"unknown"`), and an optional `required` flag.
- Use `action.parameters` to know which fields to render and submit. A create result may include `result["created-row"]`, but that row is only typed as `Record<string, RowValue>`; use it for lightweight confirmation, then refresh the existing table/question/query data already used by the page. Do not fetch or render the parent model itself.
What's in the schema (and what isn't)
Action entries are only generated when the typed schema includes models. Before writing action-invoking code, make sure the schema was generated with `include-models=true`; with `database=<name-or-id>&include-models=true`, Metabase includes models/actions for that database only. Do not rely on `question-collections` for actions; question collections only add saved questions.
Before writing any action-invoking code, look at the schema and enumerate what's available under `schema.models.<m>.actions` across the models the app cares about. The schema is your **complete** catalog of actions for the instance, not a catalog of model data to display. If a model's `actions` entry has `create`, `update`, `delete`, those are the actions invokable. Anything not present doesn't exist as far as the Data App is concerned.
The hook
import { useAction } from "@metabase/embedding-sdk-react";
import {
type ActionKindFromDataAppSchema,
type ActionParametersFromDataAppSchema,
} from "@metabase/embedding-sdk-react/data-app";
const { execute, isExecuting, result, error, reset } = useAction<
ActionParametersFromDataAppSchema<typeof schema.models.<model>.actions.<action>>,
ActionKindFromDataAppSchema<typeof schema.models.<model>.actions.<action>>
>(schema.models.<model>.actions.<action>.id);- **First argument** is the action's numeric **id** — read it off the schema entry as `schema.models.<model>.actions.<action>.id`. The hook also accepts an action's `entity_id` string, but in a Data App you always have the numeric id on hand from the schema, so pass that.
- **`TParameters` generic** — the type of the parameters object you'll pass to `execute`. In a Data App, derive it from the schema with `ActionParametersFromDataAppSchema<typeof schema.models.<model>.actions.<action>>` imported from `@metabase/embedding-sdk-react/data-app`; the helper expands the schema's `parameters[]` into a keyed object, marks `required: true` entries as required keys, and types each value from its `jsType`. Skip the generic and `execute` accepts any `Record<string, unknown>`.
- **`TKind` generic** — the action kind literal that drives the discriminated `result` shape. In a Data App, derive it from the same schema entry with `ActionKindFromDataAppSchema<typeof schema.models.<model>.actions.<action>>` imported from `@metabase/embedding-sdk-react/data-app`; the helper maps `implicitKind` (`"row/create"` → `"create"`, `"row/update"` → `"update"`, `"row/delete"` → `"delete"`, any `"bulk/*"` → `"bulk"`) and `type === "query"` → `"sql"`. Skip the generic and `result` defaults to the `AnyActionResult` union — TS-narrowable via `"<key>" in result`, but you lose the per-kind precision.
- **`execute(parameters)`** — triggers the action. Parameters object is keyed by parameter `slug`; parameters declared `required: true` are required keys, everything else optional. Returns the response body on success AND throws on failure (the error is also written to `error` state for render-time consumers). Resolves to `null` (without making a request) when `actionId` is `null` or the SDK is not yet initialized — guard the call site if those cases are reachable.
- **No `enabled` / `options` argument.** The hook only ever runs when `execute(...)` is called, so a gate option would be redundant. Skip the action by branching in the event handler:
const onClick = async () => {
if (!user.canEdit) return;
await execute({ id: orderId, discount });
};- **`isExecuting`** — `true` between the call and its resolution. Drive button `disabled` from this so the user can't double-click into duplicate requests.
- **`result`** — the response body, discriminated by `TKind` (or the `AnyActionResult` union when `TKind` is omitted). `null` before the first call and after `reset
Read more
name: metabase-data-app-actions description: Use when a Metabase data app needs to trigger a write or mutation — submitting a form, updating a row, deleting an entry, running a saved action, or any "do something" interaction. Covers invoking an existing action via `useAction`, parameter typing, response handling, and the critical post-action refresh of any UI data the action may have changed.
Triggering actions from a Metabase data app
A Metabase **action** is a server-defined write operation against the data warehouse — either a basic CRUD operation (insert / update / delete) on a model, or a custom SQL command. Actions are configured ahead of time on the Metabase instance, with their parameters, model bindings, and permissions already set. A data app's job is to **invoke** an action with the right parameters when the user does something — clicking a button, submitting a form, confirming a destructive prompt.
The mental model
Actions belong to a model. They mutate that model's rows. Concretely:
- Every action has a parent model. In the schema it appears as `schema.models.<modelName>.actions.<actionName>`.
- Model entries are included here only to catalog actions. Do not render models as questions, pass model ids to `InteractiveQuestion`, or fetch model rows; use semantic-layer queries/questions for read views.
- An action's `type` is either `"implicit"` (CRUD on the model) or `"query"` (custom SQL the user authored).
- Implicit actions have an `implicitKind` that says what they do: `"row/create"`, `"row/update"`, `"row/delete"`, or `"bulk/*"` variants.
- Each action publishes a `parameters` list. Each parameter has a `slug` (the key the Data App sends), a `jsType` (`"string"` / `"number"` / `"Date"` / `"boolean"` / `"unknown"`), and an optional `required` flag.
- Use `action.parameters` to know which fields to render and submit. A create result may include `result["created-row"]`, but that row is only typed as `Record<string, RowValue>`; use it for lightweight confirmation, then refresh the existing table/question/query data already used by the page. Do not fetch or render the parent model itself.
What's in the schema (and what isn't)
Action entries are only generated when the typed schema includes models. Before writing action-invoking code, make sure the schema was generated with `include-models=true`; with `database=<name-or-id>&include-models=true`, Metabase includes models/actions for that database only. Do not rely on `question-collections` for actions; question collections only add saved questions.
Before writing any action-invoking code, look at the schema and enumerate what's available under `schema.models.<m>.actions` across the models the app cares about. The schema is your **complete** catalog of actions for the instance, not a catalog of model data to display. If a model's `actions` entry has `create`, `update`, `delete`, those are the actions invokable. Anything not present doesn't exist as far as the Data App is concerned.
The hook
import { useAction } from "@metabase/embedding-sdk-react";
import {
type ActionKindFromDataAppSchema,
type ActionParametersFromDataAppSchema,
} from "@metabase/embedding-sdk-react/data-app";
const { execute, isExecuting, result, error, reset } = useAction<
ActionParametersFromDataAppSchema<typeof schema.models.<model>.actions.<action>>,
ActionKindFromDataAppSchema<typeof schema.models.<model>.actions.<action>>
>(schema.models.<model>.actions.<action>.id);- **First argument** is the action's numeric **id** — read it off the schema entry as `schema.models.<model>.actions.<action>.id`. The hook also accepts an action's `entity_id` string, but in a Data App you always have the numeric id on hand from the schema, so pass that.
- **`TParameters` generic** — the type of the parameters object you'll pass to `execute`. In a Data App, derive it from the schema with `ActionParametersFromDataAppSchema<typeof schema.models.<model>.actions.<action>>` imported from `@metabase/embedding-sdk-react/data-app`; the helper expands the schema's `parameters[]` into a keyed object, marks `required: true` entries as required keys, and types each value from its `jsType`. Skip the generic and `execute` accepts any `Record<string, unknown>`.
- **`TKind` generic** — the action kind literal that drives the discriminated `result` shape. In a Data App, derive it from the same schema entry with `ActionKindFromDataAppSchema<typeof schema.models.<model>.actions.<action>>` imported from `@metabase/embedding-sdk-react/data-app`; the helper maps `implicitKind` (`"row/create"` → `"create"`, `"row/update"` → `"update"`, `"row/delete"` → `"delete"`, any `"bulk/*"` → `"bulk"`) and `type === "query"` → `"sql"`. Skip the generic and `result` defaults to the `AnyActionResult` union — TS-narrowable via `"<key>" in result`, but you lose the per-kind precision.
- **`execute(parameters)`** — triggers the action. Parameters object is keyed by parameter `slug`; parameters declared `required: true` are required keys, everything else optional. Returns the response body on success AND throws on failure (the error is also written to `error` state for render-time consumers). Resolves to `null` (without making a request) when `actionId` is `null` or the SDK is not yet initialized — guard the call site if those cases are reachable.
- **No `enabled` / `options` argument.** The hook only ever runs when `execute(...)` is called, so a gate option would be redundant. Skip the action by branching in the event handler:
const onClick = async () => {
if (!user.canEdit) return;
await execute({ id: orderId, discount });
};- **`isExecuting`** — `true` between the call and its resolution. Drive button `disabled` from this so the user can't double-click into duplicate requests.
- **`result`** — the response body, discriminated by `TKind` (or the `AnyActionResult` union when `TKind` is omitted). `null` before the first call and after `reset
Metabase is the easy, open-source way for everyone in your company to ask questions and learn from data.
Repo: metabase/metabase
Other skills on metabase.
- /add-malli-schemas
Efficiently add Malli schemas to API endpoints in the Metabase codebase with proper patterns, validation timing, and error handling
Open skill - /add-tracing
Add OpenTelemetry tracing spans to Clojure code following Metabase tracing conventions. Use when instrumenting backend code with trace coverage.
Open skill - /analytics-events
Add product analytics events to track user interactions in the Metabase frontend
Open skill - /clojure-eval
Evaluate Clojure code via nREPL using clj-nrepl-eval. Use this when you need to test code, check if edited files compile, verify function behavior, or interact with a running REPL session.
Open skill - /clojure-review
Review Clojure and ClojureScript code changes for compliance with Metabase coding standards, style violations, and code quality issues. Use when reviewing pull requests or diffs containing Clojure/ClojureScript code.
Open skill - /clojure-write
Guide Clojure and ClojureScript development using REPL-driven workflow, coding conventions, and best practices. Use when writing, developing, or refactoring Clojure/ClojureScript code.
Open skill

