Creating CMS Content
Universal skill for creating Salesforce CMS content records — orchestrates type discovery, workspace resolution, and
dispatch against an existing
.
Content generation is server-side, always. /
update_cms_content_variant
take the goal / edit instruction as
; the server generates + validates the body against the schema and returns the persisted record. Agent never walks the schema, assembles
, pre-validates, or regenerates rejected fields (Principle 3).
Scope
Creates managed content records in Salesforce CMS — instances authored against an existing
.
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
/
— 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
, never refuse up front or propose a REST / UI workaround.
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
(≥1×), each carrying the goal via
.
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
.
Bulk exception: Step 4 presents topic prompts for approval.
VERBATIM QUESTION CONTRACT — EVERY , no exceptions. Full rule →
. Every question goes through
(never plain text); before ANY call, open
and copy BOTH the
string AND the
array
character-for-character, filling only
— never reword/shorten the
, never add/drop/reorder
, never put list content into an answer. No template covers it? STOP and re-read
— never compose one ad hoc.
Workflow Overview
text
- [ ] 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 " | 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) | disambiguation (Create new / Update existing / Publish existing / Cancel), route on the answer |
No-drift STOP rule (every route). If the route's required
tool (
/
update_cms_content_variant
/
) is unavailable on this bridge,
STOP, print the actionable user-facing message, end the turn. Never substitute SOQL,
, CLI, a sibling tool, or the
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 comes ONLY from (a) the session registry or (b) a user-typed identifier (//). Name-only, no ID, not in registry → STOP and ask via ("Which record should I work with?" question, copied EXACTLY, answers ). You may NOT locate the record yourself — no , discovery/listing tool, SOQL, or guessed ID.
- Resolve by prefix + validate before acting →
references/identifier-resolution.md
(→; /→). 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:
text
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
, 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
, 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
.
Multi-type gate — the ONE question Step 1 may raise. If the prompt spans
≥2 distinct content TYPES (each its own
, e.g.
AND
), 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 +
), carry ONLY the chosen domain forward as
. SOLE exception to "never ask" — fires only on ≥2-distinct-types, never goal/mode/quantity. Multiple topics of ONE type (
) 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_]*$
(). Capture , Step 3. Silent.
- Bucket 2 — Auto-correctable near-miss. Single-underscore between known namespace and name (→). Auto-correct, announce in chat (no ), Step 3.
- Bucket 3 — Low-confidence FQN-ish. Bare name (→propose ) 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 (, , , ) → do NOT delegate as free text; dispatch the FQN-correction prompt (
Let me provide a different FQN
/), restart Step 2 on the new string.
- Bucket 4 — Natural language or no token (). Delegate — Step 2a.
Fast-path invariants (Buckets 1/2/3): once the FQN is captured, do NOT verify it in the org, delegate, scan
, dispatch
, retrieve, or read
. A bad FQN fails deterministically as
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 →
), print banner
Resolving the content type for your request…
, invoke via
mcp__skill_bridge__load_skill("experience-cms-content-type-generate")
(NOT built-in
— returns
), pass
{ intent: "<user message verbatim>", suppressCreateContentPrompt: true, <scope> }
(
=
OR
, exactly one, never
); 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
(ignore any
), route strictly on
:
| Action for this skill |
|---|
| ( present) | Print the sibling's verbatim (carries type + deploy line); if empty, print <fqn>`.` Then Step 2c. |
| ( present) | STOP. Print verbatim. Do NOT continue to Step 3 — fails against a local-only bundle. |
| STOP silently. Print and exit. Do NOT loop back into the sibling. |
| STOP. Print verbatim (carries reason + recovery hint). Do NOT retry or work around. |
| (bridge /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 → first. |
2c (only on
) — verify
matches the FQN regex; missing/malformed → surface re-run message +
(
Retry with an explicit FQN
/
); then capture
, Step 3.
2d — never call
,
, re-present/re-pick, or build FQNs from folder names (sibling owns all of that); five drift signals → hard-stop to the
row; never downgrade
to
. 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
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
question with all options.
Full discovery + routing + validation → references/workspace-resolution.md
. In short:
- Discovery (silent): scan under each package path (fallback: recursive), read each →
{ developerName, masterLabel }
. Zero → "no local UIBundles" variant. Never invent a workspace, run SOQL, or read .
- Ask (single question): dispatch the "Step 3 — Workspace resolution" template from via (never plain text); copy + VERBATIM, reword/rename/drop nothing. No-UIBundle variant is fixed copy — :
"Which content space or folder should I create the content in? Provide the ID (Content Space IDs start with
0Zu9Pu, : . UIBundle-found, manual-ID, and invalid-ID variants → .
- UIBundle pick →
get_or_create_cms_workspace_and_web_app_channel
(developerName, masterLabel); returned = ; record + ; persist to uiBundles/<developerName>/public/content-metadata.json
(merge). Tool failure → retry prompt.
- Manual ID → record (no → Step 10 render hidden); manual-ID prompt with "Go back". Validate (prefix+length necessary NOT sufficient): L1 → / →, 15/18 alphanumeric. L2 on FIRST call touching the space (delegation:
get_content_types_for_workspace
; fast-path: ) — 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 — MUST come from Step 2 (Bucket 1/2/3 fast-path capture OR
sibling outcome). Not run Step 2 (and delegated on Bucket 4)? STOP, back to Step 2. Never invent, guess, or default a type —
picking 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
; server generates + validates the body. Agent only shapes the payload and generates a title. Inputs:
= Step 2
;
= Step 3;
=
(message verbatim for single, or Step 4.B.2-approved topic prompt for bulk).
Title stays agent-side — concise, on-topic, customer-facing, reflecting
.
- Single (quantity = 1):
assets/payloads/create-content-single.json
. Required: , , , (verbatim). Optional (, , , ) only when the user named them. Never include — mutually exclusive with .
- Bulk (quantity > 1): generate N distinct topic prompts, present for approval (two-part: markdown list + ), then build N independent 1-element payloads (shared /; per-item + ). Never combine into one 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
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.
→ 5b;
→ end session.
5b. Dispatch N parallel single-item calls per
references/bulk-batching.md
: exactly N
calls, each a 1-element
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 — 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
:
Retry / Refine goal and retry / Cancel
—
EXCEPT type-class-unsupported (
/ 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
(single or bulk variant by quantity). Route:
- → Step 8
- / → Step 9 with all variant IDs
- → ask which items by number via the publish-subset template in , then Step 9 with selected variant IDs
- → 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
, write via
). Load-bearing summary:
- Step 8 — Edit (
update_cms_content_variant
). 8a scope (single, or Step 8a prompt for bulk) → 8b edit-instructions prompt (verbatim, applies to all selected) → 8c payload ( from item's latest response OR the Step 0-U ID; verbatim; re-emit 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 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 ( via ). 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 draft→ (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 /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 ( includes render, hides it). In-session publish ALWAYS dispatches this for BOTH methods — skipping to is a regression. → 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. → hand off to
experience-cms-content-render
with , (ONLY ), , , ; zero published → print the "no published items" line + re-dispatch. →
Error Handling
references/error-recovery.md
— full error-signal → response mapping:
/
update_cms_content_variant
/
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 + routing in Step 2a–2d).
- Render published content to a UIBundle (Step 10) →
experience-cms-content-render
(handoff params + gate in Step 10).
Key Principles
Full text of all 15 → (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
.