Enhancement: Custom HTML rendering on the card view #403

Closed
opened 2026-10-05 02:45:16 +02:00 by adam · 13 comments
Owner

Allow custom HTML to be rendered on the card view. This allows authors to extend their dashboards/cards experience.

Allow custom HTML to be rendered on the card view. This allows authors to extend their dashboards/cards experience. <!-- github-sync-id:1598997982 -->
adam closed this issue 2026-10-05 02:45:16 +02:00
Author
Owner

I started using the https://visitorbadge.io service on my blog to get simple visitor stats per page. That gave me the idea to have a custom renderer that allows me to quickly drop some HTML per card to check these stats on the card/dashboard.

The outcome of this looks as follows:

image

This is not yet implemented; just a POC on my end for now to check the possibilities and what the best way would be to get the HTML on the card view.

I started using the https://visitorbadge.io service on my blog to get simple visitor stats per page. That gave me the idea to have a custom renderer that allows me to quickly drop some HTML per card to check these stats on the card/dashboard. The outcome of this looks as follows: ![image](https://user-images.githubusercontent.com/2900833/221244056-f89243d6-c87b-4d61-b633-6d23ce911eea.png) This is not yet implemented; just a POC on my end for now to check the possibilities and what the best way would be to get the HTML on the card view. <!-- github-comment-id:1444065310 -->
Author
Owner

Option 1

Reference is like a script, which will be invoked when the card renders; you can use node/bash/... to execute, all you need to return is the HTML string.

Note

: This would work similarly like custom actions and post scripts.

Update

It will become option 2. The reason for this is that it is more powerful to create dynamic components and generates less overhead.

### ~Option 1~ ~Reference is like a script, which will be invoked when the card renders; you can use node/bash/... to execute, all you need to return is the HTML string.~ > **Note**: ~This would work similarly like custom actions and post scripts.~ ### Update It will become **option 2**. The reason for this is that it is more powerful to create dynamic components and generates less overhead. <!-- github-comment-id:1444123927 -->
Author
Owner

Option 2

Ability to reference a JS file that provides a function like getCardHtml, this will be called when the card gets rendered.

Example:

fmExternal = window.fmExternal || {};

fmExternal.getCardHtml = function (filePath, data) {
  console.log('getCardHtml', filePath, data)
  if (data && data.slug) {
    return `<div style="margin-top:0.5rem">html to render</div>`
  }
  return '';
}

Note

: with this kind of approach, we might be able to get more functionality and extensibility points in and it would also fit in the marketplace idea. It also has less overhead, as there is no external script execution required.

## Option 2 Ability to reference a JS file that provides a function like `getCardHtml`, this will be called when the card gets rendered. Example: ```js fmExternal = window.fmExternal || {}; fmExternal.getCardHtml = function (filePath, data) { console.log('getCardHtml', filePath, data) if (data && data.slug) { return `<div style="margin-top:0.5rem">html to render</div>` } return ''; } ``` > **Note**: with this kind of approach, we might be able to get more functionality and extensibility points in and it would also fit in the marketplace idea. It also has less overhead, as there is no external script execution required. <!-- github-comment-id:1444170289 -->
Author
Owner

Investigating option 2, and must say that this has a lot of potential.

Here is an example of how it can be used with, for instance Handlebars

import handlebars from 'https://cdn.skypack.dev/handlebars';

const getHtml = (data) => {
  const source = `
  <div style="margin-top: .5em; font-style: italic;">
    {{title}}
  </div>
  `;

  const template = handlebars.compile(source);
  return template(data);
}

window.fmExternal = window.fmExternal || {};

fmExternal.getCardHtml = async (filePath, data) => {
  return getHtml(data);
}
Investigating option 2, and must say that this has a lot of potential. Here is an example of how it can be used with, for instance Handlebars ```js import handlebars from 'https://cdn.skypack.dev/handlebars'; const getHtml = (data) => { const source = ` <div style="margin-top: .5em; font-style: italic;"> {{title}} </div> `; const template = handlebars.compile(source); return template(data); } window.fmExternal = window.fmExternal || {}; fmExternal.getCardHtml = async (filePath, data) => { return getHtml(data); } ``` <!-- github-comment-id:1444546371 -->
Author
Owner

Question 1

If we would bring this functionality, where would you like to add your HTML?

Thinking of the following zones:

  • Replace the card header/image
  • Add a card footer

Feel free to suggest other zones to which you want to add your HTML.

## Question 1 If we would bring this functionality, where would you like to add your HTML? Thinking of the following zones: - Replace the card header/image - Add a card footer Feel free to suggest other zones to which you want to add your HTML. <!-- github-comment-id:1445138889 -->
Author
Owner

Registering your extensibility script(s)

"frontMatter.extensibility.scripts": [
  "[[workspace]]/external.js"
],
Registering your extensibility script(s) ```json "frontMatter.extensibility.scripts": [ "[[workspace]]/external.js" ], ``` <!-- github-comment-id:1445191143 -->
Author
Owner

It is now available in the latest beta, be sure to enable the experimental feature setting: "frontMatter.experimental": true.

Register your script

Add the frontMatter.extensibility.scripts setting, with the path to your script. You can use an external URL (https only), or a file reference within your project. You can use the [[workspace]] placeholder.

The script

All you need to do for the script is register the window.fmExternal.getCardFooter method. Example:

window.fmExternal = window.fmExternal || {};

window.fmExternal.getCardFooter = async (filePath, data) => {
  return `<span>This is the HTML to render</span>`
}
It is now available in the latest beta, be sure to enable the experimental feature setting: `"frontMatter.experimental": true`. ### Register your script Add the `frontMatter.extensibility.scripts` setting, with the path to your script. You can use an external URL (https only), or a file reference within your project. You can use the `[[workspace]]` placeholder. ## The script All you need to do for the script is register the `window.fmExternal.getCardFooter` method. Example: ```js window.fmExternal = window.fmExternal || {}; window.fmExternal.getCardFooter = async (filePath, data) => { return `<span>This is the HTML to render</span>` } ``` <!-- github-comment-id:1445378027 -->
Author
Owner

I've just published a new extensibility dependency, which makes registration easier.

Your script its content looks as follows:

import { registerCardFooter } from "https://cdn.skypack.dev/@frontmatter/extensibility";

registerCardFooter(async (filePath, data) => {
  return `<span>Your HTML</span>`;
});
I've just published a new extensibility dependency, which makes registration easier. Your script its content looks as follows: ```js import { registerCardFooter } from "https://cdn.skypack.dev/@frontmatter/extensibility"; registerCardFooter(async (filePath, data) => { return `<span>Your HTML</span>`; }); ``` <!-- github-comment-id:1445396236 -->
Author
Owner

Testing a bit more with lit webcomponents

https://user-images.githubusercontent.com/2900833/221435369-ae65d592-eb86-4eac-a538-e7c63826e4e2.mov

The code

import {
  registerCardFooter
} from "https://cdn.skypack.dev/@frontmatter/extensibility";

import {html, css, LitElement} from 'https://cdn.skypack.dev/lit';

export class SimpleGreeting extends LitElement {
  static styles = css`p { color: #44FFD2 }`;

  static properties = {
    count: {type: Number},
  };

  constructor() {
    super();
    this.count = 0;
  }

  _increment(e) {
    this.count++;
  }

  render() {
    return html`
      <p><button @click="${this._increment}">Click Me!</button></p>
      <p>You've clicked me: ${this.count}!</p>
    `;
  }
}
customElements.define('simple-counter', SimpleGreeting);

registerCardFooter(async (filePath, data) => {
  const slug = encodeURIComponent(data.slug);
  return `
    <simple-counter></simple-counter>
  `;
});

I think this has the potential to allow you to create custom field renderers as well.

Testing a bit more with [lit webcomponents](https://lit.dev/) https://user-images.githubusercontent.com/2900833/221435369-ae65d592-eb86-4eac-a538-e7c63826e4e2.mov **The code** ```js import { registerCardFooter } from "https://cdn.skypack.dev/@frontmatter/extensibility"; import {html, css, LitElement} from 'https://cdn.skypack.dev/lit'; export class SimpleGreeting extends LitElement { static styles = css`p { color: #44FFD2 }`; static properties = { count: {type: Number}, }; constructor() { super(); this.count = 0; } _increment(e) { this.count++; } render() { return html` <p><button @click="${this._increment}">Click Me!</button></p> <p>You've clicked me: ${this.count}!</p> `; } } customElements.define('simple-counter', SimpleGreeting); registerCardFooter(async (filePath, data) => { const slug = encodeURIComponent(data.slug); return ` <simple-counter></simple-counter> `; }); ``` I think this has the potential to allow you to create custom field renderers as well. <!-- github-comment-id:1445459413 -->
Author
Owner

Another place could be a custom view panel.

image

Another place could be a custom view panel. ![image](https://user-images.githubusercontent.com/2900833/221522423-dd0b2b69-e5c6-4d99-b2ff-d48cfc3a1138.png) <!-- github-comment-id:1445966582 -->
Author
Owner
💥 CUSTOM FIELDS 💥 https://user-images.githubusercontent.com/2900833/221549235-221f45f5-005e-4365-bbd0-0941d51c1aee.mov <!-- github-comment-id:1446144209 -->
Author
Owner

Examples have been added here: https://github.com/FrontMatter/extensibility

Examples have been added here: https://github.com/FrontMatter/extensibility <!-- github-comment-id:1447022189 -->
Author
Owner

With the enableDevelopmentMode method from the extensibility dependency, you can turn on the development mode.

This allows you to quickly reload the webview without the need to reload vscode or close and open the dashboard again and again.

image

With the `enableDevelopmentMode` method from the extensibility dependency, you can turn on the development mode. This allows you to quickly reload the webview without the need to reload vscode or close and open the dashboard again and again. ![image](https://user-images.githubusercontent.com/2900833/221843513-f6b8fac1-17af-4464-bcc9-515c6e60ade0.png) <!-- github-comment-id:1448038498 -->
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/vscode-front-matter#403