Creating CMS Content
Universal skill for creating Salesforce CMS content records — orchestrates type discovery, workspace resolution, and createcmscontent dispatch against an existing ContentTypeBundle.
Content generation is server-side, always. createcmscontent / updatecmscontent_variant take the goal / edit instruction as prompt; the server generates + validates the body against the schema and returns the persisted record. Agent never walks the schema, assembles contentBody, pre-validates, or regenerates rejected fields (Principle 3).
Scope
Creates managed content records in Salesforce CMS — instances authored against an existing ContentTypeBundle.
Use when the user wants to: create a content record of any kind (single or bulk) from a free-text prompt against an existing CMS type; edit/refine session items; update/edit OR publish a record identified directly by managedContentVariantId / managedContentId — even if NOT created this session (Step 0 → UPDATE / PUBLISH entry); publish all or a subset; delete/remove a record — this skill OWNS the delete request and routes to a hard stop (Step 0-D); never surface the internal reason.
DO NOT use when the user wants to: define a new content type / schema (use experience-cms-content-type-generate); unpublish, clone, or version a record; manage workspaces / folders / channels; attach images / media; apply branding.
"Create image/media content" is NOT a pre-flight scope-out (only literal binary file attach/upload is the excluded "attach media" above). Creating an image/video content record (e.g. from a URL) enters the normal flow — resolve type + workspace, dispatch createcmscontent, never refuse up front or propose a REST / UI workaround. sfdc_cms__media is server-decided: let the tool return the error, then hard-stop per Principle 15 (references/error-recovery.md).
Before You Start
Follow steps in order — do not skip or reorder.
Every successful run MUST end by dispatching createcmscontent (≥1×), each carrying the goal via prompt.
Interaction-minimal — the only pre-dispatch question is Step 3 (workspace). Never ask for goal refinement, mode, or body details. Step 1 is silent. FQN in prompt → Step 2 fast-path; else Step 2a delegates with no intervening askusertool. Bulk exception: Step 4 presents topic prompts for approval.
VERBATIM QUESTION CONTRACT — EVERY askusertool, no exceptions. Full rule → references/ux-rules.md. Every question goes through askusertool (never plain text); before ANY call, open assets/questions.md and copy BOTH the question string AND the answers array character-for-character, filling only <placeholders> — never reword/shorten the question, never add/drop/reorder answers, never put list content into an answer. No template covers it? STOP and re-read assets/questions.md — never compose one ad hoc.
Workflow Overview
- [ ] Step 1: Understand intent (silent — domain, count, FQN if present)
- [ ] Step 2: Resolve content type — fast-path (Buckets 1/2/3) or delegate → contentTypeFqn
- [ ] Step 3: Resolve workspace — UIBundle options or manual ID
- [ ] Step 4: Build payload — single or bulk (topic approval if bulk)
- [ ] Step 5: Dispatch create_cms_content (parallel for bulk)
- [ ] Step 6: Display content body + summary
- [ ] Step 7: Post-action loop — Edit / Publish / End
- [ ] Step 8: Edit — update_cms_content_variant
- [ ] Step 9: Publish — publish_content
- [ ] Step 10: Post-publish — Create more / Render (if UIBundle) / End
Step 0: Route the Intent (run FIRST, always)
Before Step 1, classify into exactly one intent and route. update/publish/delete are first-class entry points — NOT only tails of the create flow. Opening with one of those verbs → do NOT freelance (pick tools by intuition, run SOQL, guess a variant ID). Read the opening verb and route:
| User intent (opening verb) |
Route to |
create, generate, write, draft, author, post, add, compose; "create N <things>" |
CREATE — continue to Step 1 |
| update, edit, change, modify, revise an existing record |
UPDATE entry — Step 0-U |
| publish an existing record |
PUBLISH entry — Step 0-P |
| delete, remove, destroy a record |
DELETE — Step 0-D (hard stop) |
| ambiguous (can't tell create vs update vs publish) |
askusertool disambiguation (Create new / Update existing / Publish existing / Cancel), route on the answer |
No-drift STOP rule (every route). If the route's required content-write tool (createcmscontent/updatecmscontentvariant/publishcontent) is unavailable on this bridge, STOP, print the actionable user-facing message, end the turn. Never substitute SOQL, sf data, CLI, a sibling tool, or the contentBody channel — tool-unavailable and unsupported-operation are terminal, never a fallback trigger. Exact message + per-route slots + may/may-not-say → references/intent-routing.md § No-drift STOP rule.
Step 0-U (UPDATE) and Step 0-P (PUBLISH) — direct-entry paths
Full procedural detail → references/intent-routing.md. Direct-entry counterparts to in-session Step 8 (edit) / Step 9 (publish) — a record identified directly, possibly from a prior session or another user (editing/publishing is NOT session-only). Load-bearing invariants:
- Hard identifier gate — BEFORE any tool call. Target
managedContentVariantId comes ONLY from (a) the session registry or (b) a user-typed identifier (MC…/20Y…/9Ps). Name-only, no ID, not in registry → STOP and ask via askusertool ("Which record should I work with?" question, copied EXACTLY, answers ["Cancel"]). You may NOT locate the record yourself — no search_content, discovery/listing tool, SOQL, or guessed ID.
- Resolve by prefix + validate before acting →
references/identifier-resolution.md (9Ps→getcontentvariant; MC…/20Y…→get_content). Read fails → STOP; never edit/publish blindly.
- 0-U → Step 8c (after MANDATORY full-body display + edit-instructions prompt), closes via Step 7 dialog. 0-P → Step 9b, closes with publish confirmation and ends the turn (no Step 10 — no session context). Neither runs Steps 1–5.
Step 0-D: DELETE (hard stop)
Delete is not available (no delete capability — internal detail, do NOT surface). Print exactly and only this message, nothing else:
Deleting content isn't supported here. To delete this content, open it in Salesforce CMS and use the Delete option there.
Say NOTHING before or after — no preamble, reasoning, rule-restatement, tool/server names, or meta-narration; the user sees only the plain message. End the turn. Do NOT offer unpublish_content, SOQL, or CLI as a substitute — the CMS UI is the only path to surface.
Step 1: Understand Intent
Silent step — NEVER ask anything here. Extract agent-internally, then go straight to Step 2. No "Let me clarify…" turn, no askusertool, no content-type menu — even a vague prompt (generate new content for Q4 launch) has everything Step 2 needs. About to dispatch a question in Step 1 (clarify, content-type menu, "type in mind?", "how many?")? That IS the bug — move to Step 2. Parse the message for:
- Content domain (article, blog, news, FAQ, …) — inferred as-is; no domain named is fine.
- Quantity — explicit count → that number; "multiple"/"several"/"a few"/"some" → agent chooses 4–5; no indicator → single.
- Content-type reference — capture any token that looks like a content type name (exact FQN, single-underscore near-miss, bare developer name, natural-language name) verbatim. Do NOT classify here — Step 2 assigns the bucket.
Do not strip or rewrite the message — the original is the creative direction for Step 4 topic prompts and what Step 2a passes to the sibling verbatim as intent.
Multi-type gate — the ONE question Step 1 may raise. If the prompt spans ≥2 distinct content TYPES (each its own ContentTypeBundle, e.g. grooming packages AND service area coverage), you MUST ask the user to pick ONE before Step 2 (one type per run; two corrupts the delegation contract). Dispatch the Step 1 multi-type prompt VERBATIM (answers = distinct domains in the user's words + Cancel), carry ONLY the chosen domain forward as intent. SOLE exception to "never ask" — fires only on ≥2-distinct-types, never goal/mode/quantity. Multiple topics of ONE type (5 blog posts) do NOT trigger it (bulk). Rule + examples → references/content-type-classification.md § Multi-type gate.
Step 2: Resolve the Content Type
Classify Step 1's captured content-type reference into one of four buckets — full detection rules, examples, routing → references/content-type-classification.md. Summary:
- Bucket 1 — Exact FQN. Matches
^[a-zA-Z][a-zA-Z0-9]__[A-Za-z][A-Za-z0-9_]$ (c__BlogPost). Capture contentTypeFqn, Step 3. Silent.
- Bucket 2 — Auto-correctable near-miss. Single-underscore between known namespace and name (
c_news→c__news). Auto-correct, announce in chat (no askusertool), Step 3.
- Bucket 3 — Low-confidence FQN-ish. Bare name (
BlogPost→propose c__BlogPost) or unknown-namespace single-underscore. Dispatch the Step 2 FQN-correction prompt, route on the answer.
- Invalid-FQN gate. Token meant as FQN (has
) but malformed, not a Bucket 2 near-miss (c, Foo, cBlog Post, c__123) → do NOT delegate as free text; dispatch the FQN-correction prompt (Let me provide a different FQN/Cancel), restart Step 2 on the new string.
- Bucket 4 — Natural language or no token (
blog post). Delegate — Step 2a.
Fast-path invariants (Buckets 1/2/3): once the FQN is captured, do NOT verify it in the org, delegate, scan contentTypes/, dispatch metadata-grounding, retrieve, or read schema.json. A bad FQN fails deterministically as INVALID_TYPE at Step 5. On the delegated path the sibling owns scan, grounding, pick question, create-new + deploy, FQN construction, retrieve-and-reconcile.
2a–2d. Delegate to the sibling (Bucket 4 only)
Full protocol → references/delegation-protocol.md. In short: 2a resolve workspace FIRST (Step 3 → contentSpaceOrFolderId), print banner Resolving the content type for your request…, invoke via mcp__skill_bridge__load_skill("experience-cms-content-type-generate") (NOT built-in Skill — returns Unknown skill), pass { intent: "<user message verbatim>", suppressCreateContentPrompt: true, <scope> } (<scope> = spaceId 0Zu OR folderId 9Pu, exactly one, never baseType); no pre-delegation question. 2b — while the sibling runs this skill is PAUSED (no Step 4+ call until a terminal outcome; interleaving corrupts its state machine). Consume { status, fqn, message } (ignore any schema), route strictly on status:
status |
Action for this skill |
success (fqn present) |
Print the sibling's message verbatim (carries type + deploy line); if empty, print Using content type \<fqn>\. Then Step 2c. |
not_deployed (fqn present) |
STOP. Print message verbatim. Do NOT continue to Step 3 — createcmscontent fails INVALID_TYPE against a local-only bundle. |
cancelled |
STOP silently. Print Session ended. and exit. Do NOT loop back into the sibling. |
error |
STOP. Print message verbatim (carries reason + recovery hint). Do NOT retry or work around. |
load failed (bridge Unknown skill/error — sibling never ran) |
STOP per references/delegation-protocol.md § 2b (terminal, NOT a fallback — no local scan, grounding, or workspace call). Retry once via searchskills→loadskill first. |
2c (only on success) — verify fqn matches the FQN regex; missing/malformed → surface re-run message + askusertool (Retry with an explicit FQN/Cancel); then capture contentTypeFqn, Step 3. 2d — never call metadata-grounding, sf project retrieve, re-present/re-pick, or build FQNs from folder names (sibling owns all of that); five drift signals → hard-stop to the load failed row; never downgrade not_deployed to success. On a manual space the 2a call is the Layer-2 confirmation — invalid/not-found → HARD STOP, re-ask via the invalid-ID template.
Step 3: Resolve the Workspace — Question 1
When Step 3 runs depends on how Step 2 resolved the type: fast-path (Buckets 1/2/3) → run it here, after Step 2. Delegation path (Bucket 4) → runs EARLY, first action of Step 2a before the sibling is dispatched, so contentSpaceOrFolderId scopes the sibling's discovery; do NOT re-run or re-ask after the sibling returns. Same resolution steps both cases.
Silently scan the local project for UIBundle directories, then present a single askusertool question with all options. Full discovery + routing + validation → references/workspace-resolution.md. In short:
- Discovery (silent): scan
uiBundles/ under each sfdx-project.json package path (fallback: recursive), read each <name>.uibundle-meta.xml <masterLabel> → { developerName, masterLabel }. Zero → "no local UIBundles" variant. Never invent a workspace, run SOQL, or read ui-bundle.json.
- Ask (single question): dispatch the "Step 3 — Workspace resolution" template from
assets/questions.md via askusertool (never plain text); copy question + answers VERBATIM, reword/rename/drop nothing. No-UIBundle variant is fixed copy — question: "Which content space or folder should I create the content in? Provide the ID (Content Space IDs start with 0Zu; Folder IDs start with 9Pu).", answers: ["Cancel"]. UIBundle-found, manual-ID, and invalid-ID variants → assets/questions.md.
- UIBundle pick →
getorcreatecmsworkspaceandwebappchannel(developerName, masterLabel); returned spaceId = contentSpaceOrFolderId; record workspaceMethod=uibundle + channelId; persist channelId to uiBundles/<developerName>/public/content-metadata.json (merge). Tool failure → retry prompt.
- Manual ID → record
workspaceMethod=manual (no channelId → Step 10 render hidden); manual-ID prompt with "Go back". Validate (prefix+length necessary NOT sufficient): L1 0Zu→spaceId / 9Pu→folderId, 15/18 alphanumeric. L2 on FIRST call touching the space (delegation: getcontenttypesforworkspace; fast-path: createcmscontent) — invalid/not-found → STOP, surface verbatim, re-ask via invalid-ID template. No org picker.
Step 4: Build the Create-Content Payload
HARD ENTRY GATE — contentTypeFqn MUST come from Step 2 (Bucket 1/2/3 fast-path capture OR status: success sibling outcome). Not run Step 2 (and delegated on Bucket 4)? STOP, back to Step 2. Never invent, guess, or default a type — picking sfdc_cms__news or any base/standard type without Step 2 is a regression. No FQN → Bucket 4 → delegate FIRST. Front-loading the create tool via ToolSearch before Step 2 is the same bug.
Goal passes as prompt; server generates + validates the body. Agent only shapes the payload and generates a title. Inputs: contentType = Step 2 contentTypeFqn; contentSpaceOrFolderId = Step 3; prompt = [USER GOAL] (message verbatim for single, or Step 4.B.2-approved topic prompt for bulk). Title stays agent-side — concise, on-topic, customer-facing, reflecting [USER GOAL].
- Single (quantity = 1):
assets/payloads/create-content-single.json. Required: contentType, contentSpaceOrFolderId, title, prompt (verbatim). Optional (urlName, contentKey, externalId, apiName) only when the user named them. Never include contentBody — mutually exclusive with prompt.
- Bulk (quantity > 1): generate N distinct topic prompts, present for approval (two-part: markdown list +
askusertool), then build N independent 1-element payloads (shared contentType/contentSpaceOrFolderId; per-item title + prompt). Never combine into one inputs array — one call per item. Flow → references/bulk-batching.md.
Full schema → references/content-write-tool.md; payload walkthrough → examples/create-content-call.md.
Step 5: Execute the Create Call
Dispatch createcmscontent with the Step 4 payload; the server generates + validates and returns the record. Complex types can be slower.
Bulk Content
5a. Pre-dispatch confirmation (blanket approval) — one confirmation for all items (prevents per-call prompts). Print chat Ready to create all <N> content items in your org. Proceed?, dispatch the Step 5a approval prompt. Yes, create all → 5b; Cancel → end session.
5b. Dispatch N parallel single-item calls per references/bulk-batching.md: exactly N createcmscontent calls, each a 1-element inputs array, all fanned out in one turn. Partial failure → register successes, surface failures (topic + error), dispatch retry prompt (retry re-dispatches failed items as fresh parallel calls). Total failure → apply "Bulk total-failure retry guards" in references/error-recovery.md first (bulk total-failure branch only).
Step 6: Display Content Body and Summary
On success, print the summary + content-body tables from assets/display-formats.md (real markdown, every field; same for create/edit). NEVER collapse to one-line title · status · id — summary table MUST carry Content Key + Managed Content ID (omit only fields the response lacks). On failure, use references/error-recovery.md; surface the error verbatim, ask via askusertool: Retry / Refine goal and retry / Cancel — EXCEPT type-class-unsupported (sfdc_cms__media / image / video), a hard stop (Principle 15): print ONLY this fixed copy verbatim (no preamble/rewording), then end turn: Content generation isn't supported for content type \<fqn>\ (type class: \<class>\). This skill can't author that type class. To create or edit this content, use the Salesforce CMS UI directly. Server owns validation.
Step 7: Post-Action Loop
After displaying the content body (create or edit), dispatch the Step 7 prompt from assets/questions.md (single or bulk variant by quantity). Route:
Edit content → Step 8
Publish content / Publish all → Step 9 with all variant IDs
Publish subset → ask which items by number via the publish-subset template in assets/questions.md, then Step 9 with selected variant IDs
End session → print one line: Session ended. Content saved as draft. and exit.
Steps 8, 9, 10: Edit / Publish / Post-Publish
Full procedural detail → references/edit-publish-workflow.md. All three MCP-only — no SOQL, no CLI (read via content-readonly, write via content-write). Load-bearing summary:
- Step 8 — Edit (
updatecmscontentvariant). 8a scope (single, or Step 8a prompt for bulk) → 8b edit-instructions prompt (verbatim, applies to all selected) → 8c payload (variantId from item's latest response OR the Step 0-U ID; prompt verbatim; re-emit title ONLY on rename) → 8d dispatch (bulk = N parallel) → 8e back to Step 6, display full updated body, every field (NOT a diff table; re-fetch via getcontent_variant if the response lacks it), then Step 7. Failures → Retry / Refine edit instruction and retry / Cancel; bulk total-failure → error-recovery guards.
- Step 9 — Publish (
publish_content via variantIds). 9a scope (all / subset / default all) → 9b dispatch (in-session IDs, or the Step 0-P validated ID — direct-entry ends the turn after confirmation, no Step 10) → 9c on success flip each published item's registry publishStatus draft→published (hard — Step 10 filters on it), print the assets/display-formats.md publish confirmation VERBATIM (Content published successfully. + Title/Managed Content ID/Variant ID) — NOT reworded Content is live/deployment-id prose, then Step 10. Partial/total-failure → error-recovery guards + retry prompt.
- Step 10 — Post-Publish. Dispatch the Step 10 prompt; options depend on
workspaceMethod (uibundle includes render, manual hides it). In-session publish ALWAYS dispatches this for BOTH methods — skipping to Session ended. is a regression. Create more content → restart FULL flow at Step 1 (silent — no "what next?" ask, wait for user's next message) → Step 2 → Step 3; do NOT reuse this run's workspace. Render… → hand off to experience-cms-content-render with channelId, contentKeys (ONLY publishStatus=published), contentTypeFqn, uiBundleDeveloperName, uiBundleMasterLabel; zero published → print the "no published items" line + re-dispatch. End session → Session ended.
Error Handling
references/error-recovery.md — full error-signal → response mapping: createcmscontent / updatecmscontentvariant / publishcontent server-side failures, bulk partial failures, sibling terminal states, out-of-scope requests.
Cross-Skill Integration
- Discover / define a ContentTypeBundle (Step 2 delegation path only, not fast-path) →
experience-cms-content-type-generate (payload + status routing in Step 2a–2d).
- Render published content to a UIBundle (Step 10) →
experience-cms-content-render (handoff params + workspaceMethod=uibundle gate in Step 10).
Key Principles
Full text of all 15 → references/principles.md (canonical index; each is ALSO enforced inline in its Step body above): (1) workflow in order, (2) dispatch is the work, (3) server-side generation only, (4) user goal verbatim, (5) sibling owns type on delegation, (6) workspace from user/UIBundle — never infer/SOQL, (7) cancel terminates, (8) consistent display, (9) session registry + stable variant ID, (10) post-action loop, (11) bulk = one call per item parallel, (12) publish defaults to all, (13) route intent FIRST + identifier gate, (14) no-drift STOP, (15) type-class-unsupported hard stop. Identifiers → references/identifier-resolution.md.