Advanced Discord.js: Custom embeds using attachments
Tags: discordjs, node, embed, javascript
Published: 2021-01-17 22:07:24

## Table Of Contents
 * [Using SVGs](#svg)
 * [Using HTML & CSS](#html)
 * [Conclusion](#conc)

---
Looking for a Discord.JS bot? [Check out Hans](https://github.com/en3sis/hans)! Now it's open-sourced 🥳!
 
[<img src="https://res.cloudinary.com/vac/image/upload/v1683578541/Hans/hans-banner-pattern.png"/>](https://github.com/en3sis/hans)

---

While dealing with sending messages with our Discord Bot, we can do it in multiple ways such as text, attachments and the most common way, [embeds](https://discordjs.guide/popular-topics/embeds.html).

One example could be something like the following command that will display the weather based on a location:  
   
![MessageEmbed](https://dev-to-uploads.s3.amazonaws.com/i/97yo0jmjnl0lwzvt00c5.png)
   
Sometimes using the [MessageEmbed](https://discord.js.org/#/docs/main/stable/class/MessageEmbed) class it is enough, but we may want to represent the data  in a different layout/design:   
    
![Custom Image Embed - Discord Js](https://dev-to-uploads.s3.amazonaws.com/i/5uflvzp7wwk3x58stwc1.png)
   
I was seeing several others bots making use of custom ways to show things like stats, users profiles, etc., but I wasn't sure how that can be done and I couldn't find any examples. Some ideas came to my mind and it was time to try things out :P
   
> The following examples are built on top of [Discord.js](https://discord.js.org/#/), a Node.JS wrapper to interact with the Discord API. For more information related to Discord.js and how you can start building your own BOT, you can follow along with this [amazing series created by Daniel Shiffman](https://www.youtube.com/watch?v=7A-bnPlxj4k&list=PLRqwX-V7Uu6avBYxeBSwF48YhAnSn_sA4).
   
   
##  Using SVG <a name="svg"></a>
When I first started looking into this, it came to my mind that having an SVG-to-PNG library could work pretty well since we can have the SVG in a template string and replace the placeholders with our data (user input, API calls, etc...) and then make use of [MessageAttachment](https://discord.js.org/#/docs/main/stable/class/MessageAttachment) to attach the output.

For the following SVG image,   
![svg_template](https://dev-to-uploads.s3.amazonaws.com/i/7aiuxtbhd5f2g1qctdm1.png)

I was able to find a library that would take an SVG as a string value and return a [buffer] (https://nodejs.org/api/buffer.html)

```npm install svg-png-converter```

and then we could have a function to handle all the functionality, in this case we'll have it in its own file and we call it svgToPng.js (Discord only allows attachments of images as JPEG/PNG formats):

```js
// svgToPng.js
const { MessageAttachment } = require('discord.js')
const { svg2png } = require('svg-png-converter')


module.exports = async (msg, name) => {

  const outputBuffer = await svg2png({
    input: `<svg xmlns="http://www.w3.org/2000/svg" width="350" height="136" viewBox="0 0 350 136">
  <g id="template" transform="translate(-208 -209)">
    <rect id="background" width="350" height="136" transform="translate(208 209)" fill="#232323"/>
    <text id="_usr_" data-name="${name}" transform="translate(326 286)" fill="#fff" font-size="20" font-family="SegoeUI, Segoe UI"><tspan x="0" y="0">Hello ${name}</tspan></text>
    <path id="icon" d="M7.5-16.68,15-13.32v5a10.351,10.351,0,0,1-2.148,6.348A9.33,9.33,0,0,1,7.5,1.68,9.33,9.33,0,0,1,2.148-1.973,10.351,10.351,0,0,1,0-8.32v-5Zm1.758,4A2.435,2.435,0,0,0,7.5-13.4a2.435,2.435,0,0,0-1.758.723A2.361,2.361,0,0,0,5-10.918a2.425,2.425,0,0,0,.742,1.777A2.4,2.4,0,0,0,7.5-8.4a2.4,2.4,0,0,0,1.758-.742A2.425,2.425,0,0,0,10-10.918,2.361,2.361,0,0,0,9.258-12.676ZM7.5-6.836a8.754,8.754,0,0,0-2.031.273,6.19,6.19,0,0,0-2.051.9A1.74,1.74,0,0,0,2.5-4.258,6.007,6.007,0,0,0,4.707-2.383,5.947,5.947,0,0,0,7.5-1.6a5.947,5.947,0,0,0,2.793-.781A6.007,6.007,0,0,0,12.5-4.258a1.486,1.486,0,0,0-.547-1.094,4.2,4.2,0,0,0-1.348-.82A10.513,10.513,0,0,0,8.984-6.66,7.147,7.147,0,0,0,7.5-6.836Z" transform="translate(302 286)" fill="#fff"/>
  </g>
</svg>
`,
    encoding: 'buffer',
    format: 'png',
    quality: 1
  })
// for more configuration options refer to the library

  return msg.channel.send(`This is a test:`, new MessageAttachment(outputBuffer, '${name}.png'))

 }
}
```

#### The result:
![SVG Example - GIF](https://dev-to-uploads.s3.amazonaws.com/i/s8witq0nwa8mieykf5z4.gif)


This was working great with small illustrations where there are just some small changes in the text. For other things, it was not good enough (when you want to dynamically show an image based on some value, such as a list of different icons for each different user role). While this still could be achieved with the SVG, it felt that it's too much work and it wasn't the right fit.

It was time to go back to the drawing board and to think about some other ways to achieve this. 

## Using HTML & CSS <a name="html"></a>

In the same way, we deal with SVG to PNG conversion, it would be possible to render a webpage with all our data, assets and take a screenshot of it (using puppeteer or some other libraries) and finally attach the screenshot? This would solve many issues and creating a layout in the front-end it's something that I understand and I feel comfortable with, so it sounded like a great idea!

It ends up that you can! After a quick search in the infinitely large npm registry and I was able to find a library that does exactly that ([node-html-to-image](https://www.npmjs.com/package/node-html-to-image)).

Now the only thing we have to do is to build the layout for our example (HTML & CSS) and to put everything together. We'll make use of the [avatar API](https://avatars.dicebear.com/) to dynamically generate an avatar based on the user's name input.

Let start building a quick mockup for our card:
 {% codesandbox embedexample-8y0ku initialpath=/initial/load/path %} 


Now that we have our HTML & CSS, we can start working on generating an image from it using the NPM package: 
```npm install node-html-to-image```

and in our htmlToPng.js file we have:

```js
//  htmlToPng.js
const { MessageAttachment } = require('discord.js')
const nodeHtmlToImage = require('node-html-to-image')

module.exports = async (msg, name) => {

  const _htmlTemplate = `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <style>
      body {
        font-family: "Poppins", Arial, Helvetica, sans-serif;
        background: rgb(22, 22, 22);
        color: #fff;
        max-width: 300px;
      }

      .app {
        max-width: 300px;
        padding: 20px;
        display: flex;
        flex-direction: row;
        border-top: 3px solid rgb(16, 180, 209);
        background: rgb(31, 31, 31);
        align-items: center;
      }

      img {
        width: 50px;
        height: 50px;
        margin-right: 20px;
        border-radius: 50%;
        border: 1px solid #fff;
        padding: 5px;
      }
    </style>
  </head>
  <body>
    <div class="app">
      <img src="https://avatars.dicebear.com/4.5/api/avataaars/${name}.svg" />

      <h4>Welcome ${name}</h4>
    </div>
  </body>
</html>
`

  const images = await nodeHtmlToImage({
    html: _htmlTemplate,
    quality: 100,
    type: 'jpeg',
    puppeteerArgs: {
      args: ['--no-sandbox'],
    },
    encoding: 'buffer',
  })
// for more configuration options refer to the library

  return msg.channel
    .send(new MessageAttachment(images, `${name}.jpeg`))
}
``` 

#### The result:
![HTML Example - GIF](https://dev-to-uploads.s3.amazonaws.com/i/m4bv874er90f3by3rxty.gif)


## Conclusion <a name="conc"></a>
Now we have two new ways to generate more complex and good looking cards for your Discord users to consume. One good example of this would be to display some game stats for example. My brother and his friends play World of Warcraft, so I took a little time to create a command, that will display the main stats for their characters using the HTML & CSS technique, here is the result of it:

![WOW Stats - Discord.js](https://dev-to-uploads.s3.amazonaws.com/i/32oj0l49toik9d7y015d.png)

I would suggest using the SVG method while dealing with a static design/elements but only some text changes and use the HTML one while dealing with dynamic lists or images.

In the previous example, we might be able to archive the same result using an SVG but I find it hard since each element (the item icon and his value) are coming from an API in a different format (JPG/PNG for the images). Appending the item to a list it's way easier than applying a block of code to the SVG bases on some condition. Also styling things like images wrappers (round images with round borders) it's way easier in HTML & CSS than with SVG using things like masks.

I hope that this guide will open new ways for your creativity, which I would love to see them. 


You can find the BOT code with all examples at https://github.com/en3sis/discord-guides

Any feedback, questions or suggestions are welcome! 
Thanks for reading! ~ https://twitter.com/en3sis