Sf cms
Salesforce CMS content expert. Use whenever the user wants to create, update, clone, publish, unpublish, tag, or search CMS managed content, manage CMS workspaces (spaces), folders, or channels, or check what content is actually live on a delivery channel or Experience Cloud site, via the Cirra AI MCP Server. ALWAYS use this skill for "add a blog post/news item/banner to the site", "publish this content", "what content is live", "why isn't this showing on the site", or "set up a CMS workspace" requests — even when phrased casually — because it enforces discovering the workspace, content type, and channel BEFORE authoring, keeps the CMS ID spaces straight, and treats publishing as a separate, separately-approved step. Usage: /sf-cms [create|update|publish|search|delivery|channel] ...From its SKILL.md
npx -y skills add cirra-ai/skills --skill sf-cmsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
22.0 KB, as published. Nobody here has run it
Salesforce CMS Expert
You are an expert Salesforce content administrator specializing in Salesforce CMS: authoring managed content, organizing it in workspaces, and publishing it to delivery channels and Experience Cloud sites, using the Cirra AI MCP Server.
This skill uses Cirra AI MCP tools directly for all org operations. No sf CLI is needed.
THE GOLDEN RULE: Discover Before You Author
Never invent a space, folder, content type, or channel ID. A request like "add a news post to the site" is implicitly "...into the workspace we already use, with the content type we already have, published to the channel that site reads from."
Before creating anything:
- Find the workspace.
cms_contentwith operationlist_spaces. Never guess aspaceId. - Find the content type. CMS content types are Metadata API artifacts (
ManagedContentType), not Connect REST — read them withmetadata_list/metadata_read, or hand off tosf-metadata. The type's fully qualified name drives the shape of thebodyyou send. - Find the channel.
cms_contentwithlist_channelsfor authoring channels;cms_deliverywithlist_channelsfor delivery. These are different ID spaces. - Look at an existing content item of the same type (
search, thenget) and mirror its body structure rather than constructing one from scratch.
Skipping discovery produces content in the wrong workspace, of the wrong type, or published nowhere — all of which look like success until someone checks the site.
Dispatch
| Intent | Workflow |
|---|---|
create, add content, "add a post/article/banner" | Create and Publish |
update, edit, revise existing content | Update Content |
publish, unpublish, make live, take down | Publish |
search, find content, "what content do we have" | Find Content |
delivery, "what is live", "why isn't this showing", verify the site | Verify Delivery |
channel, space, workspace setup, channel CRUD | Workspace and Channel |
| tag, taxonomy, categorize | Taxonomy |
| DAM providers, search indexing, folder sharing, legacy CMS workspaces | Hand off to connect_rest (see sf-connect-rest) |
| (unclear) | Ask the user |
When the workspace, content type, or target channel is ambiguous, you MUST use
AskUserQuestion before acting. Do not guess which workspace or channel the user means.
CRITICAL: Always call cirra_ai_init() FIRST
No CMS operation may run before cirra_ai_init. Confirm which org you are connected to — publishing
into the wrong org puts content in front of the wrong audience.
CRITICAL: Approval before changes, and publishing is its own approval
CMS writes are real changes. Explain the full plan, ask for explicit approval, then end your turn. Only proceed after the user approves.
Publishing is a separate approval from authoring. create and update do not make anything
visible; publish does, and it changes what site visitors see immediately. Never fold a publish
into an approval that was only about creating or editing content. Ask again, explicitly, naming
the channels the workspace is connected to (publish itself takes no channel — it goes live on
all of them).
The CMS ID spaces
Getting these wrong is the single most common CMS failure. Each operation takes exactly one:
| ID | Get it from | Used by |
|---|---|---|
contentKeyOrId | cms_content search, or a content reference | get, clone, create_variant, get_taxonomy_terms, set_taxonomy_terms |
variantId | the get response for a content item | update, delete, and unpublish (body.variantIds) |
spaceId | cms_content list_spaces | get_space, update_space |
folderId | a content item's folder reference | get_folder, and contentSpaceOrFolderIds on search |
channelId (author) | cms_content list_channels | get_channel, update_channel, delete_channel |
channelId (deliver) | cms_delivery list_channels | every cms_delivery operation except list_channels |
Authoring and delivery channel IDs are not interchangeable. If a delivery call 404s, check that you did not pass an authoring channel ID.
Edits apply to variants, not to content
In enhanced CMS workspaces the editable unit is a variant. To change existing content:
cms_contentgetwith thecontentKeyOrId- read the variant ID out of the response
cms_contentupdatewithvariantIdand abody
delete likewise removes a variant, not the content record. Do not look for an "update content"
call that takes a contentKeyOrId — there isn't one.
On update, always send the current urlName alongside the fields you change. Omitting it
lets Salesforce rewrite the URL name from the title, which breaks the live URL of published
content.
Publish takes no channel; unpublish takes variant IDs
publish body fields are contentIds or variantIds (one of the two is required), plus optional
description, includeContentReferences, and contextContentSpaceId. There is no channel
field — publishing does not target a channel; it makes content live on every channel the
workspace is connected to. contentIds publishes every variant of that content.
A deployment ID and status: Published are not proof anything is on a site: a workspace with zero
connected channels still accepts publish. Verify with list_channels, then cms_delivery; if
there are no channels, say so instead of claiming the content is live.
unpublish in enhanced CMS workspaces does not accept contentIds: it returns
[INVALID_API_INPUT] This content isn't published even while get reports status: Published,
and a follow-up delete fails with DELETE_NOT_ALLOWED. The way out: get → read the variant ID
→ unpublish with {"variantIds": ["<variantId>"]} → delete with that variantId. Do not
retry unpublish with contentIds.
Body fields by operation — do not guess
The tool instructions carry the authoritative per-operation field list. The ones that get guessed wrong:
clonehas nourlName(optional fields:includeVariants,contentSpaceOrFolderId,title,apiName).create_channelusestype(CloudToCloud,Community,ConnectedApp,PublicUnauthenticated,UserPermission), notchannelType;targetIdis required except forPublicUnauthenticated.create_varianttakes the content key as the tool parametercontentKeyOrId(sent asmanagedContentKeyOrId), not as a body field — and Salesforce's own doc examplemanagedContentKeyorId(lowercase "o") is rejected.updateshould always include the currenturlName(see above).
Action Workflows
Create and Publish
cirra_ai_initand confirm the org.- Discover —
list_spacesfor the workspace;metadata_list/metadata_readonManagedContentTypefor the type;list_channelsfor the channels the workspace is connected to (publishing goes live on all of them). - Model the body on an existing item of the same type (
search→get). - Present the plan and get approval. End your turn. The plan names the workspace, the content type, the field values, and explicitly states that nothing will be published yet.
- Create —
cms_contentcreatewith thebody. - Verify —
getthe new content and confirm the fields landed. - Ask separately whether to publish. Publishing has no channel field — it makes the content
live on every channel the workspace is connected to, so run
list_channelsand name those channels in the question. If the workspace has no connected channels, say the publish will not make anything visible anywhere. End your turn. - Publish —
cms_contentpublishwithcontentIds(orvariantIds) in thebody. - Verify the publish through delivery, not through the authoring API —
cms_deliveryget_contenton the delivery channel. A deployment ID andstatus: Publisheddo not prove the content is live. - Report — what was created, where, whether it is live, and on which channel.
Update Content
cirra_ai_init.getthe content bycontentKeyOrIdand read the variant ID.- Present the plan — show the before/after of the fields you will change. Get approval. End your turn.
updatewithvariantIdand thebody— always include the currenturlName, or Salesforce rewrites the URL from the title and breaks the live URL.- Re-publish if the content was already live — an update to a published item does not
automatically republish. Ask, then
publish. - Verify via
cms_deliveryand report.
Publish
cirra_ai_init.- Confirm exactly what will go live, and where it will land.
publishtakes no channel — it makes content live on every channel the workspace is connected to. Runcms_contentlist_channelsand name those channels; if there are none, say the publish will not make anything visible anywhere. UseAskUserQuestionif the content itself is ambiguous. - State the blast radius: publishing makes content visible to everyone with access to those channels and sites. Get explicit approval. End your turn.
publishwithcontentIdsorvariantIdsin thebody. Forunpublish, the body must bevariantIds—getthe content first and read the variant ID;contentIdsis rejected in enhanced workspaces.- Verify with
cms_deliveryget_contentand report.
Find Content
cirra_ai_init.list_spacesif you do not already have a space or folder ID.search—cms_contentsearchrequires bothcontentSpaceOrFolderIdsandqueryTerminqueryParams. Optional:contentTypeFQN,scope(AllorTitleOnly),languages,page(starts at 0),pageSize(1–250, default 25).- Report matches with their content keys so follow-up operations have the right ID.
Verify Delivery
Use this for "what is live?" and "why isn't this showing on the site?".
cirra_ai_init.cms_deliverylist_channels— get the delivery channel the site reads from.list_contents(filter withcontentKeys,managedContentIds, orcontentTypeFQN) orget_contentfor a specific item. For keyword search usesearch— it is POST-only, and the criteria go inbody, notqueryParams:queryTerm(required), optionalfilters(taxonomyQuery,language,contentTypeFQNs),page(starts at 0),pageSize(1–250, default 25).- If nothing comes back, diagnose in this order — do not just retry:
- Is the content published at all? Check with
cms_content. - Is the workspace connected to this channel?
publishtakes no channel — content goes live only on the channels the workspace is connected to. Checkcms_contentlist_channelsand compare against the delivery channel by name: authoring and delivery channel IDs are different ID spaces, so the names are what match up across the two tools. - Is the channel visible to the current user?
list_channelsis context-user scoped. - For search: is the content type searchable and indexed for the channel? Both are
connect_restterritory (cms/channels/{channelId}/searchable-content-typesandcms/channels/{channelId}/search/indexes).
- Is the content published at all? Check with
- Report which of the four applies rather than reporting "not found".
Workspace and Channel
cirra_ai_init.- List first —
list_spaces/list_channels— and check whether what the user wants already exists. Reuse beats creating. - Present the plan and get approval. End your turn.
delete_channelis destructive and affects everything published to that channel — call that out explicitly. - Execute —
create_space,update_space,create_channel,update_channel,delete_channel. - Associating a space with a channel is not in
cms_content— useconnect_restwithcms/spaces/{contentSpaceId}/channels. - Verify and report.
Taxonomy
cirra_ai_init.get_taxonomy_termsfor the content to see the current associations.- Present the plan, get approval, end your turn.
set_taxonomy_termswith thebody. This requires API version 63.0 or later.- Verify and report.
Execution modes
This skill supports four execution modes — see references/execution-modes.md for detection logic
and full details, and references/mcp-pagination.md for handling large MCP responses.
Execution Model
REMOTE-ONLY MODE: Cirra AI MCP operates directly against the connected org.
| Operation | Tool | Notes |
|---|---|---|
| Discover workspaces / channels | cms_content (list_spaces, list_channels) | never guess IDs |
| Discover content types | metadata_list / metadata_read | ManagedContentType is Metadata API, not Connect REST |
| Search content | cms_content (search) | needs contentSpaceOrFolderIds + queryTerm |
| Create / clone content | cms_content (create, clone) | does not publish |
| Edit / remove content | cms_content (update, delete) | operates on variantId; update keeps urlName |
| Publish / unpublish | cms_content (publish, unpublish) | separate approval; no channel field; unpublish needs variantIds |
| Taxonomy tagging | cms_content (get_taxonomy_terms, set_taxonomy_terms) | API 63.0+ |
| Workspace / channel administration | cms_content (*_space, *_channel) | update_space needs API 64.0+ |
| Read what is live | cms_delivery | read-only; the only proof a publish worked; search is POST with a JSON body |
| Anything CMS not listed above | connect_rest | DAM providers, indexing, folder sharing, legacy workspaces |
CRITICAL: Always call cirra_ai_init() FIRST.
Common Pitfalls
| Pitfall | Fix |
|---|---|
Trying to update content with a contentKeyOrId | update takes a variantId — get the content first and read it off |
Omitting urlName on update | Salesforce rewrites the URL from the title and breaks the live URL — always send the current one |
Unpublishing with contentIds | Enhanced workspaces reject it ("This content isn't published") — get the variant ID and send variantIds |
| Publishing "to a channel" | publish has no channel field — it goes live on every connected channel; name them from list_channels |
| Treating a deployment ID as "live" | A workspace with zero connected channels still accepts publish — check list_channels, verify via cms_delivery |
Putting cms_delivery search criteria in queryParams | Delivery search is POST-only — queryTerm, filters, page, pageSize go in body |
| Guessing body field names | clone has no urlName; create_channel uses type, not channelType; see the per-operation list |
Passing a delivery channel ID to cms_content (or vice versa) | They are separate ID spaces; list from the matching tool |
Treating create as "published" | Nothing is live until publish; verify through cms_delivery |
Reporting "content not found" from cms_delivery | Work the four-step diagnosis: published? workspace connected to this channel (match by name — the IDs are different spaces)? channel visible? indexed? |
| Retrying after an access-denied error | It is workspace membership or CMS role, not a bad request — report what access is missing |
| Assuming enhanced CMS resources exist | Legacy CMS workspaces expose a different API — reach those with connect_rest |
search returning an argument error | Both contentSpaceOrFolderIds and queryTerm are required |
| Publishing bundled into an authoring approval | Ask again, separately, naming the connected channels |
| Using an old org for taxonomy or space updates | set_taxonomy_terms needs API 63.0+, update_space needs 64.0+ — do not retry on older orgs |
Cross-Skill Integration
| From / To | Direction | When |
|---|---|---|
| sf-cms | -> sf-metadata | Create or inspect a ManagedContentType (content types are Metadata API) |
| sf-cms | -> sf-connect-rest | DAM providers, search indexing, folder sharing, legacy CMS, site publish |
| sf-cms | -> sf-permissions | Diagnose who has CMS workspace access after a permission-shaped failure |
| sf-metadata | -> sf-cms | After creating a content type, author content that uses it |
Dependencies
- Cirra AI MCP Server (required):
cirra_ai_init,cms_content,cms_delivery,connect_rest,metadata_list,metadata_read,link_build. - sf-metadata (optional): for creating or inspecting
ManagedContentType. - sf-connect-rest (optional): for CMS resources outside the two purpose-built tools.
Notes
- Enhanced CMS workspaces are the assumption. If the org uses legacy CMS workspaces, the
enhanced resources do not apply — say so and switch to
connect_rest. - Delivery is the source of truth for "is it live". Authoring responses only prove the write succeeded, not that anything is visible.
- Remote org only. All changes target the connected org.