Emojis at notions are not emojis.... Well sort of
I was doing my usual "one quick engineering blog before I start work" thing (aka lying to myself), and I landed on Notion’s breakdown of how they built custom emojis.
Because the problem isn’t “how do we let people upload little images?”
The problem is:
how do you let teams upload hundreds of tiny, sometimes animated images, and make them feel instant everywhere without wrecking performance, memory, or the editor model?
So emojie chars actual emojies ?
Notion starts by reminding us that their editor isn’t plain text. A single line can have bold, italics, highlights, equations, mentions… all stacked inside the same block.
Their internal representation (simplified) looks like: Each text piece is stored as
[string, Array<TextAnnotation>]
They give examples like:
- bold link “Click here” encoded as a string + annotations
- a user mention “@Henry” represented using a special marker + metadat
And this hit me because I’ve been there. Any time you have a “rich” editor model, you can’t just shove new stuff in. You either do it the “quick hack” way and regret it forever or you align with the model properly so it behaves like everything else. Notion went with option 2.
The clever move: treat custom emoji like a mention
Notion already had a powerful concept: mentions are basically pointers to backend objects (users, pages, etc.). So instead of inventing a totally new “emoji embed” system, they modeled custom emojis the same way.
They represent a custom emoji using something like:
["‣", [["ce", "<CUSTOM_EMOJI_ID>", "<WORKSPACE_ID>"]]]
So the emoji is not “an image blob shoved into text.” It’s a stable reference.
This makes text editor
- Reactive updates: change the emoji image/name and it updates everywhere it’s used
- Seamless integration: it plugs into existing editor/collab/rendering systems without rebuilding everything
- Future-proofing: later you can add metadata, permissions, categorization, sharing, etc. while keeping references stable
This is one of those “boring on paper, brilliant in practice” decisions. Notion’s approach is basically: don’t make emojis special. Make them first-class.
So there is a solution but there is more
Okay, so now emojis can exist in the model. Cool. The emoji picker was fine when it was just the standard emoji set. But custom emojis introduce a new chaos factor: workspaces can have hundreds of extra images.
Notion tried rendering everything at once and ran into the classic triple punch:
- delays
- high memory usage
- sluggish interactions
- …and then the client fires off a flood of image requests and the network basically says “bro???”
So they used virtualization: render only what’s visible in the viewport (plus a buffer).
If you’ve ever used react-window / virtual lists / recycler views—same idea. You don’t pay the cost for items the user isn’t even looking at.
Notion says: since October (relative to their timeline), users created over 1.5 million custom emojis.
Next problem : how do we load these emojis so the picker feels instant
Notion basically had two paths. The long-term “perfect” path is on-demand loading with an edge cache: fetch only what you need, when you need it, and keep it close to users. That’s scalable and elegant… and also a whole separate infrastructure project (caching, invalidation, versioning, etc.). So for the first release, they took the practical route: preload custom emoji metadata during app boot, but after the most critical startup requests, so emojis don’t steal the app’s first impression. To keep the preload approach from getting out of control, they added a guardrail: cap custom emojis per workspace (500 initially). Then, after adoption proved massive, the plan is to move toward the edge-cached “load on demand” model to lift that cap without hammering memory/network.

If the emoji picker is used constantly, preload wins because speed becomes the feature. If custom emojis are huge or rarely used, lazy-load wins because you don’t pay the cost upfront. Either way, the goal is the same: make it feel instant without wasting resources.