Skip to content

Instantly share code, notes, and snippets.

@CodingWithAnxiety
Created July 18, 2026 20:45
Show Gist options
  • Select an option

  • Save CodingWithAnxiety/00a5738a83de999ed5e478bc82eeeb05 to your computer and use it in GitHub Desktop.

Select an option

Save CodingWithAnxiety/00a5738a83de999ed5e478bc82eeeb05 to your computer and use it in GitHub Desktop.
F-List Profile Metadata (FPM) standard, version <v0.2.0>

F-List Profile Metadata (FPM) standard, version 0.2.0

Status: Draft. Awaiting feedback from the F-List community and other F-List client developers. The F-Chat Horizon organization maintains this revision and the standard's registry (section 3.6). The specification remains provisional until maintainers from other clients have reviewed it.

FPM is a client-neutral convention for embedding a small, versioned, human-readable metadata record inside an F-List profile description. Chat clients that understand the record can offer features the site does not (portrait overrides, name colors, accurate local time). Clients and browsers that do not understand it render nothing.

[i=fpm://v=1;avatar=static.f-list.net/images/charimage/44262665.png;namecolor=cyan;timezone=America/New_York][/i]

1. Problem

1.1 Environment

F-List profiles are BBCode documents with a fixed set of site-defined fields (infotags, kinks, images). Some client features cannot fit in those fields. F-List is in a maintenance state, so profile-schema changes, new API fields, and protocol extensions are unlikely. Yet third-party clients, including F-Chat Horizon, Frolic, XarChat, Luna, and future clients, need per-character presentation data that the site cannot store: a high-quality chat portrait, a name color, and a reliable timezone.

The site's own timezone field illustrates the gap: it is a bare UTC offset, optionally hidden, with no DST awareness. Clients that try to compute local time from it are wrong for large parts of the year, so most do not try.

1.2 The problem

Clients that need this data have created separate profile-description conventions for overlapping features:

Convention Client Visible on site?
[url=<url>]Rising Portrait[/url] Rising (legacy), Horizon, Frolic (fallback) Yes, as a link
[url=<url>]Horizon Portrait[/url] Horizon only Yes, as a link
[color=<name>]Horizon Color[/color] Horizon only Yes, as colored text
[i=hqp://<url-without-scheme>][/i] Frolic only No
[i=fcg://display=...;match=...;mismatch=...;v=1] Frolic only No

The costs of this fragmentation:

  • Users who care about cross-client presentation stack multiple conventions in one profile ("Horizon time zone: EST / XarTZ: EST / FrolicTime=EST" is the plausible near future for any new field).
  • The visible conventions clutter profiles with links and text that do nothing for users of the website or stock client, and they carry client branding, which reads as advertising.
  • The conventions already diverge on the same feature: Horizon introduced a second spelling ("Horizon Portrait") alongside the original "Rising Portrait"; Frolic's fallback tracks only the original. Users who followed either client's suggestion render inconsistently in the other; the clients had no shared registry to keep the spellings aligned.
  • Of the deployed conventions, only fcg carries a version; the rest can never change semantics without silently breaking users. The imgur ban made this concrete for Horizon Portraits: when imgur left the allowlist, Horizon could not mark existing records stale or tell affected users why their portraits had vanished.

1.3 Solutions available, and their drawbacks

  • Ask the site for a metadata field, or a custom websocket channel. This is the preferred fix, but we cannot rely on the site to change for non-official clients.
  • Standardize on Frolic's incumbent hidden schemes (hqp, fcg) as they are. This is the closest relative of this proposal. hqp is single-purpose and unversioned; fcg is versioned but its payload bakes one client's matcher semantics into the key names; neither defines an escaping rule, a key registry, or any process for adding fields. Adopting them as-is still means every new feature requires a new ad-hoc scheme with no governance. FPM completes this option: it keeps the incumbents' carrier and grammar and adds the shared namespace, escaping, and registry these standards lack.
  • Keep per-client conventions and add pairwise compatibility parsers. The status quo trajectory: every client parses every other client's formats, N clients yield N sets of regexes, and each new feature multiplies the mess. Every client must also watch every other client's releases for changes. A large team might keep up; a solo maintainer will not.
  • Repurpose existing profile fields. Infotags are a fixed server-side schema, kinks feed the site's matching, and memos are private per-viewer. Stuffing metadata into any of them breaks the site's own display of profiles and worsens the experience for users on the website or stock client.
  • An off-site metadata service keyed by character name. Adds an availability dependency, a privacy problem (viewing a profile leaks to a third party), an operator (who runs it, who moderates it), and moves character data off F-List. This would also require an entire API, login and character verification system, and a new client feature to fetch and cache the data. That is a lot of work for a small feature, and the F-List community may not accept it.

2. Proposed Solution

2.1 The general idea

Each character carries a metadata record inside their profile description. The record is invisible in rendered profiles but plainly readable in the profile source: named keys with human-readable values, no encoding, no compression. It starts with a version, so a client knows whether it understands the record before applying any of it. Clients that recognize the record apply the keys they support and ignore the rest; everything else on the page ignores the record entirely.

One grammar, one key registry. A client adopting FPM parses one thing and shares key semantics with every other adopter, instead of maintaining a zoo of per-client regexes. Trust policy is the deliberate exception: what a client is willing to fetch or display (image hosts above all) remains per-client, so identical records can still differ in what they show (section 4.4).

2.2 Existing problems this makes worse

  • Profiles as configuration files. The description field already carries hidden per-client markers; FPM legitimizes and will likely grow that use. The record stays small and single-line, but it is still machine data in a prose field.
  • Reliance on unspecified site and client behavior. The invisibility depends on how the site's BBCode renderer treats a parameter on the [i] tag. Frolic has shipped this for an extended period without incident, but the site never promised it.

2.3 New problems this creates

  • Governance. Shared keys need a shared registry and named people to run it (section 3.6). Cost of doing nothing: key collisions and semantic drift, i.e. the status quo.
  • Staleness. Clients cache profiles; a record edit propagates only when caches refresh. Users already accept this for portrait changes.
  • Abuse surface. avatar makes profile text influence what other clients fetch and display. Section 4.4 is normative because of this.
  • Silent failure. A user who hand-types a malformed record gets nothing, with no error anywhere. Mitigation: clients should generate records via UI, not ask users to type them, and should validate their user's own record on login and surface problems.
  • Invisible-text perception. FPM does not create the ability to hide text in profiles; the [i] parameter does that with or without a standard, and no format rule can stop a determined abuser from putting arbitrary content there. FPM keeps the honest ecosystem legible: conforming records use registered, human-readable keys and values, so anyone who opens the profile editor, moderation staff included, can read exactly what a record says. Compliance posture with the site itself is covered in section 4.5.

3. Specification

The key words MUST, MUST NOT, REQUIRED, SHOULD, and MAY are to be interpreted as in RFC 2119.

3.1 Record carrier

A record is the parameter of an italic BBCode tag, using the fpm scheme:

[i=fpm://<payload>][/i]
  • Generators MUST emit the record exactly as above: lowercase tag, lowercase scheme, closing [/i], empty tag body.
  • Parsers MUST match the tag name and scheme case-insensitively and MUST accept a record whose closing [/i] is absent. The tolerance exists for parser robustness only: on the site, an unclosed [i italicizes the rest of the profile, a visible failure. Generators MUST always close the tag.
  • Only the i tag is a valid carrier. Parsers MUST NOT accept fpm:// in other tags or in plain text.
  • Source field. Records are only valid in the profile description of the character they describe. Parsers MUST NOT extract records from chat message bodies, status text, channel descriptions, kink or custom-kink text, infotags, or any other field.
  • The payload ends at the first ] after the scheme; this is why ] (and [) are reserved characters (3.3).
  • A description SHOULD contain at most one record per version. Parsers scan records in source order and, among records whose version they recognize (3.4), MUST use the one with the highest version, first occurrence winning within a version; they MUST ignore the rest. A malformed or unrecognized-version record therefore never shadows a valid one, a profile can carry records for two versions during a migration, and a dual-version profile gives every client the newest record it understands rather than whichever happens to appear first. Generators emitting records for more than one version SHOULD place the newest first, so by-eye readers see the current data at the top. Parsers MAY bound the scan to a fixed number of records; 16 is more than any legitimate profile needs.
  • The record may appear anywhere in the description.
  • Parsers SHOULD reject a record whose payload exceeds 2048 characters, and SHOULD apply that cap before further processing. Generators MUST stay under it.
  • Parsers MAY extract records by flat pattern matching without building a BBCode parse tree. The consequence: a record nested inside a literal-rendering context such as [noparse] or [code] will be honored even though some renderers display it as text. That is a visible user error, not a security concern, and tree-aware parsing to exclude it is not required.

3.2 Payload grammar

payload    = pair *( ";" pair ) [ ";" ]
pair       = *WSP key *WSP "=" *WSP value *WSP
key        = 1*( %x61-7A / DIGIT )          ; a-z, 0-9
value      = *value-char                     ; may be empty; see below
value-char = any character except ";" and "]", reserved characters percent-escaped (3.3)
  • Keys are lowercase alphanumeric, no separators. Parsers MUST skip pairs whose key, after trimming, does not match the grammar.
  • Parsers MUST trim ASCII whitespace from both ends of keys and values before validation. (Frolic's deployed fcg parser trims; hand-typed records benefit.)
  • The first = in a pair separates key from value; later = characters are value content. This keeps URLs with query strings legible without escaping.
  • Within a record, the first occurrence of a key with a non-empty value wins; parsers MUST ignore later occurrences. An empty-valued occurrence is treated as if the pair were absent and does not reserve the key.
  • A trailing ; is tolerated on parse and not emitted.
  • Malformed pairs (no =, empty key) are skipped; they do not invalidate the record.

3.3 Escaping

Reserved characters in values:

Character Escape When escaped by generators
; %3B always
] %5D always
[ %5B always
% %25 only when the next two characters spell a reserved escape (25, 3B, 5D, 5B, any letter case)

Parsers MUST decode exactly the four sequences %25, %3B, %5D, %5B (case-insensitively, single pass) and MUST pass every other % sequence through untouched. The conditional rule for % means values that legitimately contain percent-encoding stay literal in the profile source: an avatar path with %20 is stored as %20, not %2520, and round-trips unchanged. Full URL-encoding is deliberately not used; records must stay readable in the source.

[ is reserved defensively: a literal [ inside a tag parameter is untested territory across BBCode renderers, and escaping it costs nothing.

3.4 Versioning

  • Every record MUST contain v. Its value token, after trimming, MUST be a decimal integer with no leading zeros and no sign (1, 2, ...); parsers compare the exact token, so 01, 1.0, and 1 x are unrecognized versions. Generators SHOULD emit v as the first pair for by-eye readability.
  • A parser that does not recognize the version MUST reject the record whole and apply nothing from it. Per 3.1, other records in the description are still considered, so an old client skips a future-version record and can still honor a coexisting current-version one.
  • A parser that recognizes more than one version selects among recognized records per 3.1: highest version wins, regardless of source order.
  • Within a known version, a client MUST ignore keys it does not recognize and MUST skip keys whose value fails validation, applying the rest. Adding a key to the registry is therefore never a version bump.
  • The version increments only for breaking semantic changes: a change that would make a conforming older client apply a key incorrectly.

In short: unknown key, skip the key; unknown version, reject the record.

Why version the record rather than individual keys: a v1 client cannot know which keys v2 redefined, so partially trusting an unrecognized-version record is unsafe by construction. The cost, discarding keys whose meaning did not change, is one we accept: the registry model makes breaking changes rare, and dual-version records (3.1) cover migrations.

3.5 Version 1 keys

Key Value Semantics
v 1 Record version. REQUIRED.
avatar https URL without the https:// prefix Replacement portrait for chat display (message avatars, hover previews, user lists). Clients supporting high-quality portraits SHOULD prefer it over site avatars.
namecolor one of: red, blue, white, yellow, pink, gray, green, orange, purple, black, brown, cyan Character name color in chat. Named, not hex, so each client maps names onto its own themes and keeps contrast readable.
timezone IANA time zone database name, e.g. America/New_York The character's (player's) declared time zone. Enables exact, DST-correct local time display. Supersedes any guess derived from the site's UTC-offset field.

avatar. The decoded value MUST contain at least one /, MUST NOT contain whitespace or \ anywhere, MUST NOT contain : or @ before its first /, and SHOULD be at most 500 characters; parsers skip values that fail any of these. Each character rule blocks a specific attack: : before the first slash excludes schemes and ports; @ before the first slash excludes userinfo, without which static.f-list.net@evil.example/x.png names evil.example as its host while wearing an allowlisted string as a costume; \ is banned outright because WHATWG-style URL parsers treat it as /, so a value containing it parses differently across platforms. Clients resolve the value by prepending https:// and MUST fetch over https only. Which hosts a client fetches from is client trust policy (section 4.4), but clients honoring avatar SHOULD allow static.f-list.net at minimum, so at least one host behaves identically across all adopters.

namecolor. Matched case-insensitively against the closed set; generators emit lowercase. The matched set name, never the raw value, is what may participate in class names or styles (section 4.4). Unknown name: skip the key.

timezone. The canonical value space is the IANA time zone database as shipped on the implementation's platform. In JavaScript, constructing Intl.DateTimeFormat with the value as the timeZone option is a sufficient validator: an invalid name throws a RangeError. Checking membership in Intl.supportedValuesOf('timeZone') is stricter, since it lists only canonical names, so aliases (Asia/Calcutta) that the constructor accepts would fail it. Either behavior is conforming; other platforms validate against their own tz database. Aliases and platform-dependent edge zones may validate on one client and not another; the failure mode is the key being skipped, which is acceptable. Invalid zone: skip the key.

Privacy note, which generators MUST respect: timezone publishes the player's approximate location to everyone who can view the profile, and some IANA names are geographically narrow. Setting it MUST be opt-in (not selected by default), and generator UIs SHOULD say plainly that the value is public.

3.6 Key registry and governance

The registry lives next to this document in two forms. KEYS.md is the human-readable, authoritative source. keys.json mirrors its entries for validation tools and documentation generators. The F-Chat Horizon organization maintains both representations and this specification. A registry entry records the key name, its value grammar, its display semantics, the version it appeared in, its status, and which clients honor it.

Horizon hosts the specification and registry and does not plan to move them. Hosting is not ownership of the format: the wire format carries no client branding, a registry decision that affects another client's published extension requires that client's agreement, and each client that ships FPM support is offered maintainer access to the standard's repository. The specification and registry are licensed CC-BY 4.0, so any adopter can fork them intact if stewardship fails; that standing possibility is the check on the host.

Clients MUST implement a key's semantics in their own releases. They MUST NOT download the registry at runtime to add or change behavior. The machine-readable registry exists for development tooling, not as executable client configuration.

FPM defines a stable shared base; the registry keeps the feature set open. Any client may propose a key for the registry, and clients may recommend or implement registered keys independently. Support for an optional key is not required for FPM conformance. However, a client that supports a feature SHOULD implement its registered key so users receive consistent results across clients.

Clients may also define extension keys for their own features, provided they do not conflict with a registered key or another published extension. Authors should request a registry entry before publishing an extension so the registry maintainers can reserve the name and document its semantics. A shared extension belongs in the registry.

Keys are never removed or reused. The registry marks a key that turns out badly as deprecated, and clients stop honoring it at their own pace.

Version 1 defines three initial keys. avatar standardizes the existing Rising Portrait, Horizon Portrait, and hqp use cases. namecolor standardizes Horizon Color. timezone adds a small, independently useful field with a well-established value space.

Future keys are added through the registry process. Candidate keys include display-name preferences, pronouns, banners, and content-warning fields. Each proposal must define its value grammar, display semantics, privacy implications, and compatibility requirements before registration.

4. Implementation

4.1 What is necessary, what is optional

Necessary for a conforming client:

  • Parse the record per sections 3.1 to 3.4.
  • Reject unknown versions whole; ignore unknown keys; validate values and skip failures.
  • Parse only the profile description field (3.1).
  • Treat record values as data at every sink. Decoded values can contain <, >, quotes, braces, spaces, and (via escapes) ;, ], [. The conforming ways to consume a value are exactly: match it against a closed set (namecolor), validate and fetch it under an allowlist (avatar), or hand it to a platform validator (timezone). A value that is ever re-serialized into BBCode, HTML, CSS, or any other grammar MUST be re-escaped for that grammar; never string-concatenate a raw value into markup, a class name, or a style.

Optional, recommended:

  • A generator UI so users never hand-type records (Frolic's portrait settings page is prior art).
  • Validating the logged-in user's own record on login and surfacing errors to them (the answer to silent failure, 2.3).
  • A per-feature user setting to disable honoring each key: the user's remedy for portrait impersonation (4.4) and for plain dislike of other people's colors.
  • Continued parsing of the legacy conventions in section 1.2 during migration. When both a legacy convention and an FPM record supply the same feature, the FPM record SHOULD win.

4.2 Parsing algorithm

  1. Scan the description for [i=fpm://...] occurrences, case-insensitively, each payload ending at the first ], optionally stopping at the scan bound (3.1).
  2. For each occurrence: enforce the payload cap; split on ;; for each piece, take the first = as the key/value split, trim both sides, skip bad keys, decode the four escapes, skip empty values and duplicate keys; note the v token.
  3. Among occurrences whose v token is a recognized version, select the one with the highest version, earliest in source order on a tie. Discard the rest.
  4. Hand the selected record's pairs to the client's apply layer, which keeps the keys it knows, validates each value, and skips failures.

A client that recognizes only one version may fold steps 2 and 3 together and stop at the first recognized record; with a single recognized version the two procedures select the same record.

4.3 Display integration

Where a client surfaces each key is its own business. For timezone, the intended experience: compute the character's current local time on demand (hover, profile sidebar) with the platform's zone database. Do not cache the computed time; cache the zone name.

4.4 Security

  • The record is attacker-controlled input. Anyone can put anything in a profile description. Every value crossing into your UI must be treated as plain text; the sink rules in 4.1 are the operative requirement.
  • avatar can trigger third-party network requests. Fetching a profile-supplied image may disclose the viewer's IP address, user agent, and viewing time to the image host. An allowlist check on the initial URL is not sufficient: redirects from an allowlisted host can send the request to an untrusted host. Clients MUST allow avatar only from approved image hosts, MUST enforce HTTPS at the network layer, and MUST either disable redirects or validate every redirect target against the allowlist. Allowlist decisions MUST be made against the host component of a URL produced by the platform's URL parser, never by prefix or substring matching on the string; section 3.5's grammar already rejects the userinfo trick, but the check belongs at both layers, because the parser and the fetcher are usually different code. Clients that operate an image cache or proxy SHOULD use it so image hosts do not receive viewer IP addresses. Removing a failed override improves the UI but does not prevent a request that has already occurred. Each client chooses its own allowlist; this specification requires that one exist.
  • avatar responses are attacker-controlled content. A fetched avatar MUST be consumed strictly as image content: decode it in a bitmap image context per the response's content type, never in a document, frame, or script-capable context. Clients SHOULD reject SVG responses outright or rasterize them in a sandbox where scripts, external loads, and interactivity cannot run. This matters doubly for Electron-based clients, where a rendering context and a privileged application context can be one mistake apart.
  • avatar enables portrait impersonation. Nothing stops a character from wearing another character's portrait, including a moderator's, to support a lookalike name. Clients SHOULD offer the per-key disable from 4.1 and MAY visually distinguish overridden portraits from native ones.
  • namecolor can spoof UI roles. If your client colors staff, mentions, or system messages, a profile-supplied color can imitate that authority. Clients MUST map the matched enum name onto their own palette and MUST NOT let profile-supplied colors collide with colors reserved for privileged UI roles.
  • timezone publishes the profile owner's approximate location. Clients do not need special viewer-side handling beyond parsing and display. Generator UIs MUST require an explicit opt-in and SHOULD explain that the selected IANA zone is public (3.5).
  • Bounded parsing. The grammar has no nesting, the reference regexes are linear (measured, not assumed: megabyte-scale adversarial inputs parse in milliseconds), the payload cap bounds the split, and the record-count bound (3.1) caps the scan. Apply the cap before splitting, as 3.1 says, and parsing cost is negligible.

4.5 Site compliance and site changes

F-List staff have not sanctioned this mechanism. Frolic's hqp and fcg records have been deployed for an extended period without moderation action. That is tolerance, not endorsement. Records remain part of the profile description: users can remove them and moderators can inspect them. FPM uses named, human-readable keys and values so an inspection reveals metadata, not an opaque blob.

Adopters SHOULD seek at least informal staff acknowledgment before promoting FPM to a broad user base. They MUST be able to disable both record generation and record handling if the site disallows the mechanism.

If the site's renderer starts displaying [i] parameters, records become visible text that users can remove. If the site strips records on save, the related features fall back to current behavior. Neither outcome damages the profile itself. A future standard revision that changes key semantics requires a new record version; v1 parsers reject those records by construction.

4.6 Reference implementation and conformance suite

This draft does not include a reference implementation or executable conformance suite. Before this specification reaches a final version, the standard's repository will publish a reference implementation and test vectors covering sections 3.1 through 3.5. The suite will cover record recognition, payload limits, escaping, version handling and multi-record selection, duplicate keys, whitespace, unknown keys, and validation of the version 1 keys, including the avatar character rules of 3.5.

Until then, sections 3.1 through 3.5 are the complete normative definition of conforming behavior. An implementation must not infer new semantics from an example or from a future test suite.

Appendix A: examples

Timezone only, the smallest useful record:

[i=fpm://v=1;timezone=Europe/Berlin][/i]

Everything v1 defines:

[i=fpm://v=1;avatar=static.f-list.net/images/charimage/44262665.png;namecolor=cyan;timezone=America/New_York][/i]

A URL containing a reserved character, escaped; note the query-string = and the %20 need no escaping:

[i=fpm://v=1;avatar=iili.io/weird%3Bpath%20name.png?size=full][/i]

Records a v1 parser rejects whole: [i=fpm://avatar=x.png][/i] (no version), [i=fpm://v=2;timezone=Asia/Tokyo][/i] (unknown version, though a v1 record elsewhere in the same description would still be used). A record it applies partially: [i=fpm://v=1;sparkles=max;timezone=Asia/Tokyo][/i] applies timezone and ignores sparkles.

An avatar value a conforming parser skips, per the 3.5 character rules:

[i=fpm://v=1;avatar=static.f-list.net@evil.example/x.png][/i]

A hypothetical migration profile carrying two versions. A v1-only client rejects the first record and uses the second; a client recognizing v2 uses the first, wherever it appears:

[i=fpm://v=2;timezone=Asia/Tokyo][/i][i=fpm://v=1;timezone=Asia/Tokyo][/i]

Appendix B: avatar hosts commonly allowed (non-normative, mid-2026)

static.f-list.net (the SHOULD-support baseline), freeimage.host, iili.io, redgifs.com, e621.net, plus per-client additions (imgchest.com, toyhou.se in Horizon; imgur.com still in Frolic). Imgur is on its way out of allowlists (UK ban of the site). This list will drift; it is informational only. Remember that an allowlist constrains the initial URL, not the redirect chain, and is compared against the parsed host, not the string (4.4).

Appendix C: migration from existing conventions (non-normative)

For clients that ship the legacy conventions today, the compatible path:

  1. Parse FPM alongside the legacy formats; FPM wins on conflict.
  2. Point record generators at FPM output only.
  3. Keep legacy parsing indefinitely, so lazier users do not lose features.

FPM v1 deliberately keeps hqp's value convention (schemeless https URL) and the ;-separated versioned key=value grammar fcg pioneered, so Frolic's migration is mechanical. FPM adds what no incumbent had: the shared scheme, the escaping rule, and the registry.

{
"spec": "fpm-standard.md",
"specVersion": "0.2.0",
"authoritative": "KEYS.md",
"keys": {
"v": {
"status": "structural",
"since": 1,
"required": true,
"value": "decimal integer, no leading zeros, no sign"
},
"avatar": {
"status": "active",
"since": 1,
"value": "https URL without the https:// prefix",
"maxLength": 500,
"honoredBy": []
},
"namecolor": {
"status": "active",
"since": 1,
"value": "one of the registered color names, matched case-insensitively",
"enum": [
"red",
"blue",
"white",
"yellow",
"pink",
"gray",
"green",
"orange",
"purple",
"black",
"brown",
"cyan"
],
"honoredBy": []
},
"timezone": {
"status": "active",
"since": 1,
"value": "IANA time zone database name",
"honoredBy": []
}
}
}

FPM key registry

This file is the authoritative list of FPM record keys. A key exists once it has an entry here; its meaning is what its entry says. The specification (fpm-standard.md) defines the format itself; this registry defines the vocabulary. keys.json mirrors these entries for tooling and MUST stay in sync.

Registry rules, from specification section 3.6:

  • Keys are never removed or reused. A key that turns out badly is marked deprecated; clients stop honoring it at their own pace.
  • Support for any key other than v is optional. A client that supports a feature SHOULD implement its registered key rather than invent a private one.
  • Clients MUST NOT fetch this registry at runtime to add or change behavior. Key support ships in client releases; this file exists for people and development tooling.
  • A registry decision that affects another client's published extension requires that client's agreement.

Proposing a key

Open a pull request adding an entry in the format below. A proposal MUST define, before registration:

  1. Value grammar: what strings are valid, with validation rules.
  2. Display semantics: what a client that honors the key shows, and where.
  3. Privacy implications: what the value reveals about the profile owner, and any generator obligations (opt-in, warnings).
  4. Compatibility: legacy conventions the key replaces, and how clients should treat conflicts with them.

To reserve a name for a client-specific extension before publishing it, open the same kind of PR with status reserved.

Statuses: active (registered, safe to implement), reserved (name claimed, semantics not final; do not ship parsers), deprecated (do not generate; honoring is discouraged).

Keys

Key Value Status Since
v record version integer structural v1
avatar https URL, scheme stripped active v1
namecolor one of 12 color names active v1
timezone IANA time zone name active v1

v

  • Value: decimal integer, no leading zeros, no sign. Defined by specification section 3.4.
  • Semantics: record version. REQUIRED in every record; not a metadata key.
  • Since: v1. Status: structural. This name is not available for registration.

avatar

  • Value: an https URL with the https:// prefix removed. After decoding, the value MUST contain at least one /, MUST NOT contain whitespace or \ anywhere, MUST NOT contain : or @ before its first /, and SHOULD be at most 500 characters (specification 3.5).
  • Display semantics: replacement portrait for chat display: message avatars, hover previews, user lists. Clients supporting high-quality portraits SHOULD prefer it over site avatars.
  • Privacy and security: fetching the image can disclose the viewer's IP and viewing time to the host. Clients MUST apply the fetch rules of specification 4.4: host allowlist, https at the network layer, redirect validation, image-only decoding context.
  • Compatibility: replaces [url=<url>]Rising Portrait[/url], [url=<url>]Horizon Portrait[/url], and Frolic's [i=hqp://<url>][/i]. The value convention is identical to hqp. When a legacy convention and an FPM record are both present, the FPM record SHOULD win.
  • Since: v1. Status: active. Honored by: none yet (Horizon implementation planned).

namecolor

  • Value: one of red, blue, white, yellow, pink, gray, green, orange, purple, black, brown, cyan. Matched case-insensitively; generators emit lowercase.
  • Display semantics: character name color in chat. Clients map the matched name onto their own themes; the raw value never reaches markup, class names, or styles (specification 4.4). Colors MUST NOT collide with colors reserved for privileged UI roles.
  • Privacy implications: none beyond the profile being public.
  • Compatibility: replaces [color=<name>]Horizon Color[/color]. Same precedence rule as avatar.
  • Since: v1. Status: active. Honored by: none yet (Horizon implementation planned).

timezone

  • Value: an IANA time zone database name, e.g. America/New_York. Validated against the platform's tz database; unknown names are skipped.
  • Display semantics: the player's declared time zone. Intended use: compute current local time on demand (hover, profile sidebar). Cache the zone name, never the computed time. Supersedes any guess derived from the site's UTC-offset field.
  • Privacy implications: publishes the player's approximate location, and some IANA names are geographically narrow. Generators MUST make setting it opt-in and SHOULD state that the value is public (specification 3.5).
  • Compatibility: no legacy convention. The site's own UTC-offset field remains whatever the site says it is; this key does not modify it.
  • Since: v1. Status: active. Honored by: none yet (Horizon implementation planned).

Candidates under discussion, not reserved

These names have come up in design discussions and are recorded here so proposals can find prior thinking. They are NOT registered and NOT reserved; shipping a parser for them now is premature.

  • pronouns: needs a value-grammar decision (free text vs enumerated) and a length cap before proposal.
  • banner: a second image URL; would inherit avatar's value grammar and fetch rules wholesale.
  • Content-warning fields: value grammar and display behavior undecided.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment