diff --git a/cSpell.json b/cSpell.json index 9e10e41d0c..7b9cbd60d4 100644 --- a/cSpell.json +++ b/cSpell.json @@ -44,6 +44,7 @@ "faber", "flatmap", "foswiki", + "frontmatter", "ftplugin", "gantt", "gitea", diff --git a/cypress/integration/rendering/conf-and-directives.spec.js b/cypress/integration/rendering/conf-and-directives.spec.js index bc17f62361..401a24894b 100644 --- a/cypress/integration/rendering/conf-and-directives.spec.js +++ b/cypress/integration/rendering/conf-and-directives.spec.js @@ -14,7 +14,6 @@ describe('Configuration and directives - nodes should be light blue', () => { `, {} ); - cy.get('svg'); }); it('Settings from initialize - nodes should be green', () => { imgSnapshotTest( @@ -28,7 +27,6 @@ graph TD end `, { theme: 'forest' } ); - cy.get('svg'); }); it('Settings from initialize overriding themeVariable - nodes should be red', () => { imgSnapshotTest( @@ -46,7 +44,6 @@ graph TD `, { theme: 'base', themeVariables: { primaryColor: '#ff0000' }, logLevel: 0 } ); - cy.get('svg'); }); it('Settings from directive - nodes should be grey', () => { imgSnapshotTest( @@ -62,7 +59,24 @@ graph TD `, {} ); - cy.get('svg'); + }); + it('Settings from frontmatter - nodes should be grey', () => { + imgSnapshotTest( + ` +--- +config: + theme: neutral +--- +graph TD + A(Start) --> B[/Another/] + A[/Another/] --> C[End] + subgraph section + B + C + end + `, + {} + ); }); it('Settings from directive overriding theme variable - nodes should be red', () => { @@ -79,7 +93,6 @@ graph TD `, {} ); - cy.get('svg'); }); it('Settings from initialize and directive - nodes should be grey', () => { imgSnapshotTest( @@ -95,7 +108,6 @@ graph TD `, { theme: 'forest' } ); - cy.get('svg'); }); it('Theme from initialize, directive overriding theme variable - nodes should be red', () => { imgSnapshotTest( @@ -111,8 +123,71 @@ graph TD `, { theme: 'base' } ); - cy.get('svg'); }); + it('Theme from initialize, frontmatter overriding theme variable - nodes should be red', () => { + imgSnapshotTest( + ` +--- +config: + theme: base + themeVariables: + primaryColor: '#ff0000' +--- +graph TD + A(Start) --> B[/Another/] + A[/Another/] --> C[End] + subgraph section + B + C + end + `, + { theme: 'forest' } + ); + }); + it('Theme from initialize, frontmatter overriding theme variable, directive overriding primaryColor - nodes should be red', () => { + imgSnapshotTest( + ` +--- +config: + theme: base + themeVariables: + primaryColor: '#00ff00' +--- +%%{init: {'theme': 'base', 'themeVariables':{ 'primaryColor': '#ff0000'}}}%% +graph TD + A(Start) --> B[/Another/] + A[/Another/] --> C[End] + subgraph section + B + C + end + `, + { theme: 'forest' } + ); + }); + + it('should render if values are not quoted properly', () => { + // #ff0000 is not quoted properly, and will evaluate to null. + // This test ensures that the rendering still works. + imgSnapshotTest( + `--- +config: + theme: base + themeVariables: + primaryColor: #ff0000 +--- +graph TD + A(Start) --> B[/Another/] + A[/Another/] --> C[End] + subgraph section + B + C + end + `, + { theme: 'forest' } + ); + }); + it('Theme variable from initialize, theme from directive - nodes should be red', () => { imgSnapshotTest( ` @@ -127,13 +202,11 @@ graph TD `, { themeVariables: { primaryColor: '#ff0000' } } ); - cy.get('svg'); }); describe('when rendering several diagrams', () => { it('diagrams should not taint later diagrams', () => { const url = 'http://localhost:9000/theme-directives.html'; cy.visit(url); - cy.get('svg'); cy.matchImageSnapshot('conf-and-directives.spec-when-rendering-several-diagrams-diagram-1'); }); }); diff --git a/demos/er.html b/demos/er.html index 49f0a683f0..65e3049c8c 100644 --- a/demos/er.html +++ b/demos/er.html @@ -21,6 +21,8 @@
       ---
       title: This is a title
+      config:
+        theme: forest
       ---
       erDiagram
         %% title This is a title
diff --git a/demos/flowchart.html b/demos/flowchart.html
index 92c5bbd6e4..8389510b28 100644
--- a/demos/flowchart.html
+++ b/demos/flowchart.html
@@ -123,6 +123,13 @@ 

graph

flowchart

+    ---
+    title: This is another complicated flow
+    config:
+      theme: base
+      flowchart:
+        curve: cardinal
+    ---
     flowchart LR
     sid-B3655226-6C29-4D00-B685-3D5C734DC7E1["
 
diff --git a/docs/config/configuration.md b/docs/config/configuration.md
index c7b780143d..1e85427ea7 100644
--- a/docs/config/configuration.md
+++ b/docs/config/configuration.md
@@ -10,10 +10,41 @@ When mermaid starts, configuration is extracted to determine a configuration to
 
 - The default configuration
 - Overrides at the site level are set by the initialize call, and will be applied to all diagrams in the site/app. The term for this is the **siteConfig**.
-- Directives - diagram authors can update select configuration parameters directly in the diagram code via directives. These are applied to the render config.
+- Frontmatter (v\+) - diagram authors can update select configuration parameters in the frontmatter of the diagram. These are applied to the render config.
+- Directives (Deprecated by Frontmatter) - diagram authors can update select configuration parameters directly in the diagram code via directives. These are applied to the render config.
 
 **The render config** is configuration that is used when rendering by applying these configurations.
 
+## Frontmatter config
+
+The entire mermaid configuration (except the secure configs) can be overridden by the diagram author in the frontmatter of the diagram. The frontmatter is a YAML block at the top of the diagram.
+
+```mermaid-example
+---
+title: Hello Title
+config:
+  theme: base
+  themeVariables:
+    primaryColor: "#00ff00"
+---
+flowchart
+	Hello --> World
+
+```
+
+```mermaid
+---
+title: Hello Title
+config:
+  theme: base
+  themeVariables:
+    primaryColor: "#00ff00"
+---
+flowchart
+	Hello --> World
+
+```
+
 ## Theme configuration
 
 ## Starting mermaid
diff --git a/docs/config/directives.md b/docs/config/directives.md
index 27fd767c75..414565d53e 100644
--- a/docs/config/directives.md
+++ b/docs/config/directives.md
@@ -6,6 +6,9 @@
 
 # Directives
 
+> **Warning**
+> Directives are deprecated from v\. Please use the `config` key in frontmatter to pass configuration. See [Configuration](./configuration.md) for more details.
+
 ## Directives
 
 Directives give a diagram author the capability to alter the appearance of a diagram before rendering by changing the applied configuration.
diff --git a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md
index 8ab2598855..2082a081ea 100644
--- a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md
+++ b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md
@@ -16,4 +16,4 @@
 
 #### Defined in
 
-[mermaidAPI.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L77)
+[mermaidAPI.ts:78](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L78)
diff --git a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md
index 527b46d092..f84a51b87b 100644
--- a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md
+++ b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md
@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
 
 #### Defined in
 
-[mermaidAPI.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L97)
+[mermaidAPI.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L98)
 
 ---
 
@@ -51,4 +51,4 @@ The svg code for the rendered graph.
 
 #### Defined in
 
-[mermaidAPI.ts:87](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L87)
+[mermaidAPI.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L88)
diff --git a/docs/config/setup/modules/config.md b/docs/config/setup/modules/config.md
index 8381dc8c73..f1de64e2df 100644
--- a/docs/config/setup/modules/config.md
+++ b/docs/config/setup/modules/config.md
@@ -14,7 +14,7 @@
 
 #### Defined in
 
-[config.ts:7](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L7)
+[config.ts:8](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L8)
 
 ## Functions
 
@@ -26,9 +26,9 @@ Pushes in a directive to the configuration
 
 #### Parameters
 
-| Name        | Type  | Description              |
-| :---------- | :---- | :----------------------- |
-| `directive` | `any` | The directive to push in |
+| Name        | Type            | Description              |
+| :---------- | :-------------- | :----------------------- |
+| `directive` | `MermaidConfig` | The directive to push in |
 
 #### Returns
 
@@ -36,7 +36,7 @@ Pushes in a directive to the configuration
 
 #### Defined in
 
-[config.ts:191](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L191)
+[config.ts:188](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L188)
 
 ---
 
@@ -60,7 +60,7 @@ The currentConfig
 
 #### Defined in
 
-[config.ts:137](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L137)
+[config.ts:131](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L131)
 
 ---
 
@@ -118,7 +118,7 @@ The siteConfig
 
 #### Defined in
 
-[config.ts:223](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L223)
+[config.ts:218](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L218)
 
 ---
 
@@ -147,7 +147,7 @@ options in-place
 
 #### Defined in
 
-[config.ts:152](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L152)
+[config.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L146)
 
 ---
 
@@ -242,10 +242,10 @@ The new siteConfig
 
 #### Parameters
 
-| Name          | Type            |
-| :------------ | :-------------- |
-| `siteCfg`     | `MermaidConfig` |
-| `_directives` | `any`\[]        |
+| Name          | Type               |
+| :------------ | :----------------- |
+| `siteCfg`     | `MermaidConfig`    |
+| `_directives` | `MermaidConfig`\[] |
 
 #### Returns
 
@@ -253,7 +253,7 @@ The new siteConfig
 
 #### Defined in
 
-[config.ts:14](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L14)
+[config.ts:15](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L15)
 
 ---
 
diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md
index 2d23977fed..effaec7b13 100644
--- a/docs/config/setup/modules/defaultConfig.md
+++ b/docs/config/setup/modules/defaultConfig.md
@@ -10,7 +10,7 @@
 
 ### configKeys
 
-• `Const` **configKeys**: `string`\[]
+• `Const` **configKeys**: `Set`<`string`>
 
 #### Defined in
 
diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md
index 1160a5dda5..d5d4a1cbc1 100644
--- a/docs/config/setup/modules/mermaidAPI.md
+++ b/docs/config/setup/modules/mermaidAPI.md
@@ -25,7 +25,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
 
 #### Defined in
 
-[mermaidAPI.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L81)
+[mermaidAPI.ts:82](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L82)
 
 ## Variables
 
@@ -96,7 +96,7 @@ mermaid.initialize(config);
 
 #### Defined in
 
-[mermaidAPI.ts:668](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L668)
+[mermaidAPI.ts:673](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L673)
 
 ## Functions
 
@@ -127,7 +127,7 @@ Return the last node appended
 
 #### Defined in
 
-[mermaidAPI.ts:309](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L309)
+[mermaidAPI.ts:310](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L310)
 
 ---
 
@@ -153,7 +153,7 @@ the cleaned up svgCode
 
 #### Defined in
 
-[mermaidAPI.ts:255](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L255)
+[mermaidAPI.ts:256](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L256)
 
 ---
 
@@ -179,7 +179,7 @@ the string with all the user styles
 
 #### Defined in
 
-[mermaidAPI.ts:184](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L184)
+[mermaidAPI.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L185)
 
 ---
 
@@ -202,7 +202,7 @@ the string with all the user styles
 
 #### Defined in
 
-[mermaidAPI.ts:232](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L232)
+[mermaidAPI.ts:233](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L233)
 
 ---
 
@@ -229,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
 
 #### Defined in
 
-[mermaidAPI.ts:168](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L168)
+[mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169)
 
 ---
 
@@ -249,7 +249,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
 
 #### Defined in
 
-[mermaidAPI.ts:154](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L154)
+[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155)
 
 ---
 
@@ -269,7 +269,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
 
 #### Defined in
 
-[mermaidAPI.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L125)
+[mermaidAPI.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L126)
 
 ---
 
@@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code
 
 #### Defined in
 
-[mermaidAPI.ts:286](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L286)
+[mermaidAPI.ts:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287)
 
 ---
 
@@ -320,4 +320,4 @@ Remove any existing elements from the given document
 
 #### Defined in
 
-[mermaidAPI.ts:359](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L359)
+[mermaidAPI.ts:360](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L360)
diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts
index 13fd3232b5..308e141d03 100644
--- a/packages/mermaid/src/Diagram.ts
+++ b/packages/mermaid/src/Diagram.ts
@@ -48,7 +48,7 @@ export class Diagram {
     // extractFrontMatter().
 
     this.parser.parse = (text: string) =>
-      originalParse(cleanupComments(extractFrontMatter(text, this.db)));
+      originalParse(cleanupComments(extractFrontMatter(text, this.db, configApi.addDirective)));
 
     this.parser.parser.yy = this.db;
     this.init = diagram.init;
diff --git a/packages/mermaid/src/assignWithDepth.ts b/packages/mermaid/src/assignWithDepth.ts
index 831825779d..125b6f434a 100644
--- a/packages/mermaid/src/assignWithDepth.ts
+++ b/packages/mermaid/src/assignWithDepth.ts
@@ -1,7 +1,7 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 
 /**
- * assignWithDepth Extends the functionality of {@link ObjectConstructor.assign} with the
+ * assignWithDepth Extends the functionality of {@link Object.assign} with the
  *   ability to merge arbitrary-depth objects For each key in src with path `k` (recursively)
  *   performs an Object.assign(dst[`k`], src[`k`]) with a slight change from the typical handling of
  *   undefined for dst[`k`]: instead of raising an error, dst[`k`] is auto-initialized to `{}` and
diff --git a/packages/mermaid/src/config.spec.ts b/packages/mermaid/src/config.spec.ts
index 457cb82443..8dd4ff33c0 100644
--- a/packages/mermaid/src/config.spec.ts
+++ b/packages/mermaid/src/config.spec.ts
@@ -1,11 +1,13 @@
+/* eslint-disable @typescript-eslint/no-non-null-assertion */
 import * as configApi from './config.js';
+import type { MermaidConfig } from './config.type.js';
 
-describe('when working with site config', function () {
+describe('when working with site config', () => {
   beforeEach(() => {
     // Resets the site config to default config
     configApi.setSiteConfig({});
   });
-  it('should set site config and config properly', function () {
+  it('should set site config and config properly', () => {
     const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
     configApi.setSiteConfig(config_0);
     const config_1 = configApi.getSiteConfig();
@@ -14,19 +16,26 @@ describe('when working with site config', function () {
     expect(config_1.fontSize).toEqual(config_0.fontSize);
     expect(config_1).toEqual(config_2);
   });
-  it('should respect secure keys when applying directives', function () {
-    const config_0 = {
+  it('should respect secure keys when applying directives', () => {
+    const config_0: MermaidConfig = {
       fontFamily: 'foo-font',
+      securityLevel: 'strict', // can't be changed
       fontSize: 12345, // can't be changed
       secure: [...configApi.defaultConfig.secure!, 'fontSize'],
     };
     configApi.setSiteConfig(config_0);
-    const directive = { fontFamily: 'baf', fontSize: 54321 /* fontSize shouldn't be changed */ };
-    const cfg = configApi.updateCurrentConfig(config_0, [directive]);
+    const directive: MermaidConfig = {
+      fontFamily: 'baf',
+      // fontSize and securityLevel shouldn't be changed
+      fontSize: 54321,
+      securityLevel: 'loose',
+    };
+    const cfg: MermaidConfig = configApi.updateCurrentConfig(config_0, [directive]);
     expect(cfg.fontFamily).toEqual(directive.fontFamily);
     expect(cfg.fontSize).toBe(config_0.fontSize);
+    expect(cfg.securityLevel).toBe(config_0.securityLevel);
   });
-  it('should allow setting partial options', function () {
+  it('should allow setting partial options', () => {
     const defaultConfig = configApi.getConfig();
 
     configApi.setConfig({
@@ -42,7 +51,7 @@ describe('when working with site config', function () {
       updatedConfig.quadrantChart!.chartWidth
     );
   });
-  it('should set reset config properly', function () {
+  it('should set reset config properly', () => {
     const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
     configApi.setSiteConfig(config_0);
     const config_1 = { fontFamily: 'baf' };
@@ -55,7 +64,7 @@ describe('when working with site config', function () {
     const config_4 = configApi.getSiteConfig();
     expect(config_4.fontFamily).toEqual(config_0.fontFamily);
   });
-  it('should set global reset config properly', function () {
+  it('should set global reset config properly', () => {
     const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
     configApi.setSiteConfig(config_0);
     const config_1 = configApi.getSiteConfig();
diff --git a/packages/mermaid/src/config.ts b/packages/mermaid/src/config.ts
index e006638275..eb24b6268f 100644
--- a/packages/mermaid/src/config.ts
+++ b/packages/mermaid/src/config.ts
@@ -3,15 +3,16 @@ import { log } from './logger.js';
 import theme from './themes/index.js';
 import config from './defaultConfig.js';
 import type { MermaidConfig } from './config.type.js';
+import { sanitizeDirective } from './utils.js';
 
 export const defaultConfig: MermaidConfig = Object.freeze(config);
 
 let siteConfig: MermaidConfig = assignWithDepth({}, defaultConfig);
 let configFromInitialize: MermaidConfig;
-let directives: any[] = [];
+let directives: MermaidConfig[] = [];
 let currentConfig: MermaidConfig = assignWithDepth({}, defaultConfig);
 
-export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: any[]) => {
+export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: MermaidConfig[]) => {
   // start with config being the siteConfig
   let cfg: MermaidConfig = assignWithDepth({}, siteCfg);
   // let sCfg = assignWithDepth(defaultConfig, siteConfigDelta);
@@ -20,7 +21,6 @@ export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: any[])
   let sumOfDirectives: MermaidConfig = {};
   for (const d of _directives) {
     sanitize(d);
-
     // Apply the data from the directive where the the overrides the themeVariables
     sumOfDirectives = assignWithDepth(sumOfDirectives, d);
   }
@@ -111,12 +111,6 @@ export const getSiteConfig = (): MermaidConfig => {
  * @returns The currentConfig merged with the sanitized conf
  */
 export const setConfig = (conf: MermaidConfig): MermaidConfig => {
-  // sanitize(conf);
-  // Object.keys(conf).forEach(key => {
-  //   const manipulator = manipulators[key];
-  //   conf[key] = manipulator ? manipulator(conf[key]) : conf[key];
-  // });
-
   checkConfig(conf);
   assignWithDepth(currentConfig, conf);
 
@@ -150,9 +144,12 @@ export const getConfig = (): MermaidConfig => {
  * @param options - The potential setConfig parameter
  */
 export const sanitize = (options: any) => {
+  if (!options) {
+    return;
+  }
   // Checking that options are not in the list of excluded options
   ['secure', ...(siteConfig.secure ?? [])].forEach((key) => {
-    if (options[key] !== undefined) {
+    if (Object.hasOwn(options, key)) {
       // DO NOT attempt to print options[key] within `${}` as a malicious script
       // can exploit the logger's attempt to stringify the value and execute arbitrary code
       log.debug(`Denied attempt to modify a secure key ${key}`, options[key]);
@@ -162,7 +159,7 @@ export const sanitize = (options: any) => {
 
   // Check that there no attempts of prototype pollution
   Object.keys(options).forEach((key) => {
-    if (key.indexOf('__') === 0) {
+    if (key.startsWith('__')) {
       delete options[key];
     }
   });
@@ -188,16 +185,14 @@ export const sanitize = (options: any) => {
  *
  * @param directive - The directive to push in
  */
-export const addDirective = (directive: any) => {
-  if (directive.fontFamily) {
-    if (!directive.themeVariables) {
-      directive.themeVariables = { fontFamily: directive.fontFamily };
-    } else {
-      if (!directive.themeVariables.fontFamily) {
-        directive.themeVariables = { fontFamily: directive.fontFamily };
-      }
-    }
+export const addDirective = (directive: MermaidConfig) => {
+  sanitizeDirective(directive);
+
+  // If the directive has a fontFamily, but no themeVariables, add the fontFamily to the themeVariables
+  if (directive.fontFamily && (!directive.themeVariables || !directive.themeVariables.fontFamily)) {
+    directive.themeVariables = { fontFamily: directive.fontFamily };
   }
+
   directives.push(directive);
   updateCurrentConfig(siteConfig, directives);
 };
diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts
index 0b232116da..f8bd9b0b53 100644
--- a/packages/mermaid/src/defaultConfig.ts
+++ b/packages/mermaid/src/defaultConfig.ts
@@ -265,5 +265,5 @@ const keyify = (obj: any, prefix = ''): string[] =>
     return [...res, prefix + el];
   }, []);
 
-export const configKeys: string[] = keyify(config, '');
+export const configKeys: Set = new Set(keyify(config, ''));
 export default config;
diff --git a/packages/mermaid/src/diagram-api/frontmatter.spec.ts b/packages/mermaid/src/diagram-api/frontmatter.spec.ts
index ef05c8f149..03d46c300c 100644
--- a/packages/mermaid/src/diagram-api/frontmatter.spec.ts
+++ b/packages/mermaid/src/diagram-api/frontmatter.spec.ts
@@ -2,8 +2,13 @@ import { vi } from 'vitest';
 import { extractFrontMatter } from './frontmatter.js';
 
 const dbMock = () => ({ setDiagramTitle: vi.fn() });
+const setConfigMock = vi.fn();
 
 describe('extractFrontmatter', () => {
+  beforeEach(() => {
+    setConfigMock.mockClear();
+  });
+
   it('returns text unchanged if no frontmatter', () => {
     expect(extractFrontMatter('diagram', dbMock())).toEqual('diagram');
   });
@@ -75,4 +80,21 @@ describe('extractFrontmatter', () => {
       'tag suffix cannot contain exclamation marks'
     );
   });
+
+  it('handles frontmatter with config', () => {
+    const text = `---
+title: hello
+config:
+  graph:
+    string: hello
+    number: 14
+    boolean: false
+    array: [1, 2, 3]
+---
+diagram`;
+    expect(extractFrontMatter(text, {}, setConfigMock)).toEqual('diagram');
+    expect(setConfigMock).toHaveBeenCalledWith({
+      graph: { string: 'hello', number: 14, boolean: false, array: [1, 2, 3] },
+    });
+  });
 });
diff --git a/packages/mermaid/src/diagram-api/frontmatter.ts b/packages/mermaid/src/diagram-api/frontmatter.ts
index ab09487371..0fd2917eae 100644
--- a/packages/mermaid/src/diagram-api/frontmatter.ts
+++ b/packages/mermaid/src/diagram-api/frontmatter.ts
@@ -1,40 +1,53 @@
-import type { DiagramDB } from './types.js';
+import type { MermaidConfig } from '../config.type.js';
 import { frontMatterRegex } from './regexes.js';
+import type { DiagramDB } from './types.js';
 // The "* as yaml" part is necessary for tree-shaking
 import * as yaml from 'js-yaml';
 
-type FrontMatterMetadata = {
+interface FrontMatterMetadata {
   title?: string;
   // Allows custom display modes. Currently used for compact mode in gantt charts.
   displayMode?: string;
-};
+  config?: MermaidConfig;
+}
 
 /**
  * Extract and parse frontmatter from text, if present, and sets appropriate
  * properties in the provided db.
  * @param text - The text that may have a YAML frontmatter.
  * @param db - Diagram database, could be of any diagram.
+ * @param setDiagramConfig - Optional function to set diagram config.
  * @returns text with frontmatter stripped out
  */
-export function extractFrontMatter(text: string, db: DiagramDB): string {
+export function extractFrontMatter(
+  text: string,
+  db: DiagramDB,
+  setDiagramConfig?: (config: MermaidConfig) => void
+): string {
   const matches = text.match(frontMatterRegex);
-  if (matches) {
-    const parsed: FrontMatterMetadata = yaml.load(matches[1], {
-      // To keep things simple, only allow strings, arrays, and plain objects.
-      // https://www.yaml.org/spec/1.2/spec.html#id2802346
-      schema: yaml.FAILSAFE_SCHEMA,
-    }) as FrontMatterMetadata;
+  if (!matches) {
+    return text;
+  }
 
-    if (parsed?.title) {
-      db.setDiagramTitle?.(parsed.title);
-    }
+  const parsed: FrontMatterMetadata = yaml.load(matches[1], {
+    // To support config, we need JSON schema.
+    // https://www.yaml.org/spec/1.2/spec.html#id2803231
+    schema: yaml.JSON_SCHEMA,
+  }) as FrontMatterMetadata;
 
-    if (parsed?.displayMode) {
-      db.setDisplayMode?.(parsed.displayMode);
-    }
+  if (parsed?.title) {
+    // toString() is necessary because YAML could parse the title as a number/boolean
+    db.setDiagramTitle?.(parsed.title.toString());
+  }
 
-    return text.slice(matches[0].length);
-  } else {
-    return text;
+  if (parsed?.displayMode) {
+    // toString() is necessary because YAML could parse the title as a number/boolean
+    db.setDisplayMode?.(parsed.displayMode.toString());
   }
+
+  if (parsed?.config) {
+    setDiagramConfig?.(parsed.config);
+  }
+
+  return text.slice(matches[0].length);
 }
diff --git a/packages/mermaid/src/directiveUtils.ts b/packages/mermaid/src/directiveUtils.ts
index 563856631a..baf628e74c 100644
--- a/packages/mermaid/src/directiveUtils.ts
+++ b/packages/mermaid/src/directiveUtils.ts
@@ -1,7 +1,5 @@
 import * as configApi from './config.js';
-
 import { log } from './logger.js';
-import { directiveSanitizer } from './utils.js';
 
 let currentDirective: { type?: string; args?: any } | undefined = {};
 
@@ -60,9 +58,6 @@ const handleDirective = function (p: any, directive: any, type: string): void {
           delete directive.args[prop];
         }
       });
-      log.info('sanitize in handleDirective', directive.args);
-      directiveSanitizer(directive.args);
-      log.info('sanitize in handleDirective (done)', directive.args);
       configApi.addDirective(directive.args);
       break;
     }
diff --git a/packages/mermaid/src/docs/config/configuration.md b/packages/mermaid/src/docs/config/configuration.md
index d248944ddd..e52f2c6d5d 100644
--- a/packages/mermaid/src/docs/config/configuration.md
+++ b/packages/mermaid/src/docs/config/configuration.md
@@ -4,10 +4,28 @@ When mermaid starts, configuration is extracted to determine a configuration to
 
 - The default configuration
 - Overrides at the site level are set by the initialize call, and will be applied to all diagrams in the site/app. The term for this is the **siteConfig**.
-- Directives - diagram authors can update select configuration parameters directly in the diagram code via directives. These are applied to the render config.
+- Frontmatter (v+) - diagram authors can update select configuration parameters in the frontmatter of the diagram. These are applied to the render config.
+- Directives (Deprecated by Frontmatter) - diagram authors can update select configuration parameters directly in the diagram code via directives. These are applied to the render config.
 
 **The render config** is configuration that is used when rendering by applying these configurations.
 
+## Frontmatter config
+
+The entire mermaid configuration (except the secure configs) can be overridden by the diagram author in the frontmatter of the diagram. The frontmatter is a YAML block at the top of the diagram.
+
+```mermaid-example
+---
+title: Hello Title
+config:
+  theme: base
+  themeVariables:
+    primaryColor: "#00ff00"
+---
+flowchart
+	Hello --> World
+
+```
+
 ## Theme configuration
 
 ## Starting mermaid
diff --git a/packages/mermaid/src/docs/config/directives.md b/packages/mermaid/src/docs/config/directives.md
index c85d1d245e..5ce9fba6da 100644
--- a/packages/mermaid/src/docs/config/directives.md
+++ b/packages/mermaid/src/docs/config/directives.md
@@ -1,5 +1,9 @@
 # Directives
 
+```warning
+Directives are deprecated from v. Please use the `config` key in frontmatter to pass configuration. See [Configuration](./configuration.md) for more details.
+```
+
 ## Directives
 
 Directives give a diagram author the capability to alter the appearance of a diagram before rendering by changing the applied configuration.
diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts
index f8a36f88e6..0a5fae5758 100644
--- a/packages/mermaid/src/mermaidAPI.ts
+++ b/packages/mermaid/src/mermaidAPI.ts
@@ -23,13 +23,14 @@ import { attachFunctions } from './interactionDb.js';
 import { log, setLogLevel } from './logger.js';
 import getStyles from './styles.js';
 import theme from './themes/index.js';
-import utils, { directiveSanitizer } from './utils.js';
+import utils from './utils.js';
 import DOMPurify from 'dompurify';
 import { MermaidConfig } from './config.type.js';
 import { evaluate } from './diagrams/common/common.js';
 import isEmpty from 'lodash-es/isEmpty.js';
 import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js';
 import { parseDirective } from './directiveUtils.js';
+import { extractFrontMatter } from './diagram-api/frontmatter.js';
 
 // diagram names that support classDef statements
 const CLASSDEF_DIAGRAMS = [
@@ -385,10 +386,14 @@ const render = async function (
 
   configApi.reset();
 
-  // Add Directives. Must do this before getting the config and before creating the diagram.
+  // We need to add the directives before creating the diagram.
+  // So extractFrontMatter is called twice. Once here and once in the diagram parser.
+  // This can be fixed in a future refactor.
+  extractFrontMatter(text, {}, configApi.addDirective);
+
+  // Add Directives.
   const graphInit = utils.detectInit(text);
   if (graphInit) {
-    directiveSanitizer(graphInit);
     configApi.addDirective(graphInit);
   }
 
diff --git a/packages/mermaid/src/schemas/config.schema.yaml b/packages/mermaid/src/schemas/config.schema.yaml
index 6e5f48d95e..29e7455629 100644
--- a/packages/mermaid/src/schemas/config.schema.yaml
+++ b/packages/mermaid/src/schemas/config.schema.yaml
@@ -13,10 +13,10 @@
 #     Non-JSON values, like functions or `undefined`, still need to be manually
 #     set in `src/defaultConfig.ts`)
 #   - `src/docs.mts`
-#     Used to genereate Markdown documentation for this JSON Schema by using
+#     Used to generate Markdown documentation for this JSON Schema by using
 #     the `@adobe/jsonschema2md` NPM package.
 
-# Useful things to know when editting this file
+# Useful things to know when editing this file
 # - Use the `|` character for multi-line strings
 # - Use `meta:enum` to document enum values (from jsonschema2md)
 # - Use `tsType` to override the TypeScript type (from json-schema-to-typescript)
@@ -1851,7 +1851,7 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
           The color of the links in the sankey diagram.
         anyOf:
           - $ref: '#/$defs/SankeyLinkColor'
-          - description: An arbtirary [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value)
+          - description: An arbitrary [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value)
             type: string
         default: gradient
       nodeAlignment:
diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts
index 0c5eca2aee..be35f4291d 100644
--- a/packages/mermaid/src/utils.ts
+++ b/packages/mermaid/src/utils.ts
@@ -97,32 +97,36 @@ const directiveWithoutOpen =
  * @param config - Optional mermaid configuration object.
  * @returns The json object representing the init passed to mermaid.initialize()
  */
-export const detectInit = function (text: string, config?: MermaidConfig): MermaidConfig {
+export const detectInit = function (
+  text: string,
+  config?: MermaidConfig
+): MermaidConfig | undefined {
   const inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/);
   let results = {};
 
   if (Array.isArray(inits)) {
     const args = inits.map((init) => init.args);
-    directiveSanitizer(args);
-
+    sanitizeDirective(args);
     results = assignWithDepth(results, [...args]);
   } else {
     results = inits.args;
   }
-  if (results) {
-    let type = detectType(text, config);
-    ['config'].forEach((prop) => {
-      if (results[prop] !== undefined) {
-        if (type === 'flowchart-v2') {
-          type = 'flowchart';
-        }
-        results[type] = results[prop];
-        delete results[prop];
-      }
-    });
+
+  if (!results) {
+    return;
   }
 
-  // Todo: refactor this, these results are never used
+  let type = detectType(text, config);
+  ['config'].forEach((prop) => {
+    if (results[prop] !== undefined) {
+      if (type === 'flowchart-v2') {
+        type = 'flowchart';
+      }
+      results[type] = results[prop];
+      delete results[prop];
+    }
+  });
+
   return results;
 };
 
@@ -843,67 +847,63 @@ export const entityDecode = function (html: string): string {
  *
  * @param args - Directive's JSON
  */
-export const directiveSanitizer = (args: any) => {
-  log.debug('directiveSanitizer called with', args);
-  if (typeof args === 'object') {
-    // check for array
-    if (args.length) {
-      args.forEach((arg) => directiveSanitizer(arg));
-    } else {
-      // This is an object
-      Object.keys(args).forEach((key) => {
-        log.debug('Checking key', key);
-        if (key.startsWith('__')) {
-          log.debug('sanitize deleting __ option', key);
-          delete args[key];
-        }
+export const sanitizeDirective = (args: unknown): void => {
+  log.debug('sanitizeDirective called with', args);
 
-        if (key.includes('proto')) {
-          log.debug('sanitize deleting proto option', key);
-          delete args[key];
-        }
+  // Return if not an object
+  if (typeof args !== 'object' || args == null) {
+    return;
+  }
 
-        if (key.includes('constr')) {
-          log.debug('sanitize deleting constr option', key);
-          delete args[key];
-        }
+  // Sanitize each element if an array
+  if (Array.isArray(args)) {
+    args.forEach((arg) => sanitizeDirective(arg));
+    return;
+  }
 
-        if (key.includes('themeCSS')) {
-          log.debug('sanitizing themeCss option');
-          args[key] = sanitizeCss(args[key]);
-        }
-        if (key.includes('fontFamily')) {
-          log.debug('sanitizing fontFamily option');
-          args[key] = sanitizeCss(args[key]);
-        }
-        if (key.includes('altFontFamily')) {
-          log.debug('sanitizing altFontFamily option');
-          args[key] = sanitizeCss(args[key]);
-        }
-        if (!configKeys.includes(key)) {
-          log.debug('sanitize deleting option', key);
-          delete args[key];
-        } else {
-          if (typeof args[key] === 'object') {
-            log.debug('sanitize deleting object', key);
-            directiveSanitizer(args[key]);
-          }
-        }
-      });
+  // Sanitize each key if an object
+  for (const key of Object.keys(args)) {
+    log.debug('Checking key', key);
+    if (
+      key.startsWith('__') ||
+      key.includes('proto') ||
+      key.includes('constr') ||
+      !configKeys.has(key) ||
+      args[key] == null
+    ) {
+      log.debug('sanitize deleting key: ', key);
+      delete args[key];
+      continue;
+    }
+
+    // Recurse if an object
+    if (typeof args[key] === 'object') {
+      log.debug('sanitizing object', key);
+      sanitizeDirective(args[key]);
+      continue;
+    }
+
+    const cssMatchers = ['themeCSS', 'fontFamily', 'altFontFamily'];
+    for (const cssKey of cssMatchers) {
+      if (key.includes(cssKey)) {
+        log.debug('sanitizing css option', key);
+        args[key] = sanitizeCss(args[key]);
+      }
     }
   }
+
   if (args.themeVariables) {
-    const kArr = Object.keys(args.themeVariables);
-    for (const k of kArr) {
+    for (const k of Object.keys(args.themeVariables)) {
       const val = args.themeVariables[k];
-      if (val && val.match && !val.match(/^[\d "#%(),.;A-Za-z]+$/)) {
+      if (val?.match && !val.match(/^[\d "#%(),.;A-Za-z]+$/)) {
         args.themeVariables[k] = '';
       }
     }
   }
   log.debug('After sanitization', args);
 };
-export const sanitizeCss = (str) => {
+
+export const sanitizeCss = (str: string): string => {
   let startCnt = 0;
   let endCnt = 0;
 
@@ -1020,8 +1020,8 @@ export default {
   random,
   runFunc,
   entityDecode,
-  initIdGenerator: initIdGenerator,
-  directiveSanitizer,
+  initIdGenerator,
+  sanitizeDirective,
   sanitizeCss,
   insertTitle,
   parseFontSize,