How-to: webresource
Create and manage web resources (HTML/JS/CSS/images) and set them as model-driven app icons. See the CLI reference for every flag.
Create a web resource (type inferred from extension)
crm --json webresource create --name cwx_/scripts/ribbon.js --file ./ribbon.js --solution cwx_crmworx
--file are base64-encoded into the content column. The D365 webresourcetype is inferred from the file extension (.js → 3 / JScript above), so you don't pass a type for a known extension. --display defaults to the --name value when omitted, and --solution sends the MSCRM.SolutionUniqueName header so the resource lands in that solution. create stages by default (no publish) — pass --publish to publish it immediately.
Supported file types
The extension → webresourcetype map (the D365 webresource_webresourcetype option set):
| Extension | Type | Label |
|---|---|---|
.htm, .html |
1 | Webpage (HTML) |
.css |
2 | Style Sheet (CSS) |
.js |
3 | Script (JScript) |
.xml |
4 | Data (XML) |
.png |
5 | PNG |
.jpg, .jpeg |
6 | JPG |
.gif |
7 | GIF |
.xap |
8 | Silverlight (XAP) |
.xsl, .xslt |
9 | Style Sheet (XSL) |
.ico |
10 | ICO |
.svg |
11 | Vector format (SVG) |
.resx |
12 | String (RESX) |
Pass --type <int> to override the inferred type — needed for an extensionless or unusual file. An unknown extension with no --type is rejected with an error asking for an explicit type.
Update content or display name
crm --json webresource update cwx_/scripts/ribbon.js --file ./ribbon.js
update resolves the web resource by its name, then issues a plain PATCH of only the fields you pass — the content from --file and/or --display — not a retrieve-merge-write. At least one of --file / --display is required. --solution and the publish-after-write behavior match create.
Inspect web resources
crm --json webresource get cwx_/scripts/ribbon.js
crm --json webresource list --custom-only
get resolves a web resource by name and prints its record. list returns a table in human mode (full data under --json); --custom-only keeps only unmanaged resources, and --top N limits the rows.
Use a web resource as an app icon
crm --json webresource create --name cwx_/icons/app.svg --file ./app.svg
crm --json app create --name CRMWorx --unique-name cwx_crmworx --icon-webresource cwx_/icons/app.svg
app create --icon-webresource <name|guid> uses that web resource as the app icon. A GUID is used directly; a name is resolved to its id. Omit the flag to keep the platform default icon.
Bulk push a directory — push
push walks a local directory and upserts every file as a web resource in one run:
crm --json webresource push ./webresources --prefix cwx
crm --json webresource push ./webresources --prefix cwx --solution cwx_crmworx
Naming convention
Each file's web resource name is derived deterministically:
<prefix>_<relpath>
where <relpath> is the file's path relative to the DIRECTORY argument, with / separators regardless of OS. A file at webresources/scripts/ribbon.js pushed with --prefix cwx becomes cwx_scripts/ribbon.js. The prefix must be 2–8 alphanumeric characters, start with a letter, and not start with mscrm (reserved); an invalid prefix is rejected as a usage error before any call.
Upsert + skip + optional publish-once
For each file the command:
- Fetches the existing web resource by name (a live GET — always runs, even under
--dry-run). - Creates it when it doesn't exist.
- Updates it (PATCH of
content) when the base64 content has changed. - Skips it (no write) when the stored content is byte-identical — cheap no-op.
push stages by default (no publish), same as create/update. Pass
--publish to publish once at the end — a single PublishAllXml fires only
when --publish is passed and at least one file was created or updated
(skipping all files skips the publish too). Without --publish, run
crm solution publish-all afterward once your batch of pushes is done.
Partial-failure behavior
A failing file does not abort the run. Errors are collected in failed and reported at the end. The files that succeeded are still committed (and published, if --publish was passed). Exit code is 1 if any file failed, 0 if all succeeded.
Dry-run preview
The global --dry-run flag runs all the live GETs but issues no writes:
crm --json --dry-run webresource push ./webresources --prefix cwx
Output shows would_create (names that would be created), would_update (names whose content changed), and skipped (byte-identical), so you can audit the changeset before committing.
JSON envelope
Real run (with --publish; omit it and published reports false instead):
{
"ok": true,
"data": {
"pushed": 3,
"updated": 1,
"skipped": 2,
"published": true,
"failed": [],
"files": [
{"name": "cwx_scripts/ribbon.js", "action": "created"},
{"name": "cwx_scripts/form.js", "action": "updated"},
{"name": "cwx_styles/main.css", "action": "skipped"}
]
}
}
Dry-run:
{
"ok": true,
"data": {
"_dry_run": true,
"would_create": ["cwx_scripts/ribbon.js"],
"would_update": ["cwx_scripts/form.js"],
"skipped": 2,
"published": false,
"failed": [],
"files": [...]
}
}
Continuous redeploy loop
push has no --watch flag. Wire it to entr or watchexec for a live-reload loop during front-end development:
# re-push whenever any JS/CSS/HTML file changes
# (find is portable; bash ** globstar is off by default, so `ls **/*` can silently match nothing)
find webresources -type f \( -name '*.js' -o -name '*.css' -o -name '*.html' \) | \
entr crm webresource push webresources --prefix cwx
# watchexec equivalent (no glob expansion needed)
watchexec -e js,css,html -- crm webresource push webresources --prefix cwx
Delete a web resource
crm webresource delete cwx_/scripts/ribbon.js --yes
delete resolves a unique name or a GUID to the record id (a GUID passes through with no lookup) and deletes the web resource. The --yes flag skips the interactive confirmation prompt; omit it on a TTY to confirm interactively. No publish step is needed after a delete.
Pass --check-dependencies to preview blocking dependencies before the delete — it calls RetrieveDependenciesForDelete and folds the result into the output as can_delete and blockers. This is informational only; it does not block or skip the actual delete:
crm --json webresource delete cwx_/scripts/ribbon.js --check-dependencies --yes
Pass the global --dry-run to see what would be deleted without sending the DELETE request.
Web resource referenced by a ribbon button — the server returns 0x8004f01f and the command surfaces it as a clear error. Remove the ribbon button first (crm ribbon remove …), then retry the delete. Use --check-dependencies to identify blockers up front before attempting the delete.