Exporting Custom Slack Emoji

Published See discussion on Twitter

This workflow has been proven to work at the end of March 2024, it may break in the future!

At work we recently bypassed over 60k custom slack emoji in our workspace, and I'd been meaning to download them locally so I could reference them in other discussions outside of slack with past and present coworkers.

I did some scavenging on Google (actually Perplexity) and found a fairly active Gist that seems to have captured a few different routes to accomplish the use case, but some of the API has changed since then.

So I figured I'd document what I did to accomplish this in case others are looking for a similar solution!

Steps:

Download emoji metadata:

  1. Visit your /customize/emoji page in a browser of your choosing a. This should look roughly like: https://<organization>.slack.com/customize/emoji
  2. Open the network panel and look for a request to the emoji.adminList endpoint
  3. Copy that request as fetch (right click the request, copy as -> fetch in Chrome/Arc)
  4. Copy the below snippet and paste it into the console a. This snippet only defines a few helper functions, it won't do anything malicious, but validate it for yourself if you'd like!
1// Function to fetch a page of emoji
2async function fetchPage(pageNum) {
3 let res = TODO;
4 let json = await res.json();
5 return json;
6}
7
8// function to download a JSON file automatically from the console in devtools
9function downloadFile(content, fileName, contentType) {
10 let anchor = document.createElement("a");
11 document.body.appendChild(anchor);
12 let file = new Blob([content], { type: contentType });
13 anchor.href = URL.createObjectURL(file);
14 anchor.download = fileName;
15 anchor.click();
16}
17
18// Function to batch download emoji
19async function downloadAllEmoji(start, stop) {
20 for (let pageNum = start; pageNum <= stop; pageNum++) {
21 let json = await fetchPage(pageNum);
22 let content = JSON.stringify(json);
23 downloadFile(content, `page-${pageNum}.json`, "application/json");
24 }
25}
1// Function to fetch a page of emoji
2async function fetchPage(pageNum) {
3 let res = TODO;
4 let json = await res.json();
5 return json;
6}
7
8// function to download a JSON file automatically from the console in devtools
9function downloadFile(content, fileName, contentType) {
10 let anchor = document.createElement("a");
11 document.body.appendChild(anchor);
12 let file = new Blob([content], { type: contentType });
13 anchor.href = URL.createObjectURL(file);
14 anchor.download = fileName;
15 anchor.click();
16}
17
18// Function to batch download emoji
19async function downloadAllEmoji(start, stop) {
20 for (let pageNum = start; pageNum <= stop; pageNum++) {
21 let json = await fetchPage(pageNum);
22 let content = JSON.stringify(json);
23 downloadFile(content, `page-${pageNum}.json`, "application/json");
24 }
25}
  1. Replace the TODO within fetchPage with the copied fetch request from the network panel
  2. Important! Look in the request body of the fetch you copied for a page: 1 entry, and replace the 1 with ${pageNum} - you'll also need to change the body to a template string!
  3. Run downloadAllEmoji with a start value >= 1 and a stop value <= the max number of pages (you can look at the emoji.adminList request for page 1 to see the maximum number of pages)

Download images:

To download the actual images, you'll want to:

  1. Move the downloaded metadata files into a shared directory
  2. Initialize a new Bun project within that directory by running bun init
  3. Paste in the following snippet into index.ts:
1import fg from "fast-glob";
2
3async function loadAllEmoji() {
4 const files = await fg("page-*.json");
5 const emojis = [];
6 for (const file of files) {
7 const json = await Bun.file(file).json();
8 if (!json.emoji) {
9 throw new Error(
10 "Emoji not found in " +
11 file +
12 ", did the response get rate limited? Check the file and re-download if needed!"
13 );
14 }
15 emojis.push(...json.emoji);
16 }
17 return emojis;
18}
19
20async function downloadEmoji(emojiList) {
21 for (let emoji of emojiList) {
22 let res = await fetch(emoji.url);
23 console.log('Downloading: ' + emoji.name + ' (' + idx + 'of ' + emojiList.length + ')');
24 await Bun.write(
25 "./emojis/" + emoji.name + "." + emoji.url.split(".").at(-1),
26 res
27 );
28 }
29}
30
31let emoji = await loadAllEmoji();
32
33await downloadEmoji(emoji);
1import fg from "fast-glob";
2
3async function loadAllEmoji() {
4 const files = await fg("page-*.json");
5 const emojis = [];
6 for (const file of files) {
7 const json = await Bun.file(file).json();
8 if (!json.emoji) {
9 throw new Error(
10 "Emoji not found in " +
11 file +
12 ", did the response get rate limited? Check the file and re-download if needed!"
13 );
14 }
15 emojis.push(...json.emoji);
16 }
17 return emojis;
18}
19
20async function downloadEmoji(emojiList) {
21 for (let emoji of emojiList) {
22 let res = await fetch(emoji.url);
23 console.log('Downloading: ' + emoji.name + ' (' + idx + 'of ' + emojiList.length + ')');
24 await Bun.write(
25 "./emojis/" + emoji.name + "." + emoji.url.split(".").at(-1),
26 res
27 );
28 }
29}
30
31let emoji = await loadAllEmoji();
32
33await downloadEmoji(emoji);
  1. Install fast-glob via bun add fast-glob
  2. Run the above script via bun ./index.ts

Note! If you have a lot of emoji, this will take a while! It downloads the images synchronously to avoid being rate limited.

Recommendations:

I recommend not trying to run through all pages at once when fetching the emoji metadata, as sometimes Slack may not be able to handle the requests.

In my case, since we had ~600 pages to go through, I started with chunks of 25 then went up to 100 pages at a time.