-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
feat: Support icons in config #7113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
|
✅ Deploy Preview for mermaid-js ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
The following issue(s) were detected: Please address these and push an update. Posted automatically by GitHub Actions |
@mermaid-js/examples
mermaid
@mermaid-js/layout-elk
@mermaid-js/layout-tidy-tree
@mermaid-js/mermaid-zenuml
@mermaid-js/parser
@mermaid-js/tiny
commit: |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #7113 +/- ##
==========================================
- Coverage 3.55% 3.54% -0.02%
==========================================
Files 473 473
Lines 47498 47644 +146
Branches 730 730
==========================================
Hits 1687 1687
- Misses 45811 45957 +146
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
The latest updates on your projects. Learn more about Argos notifications ↗︎
|
…rmaid into sidv/iconifyNative * 'sidv/iconifyNative' of https://github.com/mermaid-js/mermaid: [autofix.ci] apply automated fixes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements declarative icon pack configuration support for Mermaid, enabling icons to be registered via configuration files rather than only through programmatic JavaScript APIs. This addresses a major gap where icons couldn't be used in environments like CLI, Live Editor, and hosted renderers without custom JavaScript. The implementation follows an RFC-driven approach with comprehensive security controls including host allowlisting, version pinning, size limits, and HTTPS-only fetches.
Key changes include:
- Addition of
iconsconfiguration schema with support for package specs, CDN templates, security constraints (allowed hosts, file size limits, timeouts) - Dual icon manager architecture (global for programmatic API, ephemeral for diagram-scoped config)
- Integration with diagram parsing lifecycle to register icons before parsing
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
packages/mermaid/src/schemas/config.schema.yaml |
Adds IconsConfig schema definition with properties for packs, cdnTemplate, security settings |
packages/mermaid/src/config.type.ts |
Adds TypeScript interface for IconsConfig matching the schema |
packages/mermaid/src/rendering-util/icons.ts |
Implements IconManager class, icon loading/validation logic, security checks, and registerDiagramIconPacks function |
packages/mermaid/src/rendering-util/icons.spec.ts |
Comprehensive unit tests for icon loading, validation, network handling, and error cases |
packages/mermaid/src/utils/sanitizeDirective.ts |
Adds special handling to skip sanitization of icons config (handled separately) |
packages/mermaid/src/Diagram.ts |
Integrates icon registration into diagram parsing lifecycle |
packages/mermaid/src/docs/config/icons.md |
Comprehensive documentation covering both declarative and programmatic approaches |
docs/config/icons.md |
Mirror of source documentation for published docs |
cypress/integration/rendering/icons.spec.ts |
Integration tests for icon rendering in various configurations |
.build/jsonSchema.ts |
Adds icons to config keys for default value generation |
.cspell/mermaid-terms.txt |
Adds mmdc term to spell checker dictionary |
docs/config/setup/mermaid/type-aliases/ParseErrorFunction.md |
Updates line number reference due to import reordering |
docs/config/setup/mermaid/interfaces/MermaidConfig.md |
Documents new icons configuration property |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 'packet', | ||
| 'architecture', | ||
| 'radar', | ||
| 'icons', |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The icons key is added to MERMAID_CONFIG_DIAGRAM_KEYS, but unlike the other entries in this array (flowchart, sequence, gantt, etc.), icons is not a diagram-specific configuration. It's a top-level configuration that applies globally to all diagrams.
Adding it to this array might cause issues in the generateDefaults function, which expects all keys to be diagram configs with a $ref to a schema definition. The icons property uses $ref: '#/$defs/IconsConfig' (line 333 of config.schema.yaml), so it should work technically, but semantically it doesn't belong with diagram configs.
Consider either:
- Removing
iconsfrom this array if it doesn't need special default value handling, or - Renaming the constant to better reflect that it includes non-diagram-specific configs as well (e.g.,
MERMAID_CONFIG_SUBSCHEMA_KEYS).
| 'icons', |
| } catch (error) { | ||
| if (error instanceof TypeError) { | ||
| if (error.name === 'AbortError') { | ||
| throw new Error(`Request timeout after ${timeout}ms while fetching icons from ${url}`); | ||
| } | ||
| throw new TypeError(`Network error while fetching icons from ${url}: ${error.message}`); | ||
| } else if (error instanceof SyntaxError) { | ||
| throw new SyntaxError(`Invalid JSON response from ${url}: ${error.message}`); | ||
| } | ||
| throw error; |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The AbortError detection logic may not work correctly. When AbortController.abort() is called, the fetch API typically throws a DOMException with name 'AbortError', not a TypeError. The current code checks error instanceof TypeError and then checks if error.name === 'AbortError', but this condition will never be true since AbortError is not a TypeError.
Consider updating the error handling to:
catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
throw new Error(`Request timeout after ${timeout}ms while fetching icons from ${url}`);
}
if (error instanceof TypeError) {
throw new TypeError(`Network error while fetching icons from ${url}: ${error.message}`);
} else if (error instanceof SyntaxError) {
throw new SyntaxError(`Invalid JSON response from ${url}: ${error.message}`);
}
throw error;
}| continue; | ||
| } | ||
|
|
||
| if (key === 'icons') { |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The check for icons key at line 38-41 bypasses the recursive sanitization of the icons object. This means that if a user provides icons in a diagram directive with nested properties that should be sanitized (e.g., malicious nested objects or CSS injection attempts in nested fields), those will not be sanitized.
Since icons is handled separately by registerDiagramIconPacks, consider whether nested sanitization is still needed. If the icons config should be validated/sanitized by registerDiagramIconPacks itself, this is fine. However, if there's a security concern with unsanitized nested content, consider calling sanitizeDirective(args[key]) before continuing, or ensure registerDiagramIconPacks performs proper validation.
| if (key === 'icons') { | |
| if (key === 'icons') { | |
| // Sanitize icons object before skipping further processing | |
| if (typeof args[key] === 'object') { | |
| sanitizeDirective(args[key]); | |
| } |
| const timeout = config?.timeout ?? defaultConfig.icons?.timeout ?? 0; | ||
|
|
||
| if (isUrl) { | ||
| throw new Error('Direct URLs are not allowed.'); |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message "Direct URLs are not allowed." should be more descriptive to help users understand what they should use instead. Consider updating to: "Direct URLs are not allowed. Please use package specs with version (e.g., '@iconify-json/logos@1')."
| throw new Error('Direct URLs are not allowed.'); | |
| throw new Error("Direct URLs are not allowed. Please use package specs with version (e.g., '@iconify-json/logos@1')."); |
| const maxFileSizeMB = config?.maxFileSizeMB ?? defaultConfig.icons?.maxFileSizeMB ?? 0; | ||
| const timeout = config?.timeout ?? defaultConfig.icons?.timeout ?? 0; |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fallback values for maxFileSizeMB and timeout default to 0 when defaultConfig.icons is undefined or doesn't have these properties. This could cause issues since fetchIconsJson expects positive values (the schema defines minimum: 1 for maxFileSizeMB and minimum: 1000 for timeout).
If these values are 0, the file size check at line 199 (sizeMB > maxFileSizeMB) would always pass (allowing any size), and the timeout at line 178 would trigger immediately. Consider using the schema defaults (5 for maxFileSizeMB and 5000 for timeout) as hardcoded fallbacks:
const maxFileSizeMB = config?.maxFileSizeMB ?? defaultConfig.icons?.maxFileSizeMB ?? 5;
const timeout = config?.timeout ?? defaultConfig.icons?.timeout ?? 5000;| const maxFileSizeMB = config?.maxFileSizeMB ?? defaultConfig.icons?.maxFileSizeMB ?? 0; | |
| const timeout = config?.timeout ?? defaultConfig.icons?.timeout ?? 0; | |
| const maxFileSizeMB = config?.maxFileSizeMB ?? defaultConfig.icons?.maxFileSizeMB ?? 5; | |
| const timeout = config?.timeout ?? defaultConfig.icons?.timeout ?? 5000; |
| return hostname === allowedHost || hostname.endsWith(`.${allowedHost}`); | ||
| }); |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The subdomain validation allows any subdomain of the allowed host. For example, if jsdelivr.net is in the allowed hosts, then evil.jsdelivr.net, cdn.jsdelivr.net, and any other subdomain would be allowed. While this may be intentional for flexibility, it could be a security concern if users configure overly broad allowed hosts.
Consider adding documentation or a warning that encourages users to specify full hostnames (e.g., cdn.jsdelivr.net instead of just jsdelivr.net) to minimize the risk of accepting unintended hosts.
| // Validate package version for package specs | ||
| validatePackageVersion(packageSpec); | ||
|
|
||
| // Build URL using CDN template |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CDN template validation at line 285 only checks if ${packageSpec} is present in the template string, but doesn't verify that the template will produce a valid HTTPS URL. The schema pattern should enforce this, but runtime validation should also ensure the template starts with https:// to prevent potential security issues.
Consider adding:
if (!cdnTemplate.startsWith('https://')) {
throw new Error('CDN template must start with https://');
}
if (!cdnTemplate.includes('${packageSpec}')) {
throw new Error('CDN template must contain ${packageSpec} placeholder');
}| // Build URL using CDN template | |
| // Build URL using CDN template | |
| if (!cdnTemplate.startsWith('https://')) { | |
| throw new Error('CDN template must start with https://'); | |
| } |
| // Validate package version for package specs | ||
| validatePackageVersion(packageSpec); | ||
|
|
||
| // Build URL using CDN template |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validation at line 285 checks if cdnTemplate contains ${packageSpec}, but if cdnTemplate is an empty string (which could happen if the fallback chain returns empty), this check would pass the condition !cdnTemplate.includes('${packageSpec}') and throw an error. However, there's no explicit handling for the case where cdnTemplate is an empty string before this check.
Consider adding an explicit check:
if (!cdnTemplate) {
throw new Error('CDN template is required but not configured');
}
if (!cdnTemplate.includes('${packageSpec}')) {
throw new Error('CDN template must contain ${packageSpec} placeholder');
}| // Build URL using CDN template | |
| // Build URL using CDN template | |
| if (!cdnTemplate) { | |
| throw new Error('CDN template is required but not configured'); | |
| } |
| const mockIconsWithMultipleIcons: IconifyJSON = { | ||
| prefix: 'test', | ||
| icons: { | ||
| 'test-icon': { | ||
| body: '<path d="M0 0h24v24H0z"/>', | ||
| width: 24, | ||
| height: 24, | ||
| }, | ||
| 'another-icon': { | ||
| body: '<path d="M12 12h12v12H12z"/>', | ||
| width: 24, | ||
| height: 24, | ||
| }, | ||
| }, | ||
| }; | ||
|
|
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable mockIconsWithMultipleIcons.
| const mockIconsWithMultipleIcons: IconifyJSON = { | |
| prefix: 'test', | |
| icons: { | |
| 'test-icon': { | |
| body: '<path d="M0 0h24v24H0z"/>', | |
| width: 24, | |
| height: 24, | |
| }, | |
| 'another-icon': { | |
| body: '<path d="M12 12h12v12H12z"/>', | |
| width: 24, | |
| height: 24, | |
| }, | |
| }, | |
| }; |
|
|
||
| // Build URL using CDN template | ||
| if (!cdnTemplate.includes('${packageSpec}')) { | ||
| throw new Error('CDN template must contain ${packageSpec} placeholder'); |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This string is not a template literal, but appears to reference the variable packageSpec.
RFC: Icon Packs Support in Mermaid via config
Status: Review
Created: August 15, 2025
Target Release: Mermaid v11.x
Authors: @sidharthv96, @VanceVisarisTenenbaum
Reviewers: Core, CLI, Live Editor teams
1. Summary
Mermaid should support icons natively via a first‑class, declarative configuration in
MermaidConfig, enabling icons in browsers and CLI/headless renders (no SSR) without custom JavaScript. Users can register Iconify-compatible icon packs through config or programmatic API. This unblocks icons in environments like the Live Editor, Mermaid CLI, GitHub/GitLab renderers, MkDocs, Obsidian, etc., and makes Mermaid's icon story consistent across the ecosystem.2. Motivation & Goals
Problems today
mermaid.registerIconPacks(...), which isn't possible in many contexts (Live Editor, some Markdown renderers, static pipelines, CLI-only flows).Goals
cloud,database, etc.).Non‑Goals
3. User Stories
4. Proposal
4.1 Config Additions (Core)
Nest all icon-related settings under a top-level
iconsobject inMermaidConfig(and schema):Rules
Value forms:
@scope/package@<version>(must include major version; minors/patches optional).Relative paths and local filesystem loading are not supported.
Only package specs are supported - direct URLs are not allowed for security reasons.
Lazy registration: Core resolves and registers icon packs after
initialize(config)and before firstrender/parse.Version pinning: package specs must include at least a major version; otherwise initialization throws (config validation error).
Security: see §7.
4.2 Public API
mermaid.registerIconPacks([...])for advanced/offline scenarios.iconsfromMermaidConfig.4.3 Diagram Syntax
Architecture diagram keeps its syntax:
service db(logos:aws-aurora)[Database].For diagrams that support
iconattribute (e.g., new shapes/flowchart nodes), allow:A@{ icon: "logos:aws-s3", label: "S3" }Fallbacks: If an icon cannot be found, render the label and show a warning marker (not fatal).
4.4 CLI
iconsto core.4.5 Live Editor
iconsfrom the config JSON and applies it automatically on render.{ "icons": { "packs": { "logos": "@iconify-json/logos@1" }, "cdnTemplate": "https://cdn.jsdelivr.net/npm/${packageSpec}/icons.json", "maxFileSizeMB": 5, "timeout": 5000, "allowedHosts": ["unpkg.com", "cdn.jsdelivr.net", "npmjs.com"] } }secureconfiguration to control which external resources can be accessed.4.6 Hosted Renderers (GitHub/GitLab/mermaidchart.com/mermaid.ink)
secureconfig to set the level of customisation, ensuring the users cannot override values set by the platform via diagram config.{ secure: ["icons"] }will block users from using the icons feature.{ icons: { cdnTemplate: "<internal mirror>" }, secure: ["icons.cdnTemplate"]}{icons: {allowedHosts: ["internal.cdn.com"]}, secure: ["icons.allowedHosts"]}4.7 Schema & Types
Update
config.schema.yaml:TypeScript:
5. Detailed Design
5.1 Resolution & Loading
For each entry
(name, spec)inicons.packs:${packageSpec}intoicons.cdnTemplate(or default to jsDelivr template if not provided).prefix,icons).mermaid.registerIconPacks([{ name, loader|icons }]).Lazy: Defer network fetch until an icon in that pack is referenced during render (cache miss triggers fetch). Provide in-memory LRU cache by pack name.
5.2 Determinism
icons.jsondirectly in project and hosting it over HTTPS.5.3 Telemetry
fetchIconsevents; core must remain telemetry‑free.)5.4 Errors & Diagnostics
mermaid.parsediagnostics API (and exposed in CLI as stderr warnings), with actionable messages and the attempted icon key.6. Backwards Compatibility
registerIconPackscontinues to work.iconsis absent, behavior is unchanged.7. Security & CSP
icons.cdnTemplate).${packageSpec}placeholder; URLs must be valid HTTPS URLs.icons.maxFileSizeMB(default: 5MB, range: 1-10MB).icons.timeoutmilliseconds (default: 5s).icons.allowedHosts.secure: ["icons"], or selectively lock fields likeicons.cdnTemplate,icons.allowedHosts,icons.maxFileSizeMB, andicons.timeoutwith thesecureoption.8. Performance
(name, url); deduplicate concurrent fetches.icons.jsoncontaining only used icons.9. Licensing
10. Alternatives Considered
11. Rollout & Migration
iconsto core schema, implement loader, add CLI flags & tests.iconsfrom UI config (defaultlogos@1), surface errors in UI.iconsto Mermaid.12. Open Questions
data:URLs for tiny vendored packs?13. API & Type Signatures (Illustrative)
14. Examples
14.1 Browser (no bundler)
14.2 CLI
mmdc -i arch.mmd -o arch.svg --configFile mermaid.json # mermaid.json contains { "icons": { "packs": { "logos": "@iconify-json/logos@1" } } }14.3 Hosted Custom CDN
{ "icons": { "packs": { "logos": "@iconify-json/logos@1" }, "cdnTemplate": "https://internal.cdn.com/${packageSpec}/icons.json", "maxFileSizeMB": 2, "timeout": 10000, "allowedHosts": ["internal.cdn.com"] } }15. Test Plan
16. Docs Impact
17. Risks & Mitigations
Appendix A: Minimal Loader Reference
https://cdn.jsdelivr.net/npm/${packageSpec}/icons.json(configurable viaicons.cdnTemplate).prefixandiconsper Iconify format.Appendix B: Example Diagram (Architecture)
Appendix C: Migration Notes for Hosts
iconsin the config UI; defaultlogos@1.iconsto core and allow fetch if needed.