Skip to content

Instantly share code, notes, and snippets.

@Davr1
Last active February 2, 2026 14:31
Show Gist options
  • Select an option

  • Save Davr1/af6a5806a3bf4b5b7dc18829029b42c2 to your computer and use it in GitHub Desktop.

Select an option

Save Davr1/af6a5806a3bf4b5b7dc18829029b42c2 to your computer and use it in GitHub Desktop.
Backup and restore discord favourite gifs

Backup and restore discord favourite gifs (last update: 27/01/2026)

How to use these scripts:

  1. Open a browser and log into your discord account.
  2. Open devtools using Ctrl+Shift+I (or Cmd ⌘ + Option ⌥ + I)
  3. Go to the console tab.
  4. Paste the snippet and hit enter. You might also need to type "allow pasting" if this is your first time using snippets.

Backup gifs

This snippet will export your favourite gifs into a file named discord-favorite-gifs.json.

window.FrecencyUserSettings ??= webpackChunkdiscord_app.push([[Symbol()],,e=>Object.values(e.c).values().map(m=>m.exports).filter(x=>typeof x=="object"&&x!=window&&x!=DOMTokenList.prototype).flatMap(x=>[x,...Object.values(x)]).find(x=>x?.ProtoClass?.typeName?.endsWith(".FrecencyUserSettings"))]);

function downloadJSON(content, download) {
  const json = JSON.stringify(content, null, 2);
  Object.assign(document.createElement("a"), {
    href: URL.createObjectURL(new Blob([json], { type: "application/json" })),
    download
  }).click();
}

FrecencyUserSettings.loadIfNecessary();
downloadJSON(FrecencyUserSettings.getCurrentValue().favoriteGifs.gifs, "discord-favorite-gifs.json");

Restore gifs

This snippet will ask you to pick a file with your (previously exported) gifs. It will merge them with the current set of favourite gifs, moving the old ones further down the list.

Note

If you generated your favourites file using a different tool/snippet it could be incompatible. Make sure it follows the specific structure that discord uses.

Click to view example
{
  // The object keys store the source urls - this is what gets sent in chat when you select a gif
  "https://tenor.com/view/cirno-fumo-touhou-fumo-touhou-gif-23545101": {
    // Gif format: NONE = 0, IMAGE = 1, VIDEO = 2
    "format": 2,
    // The media url - discord uses this to generate thumbnails in the gif picker
    "src": "https://media.tenor.co/videos/10b5a62192508ab85ec795ce4124f12a/mp4",
    // The thumbnail width
    "width": 640,
    // The thumbnail height
    "height": 358,
    // Numerical ordering index - gets incremented with each newly favorited gif.
    // This snippet automatically updates the indices of new gifs to always be higher than old gifs,
    // which does not affect their relative order.
    "order": 78
  },
  // ...
}
window.FrecencyUserSettings ??= webpackChunkdiscord_app.push([[Symbol()],,e=>Object.values(e.c).values().map(m=>m.exports).filter(x=>typeof x=="object"&&x!=window&&x!=DOMTokenList.prototype).flatMap(x=>[x,...Object.values(x)]).find(x=>x?.ProtoClass?.typeName?.endsWith(".FrecencyUserSettings"))]);

window._log = (msg, color) => console.log(`%c${msg}`, `font-size:2rem;color:${color}`);

function uploadJSON() {
  return new Promise((res, rej) =>
    Object.assign(document.createElement("input"), {
      type: "file",
      accept: "application/json",
      onchange: ({ target: { files: [file] } }) => file?.text().then(JSON.parse).then(res).catch(rej)
    }).click()
  );
}

uploadJSON()
  .catch(() => _log("Failed to parse JSON.", "red"))
  .then(gifs =>
    FrecencyUserSettings.updateAsync("favoriteGifs", state => {
      const offset = Object.values(state.gifs).map(g => g.order).reduce((a, b) => Math.max(a, b), 0) + 1;
      Object.values(gifs).sort((a, b) => a.order - b.order).forEach((gif, i) => gif.order = offset + i);
      Object.assign(state.gifs, gifs);

      const valid = state[Symbol.for("protobuf-ts/message-type")].toBinary(state).length <= 762880;
      if (!valid) _log("Too many gifs.", "red");

      return valid;
    }, 0)
  )
  .catch(() => _log("Unspecified/Network error.", "red"))
  .then(() => _log("Successfully imported gifs.", "green"));

Clear all favourite gifs

Caution

This action is irreversible. Backup your gifs before using this snippet.

Click to view snippet
window.FrecencyUserSettings ??= webpackChunkdiscord_app.push([[Symbol()],,e=>Object.values(e.c).values().map(m=>m.exports).filter(x=>typeof x=="object"&&x!=window&&x!=DOMTokenList.prototype).flatMap(x=>[x,...Object.values(x)]).find(x=>x?.ProtoClass?.typeName?.endsWith(".FrecencyUserSettings"))]);

FrecencyUserSettings.updateAsync("favoriteGifs", state => void delete state.gifs);
@Lncvrt
Copy link

Lncvrt commented Dec 4, 2025

thanks

@taep96
Copy link

taep96 commented Dec 5, 2025

glad I could help :)

@nobungaga
Copy link

i have absolutely no idea how this stuff works, how do i use it

@N7NobodyCats
Copy link

how does this get used? how do you run this?

@Lncvrt
Copy link

Lncvrt commented Dec 18, 2025

On either discord app with vencord or vesktop open the dev console and paste that in there. You can look through the code if you don't trust that.

@zoey-on-github
Copy link

as of 1/25/26, does not work. discord changed something

@taep96
Copy link

taep96 commented Jan 26, 2026

queries the api instead of webpack so it shouldn't be as fragile:

const iframe = document.body.appendChild(document.createElement("iframe"));
const token = iframe.contentWindow.localStorage.token.replace(/"/g, "");
document.body.removeChild(iframe);

const response = await fetch(
	"https://discord.com/api/v9/users/@me/settings-proto/2",
	{ headers: { "Authorization": token } },
);
const { settings } = await response.json();
const urls = atob(settings).match(/https?:\/\/[^\s\x00-\x1f]+/g);

Object.assign(document.createElement("a"), {
	href: URL.createObjectURL(
		new Blob([JSON.stringify(urls, null, 2)], { type: "application/json" }),
	),
	download: "discord-favorite-gifs.json",
}).click();

@taep96
Copy link

taep96 commented Jan 26, 2026

closer to the original

const iframe = document.body.appendChild(document.createElement("iframe"));
const proto_2 = Object.entries(iframe.contentWindow.localStorage)
	.filter(([key]) => key.startsWith("UserSettingsProtoStore:parsed:2:"))[0][1];
document.body.removeChild(iframe);

const gifs = JSON.parse(proto_2)["favoriteGifs"]["gifs"];
const json = JSON.stringify(gifs, null, "\t");

Object.assign(document.createElement("a"), {
	href: URL.createObjectURL(new Blob([json], { type: "application/json" })),
	download: "discord-favorite-gifs.json",
}).click();

@Davr1
Copy link
Author

Davr1 commented Jan 27, 2026

Since this snippet is still relevant years later, I've decided to actually update this page. Big thanks to @taep96 for keeping it up to date, however there were some bugs with their implementation (namely that matching links using regexes would match both the source and media url - these are different and not usually interchangeable).

The updated snippet now exports the full object format again, which means that it can also be imported back into discord and transferred across different accounts.

I will try to not abandon this again lol.

@taep96
Copy link

taep96 commented Jan 27, 2026

Nice work on the update!

there were some bugs with their implementation (namely that matching links using regexes would match both the source and media url

My last version reads the already-parsed data from localStorage, so there's no parsing bug. It also doesn't depend on webpack which should make it more stable, though it's read-only.

A better way to both read/write would be to send a GET/PATCH request to the API and use discord-protos for parsing, but that's tricky to do cleanly in a console script (CSP), and external tools add complexity. Probably not worth it for most use cases.

@Davr1
Copy link
Author

Davr1 commented Jan 27, 2026

Your localStorage snippet only worked on one of my discord instances and nowhere else :<
I think the :parsed: values were leftovers from a previous update, I can't even find what generated them in the code.

There's probably no easier way to parse the raw protobuf data in a snippet, sure I could compile and minify the schemas manually but that would bloat up the size and become harder to maintain. Maybe there's an undocumented flag on the frecency user settings endpoint to return the raw data, but once again a workaround has to be used to retrieve the token.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment