| name | plasmic-designer |
| description | Build and modify Plasmic Studio designs using copilot tools via Chrome DevTools MCP. First argument should be a project ID, followed by the design request. Use this skill whenever the user mentions Plasmic, Plasmic Studio, visual web builder, or asks to design, build, edit, or modify UI components, pages, sections, or layouts inside a Plasmic project. Also trigger when the user references a Plasmic project ID, wants to add/remove/restyle elements in a visual editor, or asks about Plasmic component props, variants, slots, or tokens — even if they don't say "Plasmic" explicitly but describe visual design work that implies it. |
| allowed-tools | mcp__chrome-devtools__evaluate_script mcp__chrome-devtools__navigate_page mcp__chrome-devtools__take_screenshot mcp__chrome-devtools__list_pages |
| metadata | {"version":"1.3.0"} |
Plasmic Designer
Skill Version: 1.3.0
Control Plasmic Studio through Chrome DevTools MCP to build and modify production-ready interfaces.
Arguments
$ARGUMENTS should contain a project ID as the first word, followed by the design request.
Example: /plasmic-designer j2Bm3mrbGNKsXVW3Wf5KpP Add a hero section to the Homepage
If no project ID is provided, or if the conversation references multiple projects and it's unclear which one to use, ask the user to confirm the project ID before proceeding.
Setup
The studio base URL is https://studio.plasmic.app by default. Only use http://localhost:3003 if the user explicitly mentions localhost, a local dev server, or a local environment.
-
Navigate to the project using navigate_page to open {baseUrl}/projects/{projectId}/
-
Wait for studio to load and identify the session — the studio takes a few seconds to initialize. Poll until window.PLASMIC_AI_TOOLS is available, then call identify() once, before any other tool.
async () => {
for (let i = 0; i < 30; i++) {
if (window.PLASMIC_AI_TOOLS) {
return await window.PLASMIC_AI_TOOLS.identify({
model: "<model>",
client: "<client>",
skill: "<skill>",
outputFormat: "<json|xml>",
});
}
await new Promise((r) => setTimeout(r, 1000));
}
return {
success: false,
error: { message: "Studio failed to load." },
};
};
Fields (all required):
model — Model name as known to the agent (e.g. claude-opus-4-7, anthropic/claude-sonnet-4-6, gpt-5.3-codex).
client — AI client/CLI invoking the tool (e.g. claude-code, claude-code@1.x, , , ).
Workflow
Follow an explore-first pattern for every request:
- Understand —
read the current state before changing anything; prefer reusing existing components over new HTML.
- Plan — For complex requests, break the work into steps before acting.
- Execute — Make changes with the appropriate tools.
- Verify —
read to confirm structural changes; Optionally, take_screenshot to confirm the result visually
Using the tools
The toolset is exposed at runtime and is the source of truth — introspect it, don't rely on a hardcoded list. _meta is a Record<string, CopilotToolMeta> keyed by tool name:
interface CopilotToolMeta {
toolName: string;
title: string;
description: string;
inputSchema: JSONSchema7;
outputSchema: JSONSchema7;
}
Read it once with evaluate_script (return the object directly; evaluate_script serializes it for you), and treat each tool's inputSchema as authoritative for field names, required fields, enums, and nesting, and its outputSchema for what a result contains:
() => window.PLASMIC_AI_TOOLS._meta;
Call a tool with an async arrow function (tools return Promises), passing one input object that conforms to its schema:
async () => await window.PLASMIC_AI_TOOLS.<toolName>({
});
Every call resolves to a CopilotToolCallResult:
type CopilotToolCallResult =
| { success: true; output: string }
| {
success: false;
error: { message: string; type: "TOOL_NOT_FOUND" | "EXECUTION_FAILED" };
};
Check success each time; on a UUID error, re-read for fresh UUIDs and retry.
Call read before any mutation to get project structure and the UUIDs every other tool needs. Its output is usually XML: parse it for UUIDs, props, variants, and slots, and read selectively (specific components/elements) on large projects. After a successful mutation the canvas updates automatically.
Legacy Data Query Migration
Only when asked. Legacy $queries run server-side via Plasmic's integration proxy, which
applies its credentials, default headers, and role checks. New $q queries run
plasmic.fetch from wherever the page renders, so none of that carries over. Migrate only
the query the request names; for a component-wide request, assess every legacyDataQueries
entry.
- Read the component (
legacyDataQueries, dataQueries) and project.customFunctions.
For a query's types and values, read just its path:
read({ dataContext: [{ componentUuid, paths: ["$queries.<name>"] }] }).
- Skip any entry with
migratable: false (report its migrationBlockers) or one a
dataQueries entry already replaces — likely when references: 0.
- Create the replacement with
createDataQuery, bound to the Fetch custom function from
project.customFunctions, with the legacy op's baseUrl + path + params as its opts.url.
Carry over all legacy op args — a GraphQL op becomes "method": "POST" with
"body": { "query": …, "variables": … }, and the op's own headers arg becomes opts.headers.
Custom code only when no function fits. Keep dynamic parts as inline {{ }} rather than
the preview values you read, and never invent an endpoint, credential, header, or result.
- Repoint references with
migrateDataQuery, deriving subPathRewrites from
paths: ["$queries.<old>.data", "$q.<new>.data"]. Verify with paths: ["$q.<new>"].
Report one line per query (migrated, or skipped and why), plus anything migrateDataQuery
couldn't rewrite and the manual deletion of the legacy query.
Components & Variants
Targeting
Mutation tools require a componentUuid (from read results). They accept an optional variantUuids array — when omitted, changes apply to the base variant.
Reusing Existing Components
When you read a component, review its props, variants, slots, element tree and per-variant style overrides before using it.
To use a component in insertHtml:
<plasmic-component
data-plasmic-component="ComponentName"
data-plasmic-project="importedProjectId"
data-plasmic-name="primaryCta"
data-props='{"propName":"value","variantGroup":"optionName"}'
style="margin: 16px;"
>
<slot name="slotName">Slot content here</slot>
</plasmic-component>
data-plasmic-component must exactly match the component name from read() (case-sensitive).
data-plasmic-project (optional) is the id of the imported project the component comes from. Omit it for components in the current project; set it to use a component from an imported project.
data-plasmic-name (optional) names this component instance in the tree. It's a semantic name picked up by Plasmic codegen to override the element in the generated code.
data-props is a JSON object for both props and variant activations. Boolean variants: "group": true. Enum variants: "group": "optionName".
<slot name="slotName"> children fill named slots; its children become the slot content.
- Only layout/position styles work on instances: width, height, min/max sizing, margin, position, top/left/bottom/right, z-index, order, align-self, flex-grow/shrink, opacity, display (only
none), transform, and transition properties. Background, padding, color, font, border, etc. are ignored on instances — use changeElement on the component's root element instead. This is a Plasmic platform constraint, not a preference.
Dynamic Data
Text, attributes, and component props can be bound to runtime data — $props, $state, $ctx (page params/query), $queries / $q (data query results), and repetition locals (currentItem, currentIndex).
- Write bindings as inline
{{ jsExpr }} interpolation. Content is static by default; wrapping JS in {{ }} makes it dynamic (also used for non-string literals, e.g. "{{ 10 }}").
- Before binding,
read({ dataContext: [{ componentUuid, elementUuid }] }) to see which paths exist, then drill in with paths / maxArrayItems. Reference only paths it returns.
- Repetition:
data-repeat="{{ $q.myQuery.data }}" in insertHtml, or repeat: { collection: "..." } in changeElement; bind the subtree with {{ currentItem.* }}.
- Visibility:
data-visible-if="{{ ... }}" / data-visibility="displayNone", or visibility: { showIf: "..." } in changeElement.
- A prop wired to the enclosing component's prop reads back as
{{ $props.<name> }}, and a link-to-page destination as its URL with dynamic parts inlined (e.g. /products/{{ $state.slug }}).
Interactions
An element's event handler is an ordered list of interaction steps, surfaced by read under component interactions rather than in the element's props/attrs (a handler forwarded from a prop as {{ $props.<name> }}). Manage them with createElementInteractions, changeElementInteractions, and deleteElementInteractions.
eventName is a DOM event prop for tags (e.g. onClick) or the name of a function-typed prop for component instances (e.g. On click). Use the exact name read reports.
- Prefer structured actions (
updateVariable, updateVariant) over customFunction when one fits. Steps whose action kind is outside the writable action schema (e.g. navigation) are still listed by read, but can only be renamed or deleted.
- Run-code bodies see
$state, $props, $ctx, $refs, $queries / $q and the event args; later steps read earlier results as $steps.<stepName>.
HTML Code Guidelines
Before generating HTML, read references/html-constraints.md for the full set of rules. The key points:
- Use
<style> blocks with BEM-style class names instead of inline styles.
- Use flex layout exclusively (Plasmic does not support CSS Grid).
- Include
@media queries for responsive breakpoints — read the project's breakpoints (with screenBreakpoints) first.
- Use Google Fonts (single name, no fallback lists).
- Use inline SVG for icons,
https://placehold.co for placeholder images.
<slot-target name="slotName">default content</slot-target> defines a named slot when building a reusable component; slots cannot be nested.
- No JavaScript, no vendor prefixes, no
data:image/svg+xml, no currentColor, no :root.
Design Quality
Before generating designs, read references/design-guidelines.md for aesthetic principles and design thinking guidance.
Design Tokens
Read the project's tokens before generating designs, then:
- Prefer existing tokens over hardcoded values for consistency. Reference them as
var(--token-<uuid>).
- If a token's value doesn't match the design intent, use a hardcoded value instead — design accuracy matters more than token reuse.
- Not every value needs a token. Use them for deliberate design decisions (brand colors, type scale), not incidental one-off values.
Modifying Existing Components
- Small changes (color, spacing) — apply directly with
changeElement or insertHtml.
- Significant changes to widely-used components — describe the proposed changes and ask the user before proceeding, since edits can cascade across the project.
- Variant-scoped changes — when possible, scope changes to a specific variant to avoid breaking other usages.
Response Format
- Be brief and action-oriented. Lead with what you did.
- Summarize changes in 1-2 sentences.
- Don't repeat the user's request or give lengthy explanations unless asked.