- Add 'heart' as the 8th avatar shape using SVG path() clip-path with cubic beziers - Document the kind 0 'shape' metadata extension in NIP.md with all 8 defined values, client behavior rules, and forward-compatibility guidance
14 KiB
NIP: Custom Event Kinds
Event Kinds Overview
| Kind | Name | Description |
|---|---|---|
| 36767 | Theme Definition | Shareable, named custom UI theme |
| 16767 | Active Profile Theme | The user's currently active theme (one per user) |
| 16769 | Profile Tabs | The user's custom profile page tabs (one per user) |
Kind 36767: Theme Definition
Summary
Addressable event kind for publishing shareable custom UI themes. A single user may publish multiple themes, each identified by a unique d tag.
A theme consists of colors, optional fonts, and an optional background. Colors are stored in c tags, fonts in f tags, and background in a bg tag.
Event Structure
{
"kind": 36767,
"content": "",
"tags": [
["d", "mk-dark-theme"],
["c", "#1a1a2e", "background"],
["c", "#e0e0e0", "text"],
["c", "#6c3ce0", "primary"],
["f", "Inter", "https://example.com/inter.woff2"],
["bg", "url https://example.com/bg.jpg", "mode cover", "m image/jpeg", "dim 1920x1080"],
["title", "MK Dark Theme"],
["alt", "Custom theme: MK Dark Theme"]
]
}
Content
The content field is unused and MUST be an empty string ("").
Tags
| Tag | Required | Description |
|---|---|---|
d |
Yes | Unique identifier (slug) for this theme, e.g. "mk-dark-theme" |
c |
Yes (×3) | Hex color with marker. See Color Tags. |
f |
No | Font declaration. See Font Tag. |
bg |
No | Background media. See Background Tag. |
title |
Yes | Human-readable theme name |
alt |
Yes | NIP-31 human-readable fallback |
Multiple Themes Per User
Since kind 36767 is addressable, a user can publish multiple themes by using different d tag values. Publishing a new event with the same d tag replaces the previous version (this is how editing works).
Kind 16767: Active Profile Theme
Summary
Replaceable event that represents the user's currently active profile theme. Only one per user. When other users visit a profile, they query this kind to determine what theme to display.
Event Structure
{
"kind": 16767,
"content": "",
"tags": [
["c", "#1a1a2e", "background"],
["c", "#e0e0e0", "text"],
["c", "#6c3ce0", "primary"],
["f", "Inter", "https://example.com/inter.woff2"],
["bg", "url https://example.com/bg.jpg", "mode cover", "m image/jpeg"],
["title", "MK Dark Theme"],
["alt", "Active profile theme"]
]
}
Content
The content field is unused and MUST be an empty string ("").
Tags
| Tag | Required | Description |
|---|---|---|
c |
Yes (×3) | Hex color with marker. See Color Tags. |
f |
No | Font declaration. See Font Tag. |
bg |
No | Background media. See Background Tag. |
title |
No | Human-readable name for the theme |
alt |
Yes | NIP-31 human-readable fallback |
Client Behavior
- When visiting a profile, clients query
{ kinds: [16767], authors: [pubkey], limit: 1 }to get the active theme. - Clients read the
ctags to extract colors,ftags for fonts, andbgtag for the background. - Setting a new active theme publishes a new kind 16767 event (replacing the old one).
- To remove the active theme, publish a kind 5 deletion event targeting kind 16767.
Shared Tag Definitions
The following tag definitions apply to both kind 36767 and kind 16767.
Color Tags
Format: ["c", "#rrggbb", "<marker>"]
| Index | Required | Description |
|---|---|---|
| 0 | Yes | Tag name: "c" |
| 1 | Yes | Lowercase 6-digit hex color code including the # sign (e.g. "#ff0000") |
| 2 | Yes | Color role marker: one of "primary", "text", or "background" |
- All three markers (
"primary","text","background") MUST be present. - Only one
ctag per marker is allowed.
Font Tag
Format: ["f", "<family>", "<url>"]
| Index | Required | Description |
|---|---|---|
| 0 | Yes | Tag name: "f" |
| 1 | Yes | CSS font-family name (e.g. "Inter") |
| 2 | Yes | Direct URL to a font file (.woff2, .ttf, .otf) |
- The
ftag is optional on the event. - At most one
ftag per event is allowed. - The font applies globally to all text (body, headings, UI elements).
- If the URL fails to load, the client SHOULD fall back to a default font gracefully.
- Variable font files (covering multiple weights in a single file) are preferred.
Background Tag
The bg tag uses an imeta-style variadic format where each entry (after the tag name) is a space-delimited key/value pair.
Format: ["bg", "url <url>", "mode <mode>", "m <mime-type>", ...]
| Key | Required | Description |
|---|---|---|
url |
Yes | URL to an image or video file |
mode |
Yes | Display mode: "cover" or "tile" |
m |
Yes | MIME type (e.g. "image/jpeg", "image/png", "video/mp4") |
dim |
No | Dimensions in pixels: "<width>x<height>" (e.g. "1920x1080") |
blurhash |
No | Blurhash placeholder string for progressive loading |
- At most one
bgtag is allowed per event. - Clients MAY choose not to render video backgrounds for performance or bandwidth reasons.
- Unknown keys SHOULD be ignored for forward compatibility.
Kind 16769: Profile Tabs
Summary
Replaceable event kind for publishing a user's custom profile page tabs. Exactly one event per user (no d tag). Each tab defines a Nostr filter (NIP-01) that clients execute to populate the tab's content.
Visitors who load a profile fetch this event to display the custom tabs alongside the standard Posts / Media / Likes / Wall tabs.
Event Structure
{
"kind": 16769,
"content": "",
"tags": [
["var", "$follows", "p", "a:3:$me:"],
["tab", "Bitcoin Posts", "{\"kinds\":[1],\"authors\":[\"$me\"],\"search\":\"bitcoin\"}"],
["tab", "Feed", "{\"kinds\":[1,6],\"authors\":[\"$follows\"],\"limit\":40}"],
["alt", "Custom profile tabs"]
]
}
Content
The content field is unused and MUST be an empty string ("").
Tags
| Tag | Format | Description |
|---|---|---|
tab |
["tab", "<label>", "<filterJSON>"] |
One tag per custom tab. Order defines display order. |
var |
["var", "<$name>", "<tag>", "<pointer>"] |
Variable definition. See Variable Tags. |
alt |
["alt", "Custom profile tabs"] |
NIP-31 human-readable fallback. Required. |
Tab Filter JSON
The third element of each tab tag is a JSON-encoded NIP-01 filter object, optionally extended with the NIP-50 search field. Variable placeholders (strings starting with $) may appear wherever a string value is expected.
{
"kinds": [1],
"authors": ["$me"],
"search": "bitcoin",
"limit": 20
}
Supported filter fields: ids, authors, kinds, #<tag> (e.g. #t, #e, #p), since, until, limit, search.
Variable Tags
Variable tags define named placeholders that are resolved before the filter is executed. Each var tag extracts tag values from a referenced Nostr event.
Format: ["var", "$name", "<tag-to-extract>", "<event-pointer>"]
| Index | Description |
|---|---|
| 0 | Tag name: "var" |
| 1 | Variable name, starting with $ (e.g. "$follows") |
| 2 | Tag name to extract values from in the referenced event (e.g. "p") |
| 3 | Event pointer: e:<event-id> for a specific event, or a:<kind>:<pubkey>:<d-tag> for an addressable/replaceable event coordinate. Variables like $me may appear in the pubkey position. |
Example — extract follow list pubkeys:
["var", "$follows", "p", "a:3:$me:"]
This means: fetch the kind 3 event authored by $me, extract all p tag values, and bind them to $follows.
Reserved Variable: $me
The $me variable is the only runtime-provided variable. It resolves to the profile owner's pubkey (the author of the kind 16769 event). It does not require a var tag definition.
Variable Resolution
When a variable appears in a filter field that expects an array (e.g. authors, ids, #p), the variable is expanded in-place (spliced into the array). Literal values may be mixed with variables.
["tab", "Mixed", "{\"authors\":[\"$follows\",\"abc123...\"],\"kinds\":[1]}"]
After resolution (assuming $follows = ["pk1", "pk2"]):
{"authors": ["pk1", "pk2", "abc123..."], "kinds": [1]}
Behavior
- To add or update tabs: publish a new kind 16769 event with all current
tabandvartags. - To clear all tabs: publish a kind 16769 event with no
tabtags (onlyalt). - Clients MUST filter by
authors: [pubkey]when querying to prevent spoofing. vartags are shared across alltabtags in the same event.
Kind 0 Extension: Avatar Shape
Summary
An optional shape property on kind 0 (profile metadata) that controls how the user's avatar is masked/clipped when displayed. Clients that support this extension render the avatar using the specified geometric shape instead of the default circle.
Metadata Field
The shape field is added to the JSON content of a kind 0 event alongside standard fields like name, picture, etc.
{
"kind": 0,
"content": "{\"name\":\"Alice\",\"picture\":\"https://example.com/alice.jpg\",\"shape\":\"hexagon\"}"
}
Defined Shape Values
| Value | Description |
|---|---|
circle |
Standard circular avatar (the default when shape is absent) |
triangle |
Equilateral triangle pointing up |
inverted-triangle |
Equilateral triangle pointing down |
hexagon |
Regular hexagon (6-sided polygon) |
star |
5-pointed star |
inverted-star |
5-pointed star pointing down |
hexagram |
6-pointed star (Star of David) |
heart |
Heart shape with curved lobes |
Client Behavior
- When
shapeis absent or set to"circle", clients SHOULD render the avatar as a circle (the current universal default). - When
shapeis set to a recognized value, clients SHOULD apply the corresponding geometric mask (e.g. via CSSclip-path) to the avatar image and fallback. - When
shapeis set to an unrecognized value, clients MUST fall back to"circle". This ensures forward compatibility as new shapes are added. - The
shapefield is purely cosmetic and has no protocol-level significance. - Clients MAY choose not to support this extension, in which case avatars render as circles as usual.
Implementation Notes
Non-circle shapes are best implemented using CSS clip-path with polygon() for straight-edged shapes and path() for curved shapes (e.g. heart). The circle shape should continue to use border-radius: 50% (or equivalent) for simplicity and compatibility.