1. Introduction
This section is non-normative.
HyperMap is a format for REST APIs that turns them into explorable, interactive networks similar to the World Wide Web. It is built around two core ideas:
-
It is self-descriptive — metadata about using the API is embedded in responses themselves, reducing the need for out-of-band documentation or machine-readable specifications.
-
It supports code on demand — servers can send JavaScript or WebAssembly to the client for execution, enabling dynamic features such as reactive data, streaming, and client-side computation.
A HyperMap resource is a JSON object served with the
application/vnd.hypermap+json media type. It uses a reserved # key
to carry attributes such as navigation links, HTTP methods,
and script references.
When parsed, a HyperMap resource becomes a tree of nodes — maps, lists, and values — that form a DOM-like data model. This tree supports mutation, event dispatch with bubbling, and script execution.
{ "#" : { "href" : "/stocks/AAPL" , "scripts" : [ "/js/price-stream.js" ] }, "ticker" : "AAPL" , "price" : 187.42 , "company" : { "#" : { "href" : "/companies/AAPL" }, "name" : "Apple Inc." } }
2. Infrastructure
2.1. Conformance
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119].
2.2. Terminology
This specification depends on the following:
- [JSON]
-
The JSON data interchange format.
- [URL]
-
URL parsing and resolution.
- [FETCH]
-
The Fetch standard for network requests.
- [ECMASCRIPT]
-
The ECMAScript language specification.
- [DOM]
-
The DOM standard, specifically the
EventTargetinterface. - [HTML]
-
The HTML standard, specifically browsing contexts, navigation, and module script loading.
- [WEBIDL]
-
Web IDL type definitions and binding semantics.
2.3. URL Resolution
The base URL of a HyperMap resource is the URL from
which the resource was fetched, after any redirects. All relative URLs in a
resource — including href values in attributes and
script URLs — are resolved against this base URL using the URL
resolution algorithm defined in [URL].
<base> element.
HyperMap does not currently define a mechanism to override the base URL.
3. The HyperMap Format
3.1. Media Type
A HyperMap resource is served with the media type
application/vnd.hypermap+json.
Servers MUST use this media type for all HyperMap responses. Clients SHOULD
use this media type in Accept headers when requesting HyperMap resources.
3.2. Top-Level Structure
The top level of a HyperMap resource MUST be a JSON object (not an array, string, number, boolean, or null).
[{"title": "Learn HyperMap"}] should become
{"todos": [{"title": "Learn HyperMap"}]}.
3.3. The # Key
The key # is reserved in HyperMap’s JSON serialization. When present on a
JSON object, its value MUST be a JSON object containing zero or more attributes.
An attributes object MAY contain the following members:
href-
A string representing a URL, either absolute or relative to the resource’s base URL. When present, the containing object becomes a control.
method-
A string representing an HTTP method (e.g.,
"GET","POST","PATCH","DELETE"). Defaults to"GET"if omitted. Method strings MUST be uppercase. Clients MUST send the method string as-is without normalizing case. Implementations MUST NOT assume a fixed set of HTTP methods — any valid HTTP method is permitted. scripts-
An array of strings, each a URL pointing to a script to be loaded and executed by the HyperMap user agent. Scripts are scoped to the
MapNodethat declares them. See § 6 Script Execution.
The # key MUST NOT be treated as regular data. Implementations MUST extract
it during parsing and store its contents as the attributes
on the resulting MapNode.
3.4. Controls
A control is a JSON object whose attributes
include an href value. Controls represent navigable references to other
resources or actions that can be triggered.
A control’s method defaults to "GET" if not specified. The child nodes of
the control (excluding the # key) serve as form fields. For GET requests,
form data is serialized as URL query parameters. For all other methods, form
data is serialized as a JSON request body.
{ "todos" : [ { "#" : { "href" : "5J0rwwsyh/" }, "title" : "Learn HyperMap" , "completed" : false } ], "newTodo" : { "#" : { "href" : "/todos/" , "method" : "POST" }, "title" : "" } }
The first todo has an href pointing to its individual resource. The
newTodo control will POST its contents to /todos/ when triggered.
3.5. Value Types
Within a HyperMap resource, values can be:
- JSON objects
-
Parsed into
MapNodeinstances. If the object contains a#key, its contents become the node’s attributes. - JSON arrays
-
Parsed into
ListNodeinstances. - JSON primitives
-
null, booleans, numbers, and strings are parsed intoValueNodeinstances.
4. Data Model
When a HyperMap resource is parsed, it produces a tree of nodes. This tree is the HyperMap data model — the equivalent of the DOM in HTML.
The class hierarchy is:
EventTarget
└── HypermapNode
├── ValueNode
└── CollectionNode
├── MapNode
│ └── Hypermap
└── ListNode
4.1. HypermapNode
HypermapNode is the abstract base of all nodes in the
HyperMap tree. It extends EventTarget.
[Exposed=*]interface :HypermapNode EventTarget {readonly attribute HypermapNode ?; };parentNode
HypermapNode in this specification to avoid
collision with the DOM’s Node interface. Implementations MAY use the name
Node internally.
Every HypermapNode has a parent,
which is either another HypermapNode or null. The root node of a
HyperMap resource has a null parent.
A node’s parent is set when it is attached to a tree and MUST NOT be changed after attachment, except by internal tree operations (reparenting).
4.1.1. Event Dispatch
When dispatchEvent() is called on a HypermapNode, the
event propagates through the tree following the
event dispatch
algorithm defined in [DOM]:
-
Capture phase: Starting from the root node, the event is dispatched on each ancestor down to (but not including) the target node. Only listeners registered with
capture: trueare invoked. -
Target phase: The event is dispatched on the target node itself.
-
Bubble phase: The event is dispatched on each ancestor from the target’s parent up to the root node. This provides event bubbling through the HyperMap tree.
Listeners may call stopPropagation() to prevent further dispatch in any
phase.
4.1.2. Reparenting
Before attaching a node as a child, implementations MUST check for cycles. If the node to be attached is an ancestor of the target parent, a cycle exists and the operation MUST throw an error.
If the node being attached already has a parent, it MUST first be detached from its current parent.
4.2. CollectionNode
CollectionNode is the base type for
nodes that contain other nodes — either as key-value pairs (MapNode) or
as an ordered sequence (ListNode).
[Exposed=*]interface :CollectionNode HypermapNode {readonly attribute unsigned long ;size any ();toJSON any (); };toView
collection.size- Returns the number of children in the collection.
collection.toJSON()-
Returns a lossless JSON-serializable representation of the collection.
Child nodes are recursively serialized via their own
toJSON(). The output can be passed back tofromJSON()to reconstruct an equivalent node tree. collection.toView()-
Returns a lossy JSON-serializable representation suitable for consumers.
Child nodes are recursively converted via their own
toView(). Controls have their#key replaced with{"type": "control"}—consumers see that the node is a control but do not receive its full attributes. This is the representation intended for rendering and integration scripts that do not need to know about hypermedia affordances.
4.3. MapNode
A MapNode represents an ordered map of string keys to node
values. It corresponds to a JSON object in the wire format.
dictionary {HypermapAttributes USVString ;href USVString = "GET";method sequence <USVString >; }; [Exposed=*]scripts interface :MapNode CollectionNode {readonly attribute HypermapAttributes ;attributes boolean (has DOMString );key HypermapNode ?(at DOMString );key MapNode (set DOMString ,key HypermapNode );value MapNode (delete DOMString ); };key
The attributes of a MapNode are
extracted from the # key during parsing. If no # key is present, the
attributes are an empty object.
map.has(key)- Returns true if map contains an entry for key.
map.at(key)- Returns the node associated with key, or null if no such entry exists.
map.set(key, value)-
Sets the entry for key to value.
The value MUST be a
HypermapNodeinstance. If value has an existing parent, it is first reparented. The value is then attached as a child of map. A mutation event is dispatched. Returns map. map.delete(key)- Removes the entry for key. If the entry exists, the child node is detached. A mutation event is dispatched. Returns map.
4.4. ListNode
A ListNode represents an ordered sequence of node values.
It corresponds to a JSON array in the wire format.
[Exposed=*]interface :ListNode CollectionNode {HypermapNode ?(at long );index ListNode (set long ,index HypermapNode );value ListNode (append HypermapNode );value ListNode (prepend HypermapNode );value ListNode (insert long ,index HypermapNode );value ListNode (delete long ); };index
list.at(index)- Returns the node at index. Supports negative indices (from the end).
list.set(index, value)-
Replaces the node at index with value.
The value MUST be a
HypermapNodeinstance. The previous node at that index, if any, is detached. A mutation event is dispatched. Returns list. list.append(value)- Adds value to the end of the list. A mutation event is dispatched. Returns list.
list.prepend(value)- Adds value to the beginning of the list. A mutation event is dispatched. Returns list.
list.insert(index, value)- Inserts value at index, shifting subsequent elements. A mutation event is dispatched. Returns list.
list.delete(index)- Removes the node at index, shifting subsequent elements. The removed node is detached. A mutation event is dispatched. Returns list.
4.5. ValueNode
A ValueNode wraps a single primitive value.
[Exposed=*]interface :ValueNode HypermapNode {attribute any ;value any ();toJSON any (); };toView
The value of a ValueNode MUST be one
of: null, a boolean, a number, or a string. These correspond to the JSON
primitive types.
node.value- The primitive value held by this node.
node.toJSON()- Returns the primitive value. For
ValueNode,toJSON()is lossless by definition. node.toView()- Returns the primitive value. For
ValueNode,toView()is identical totoJSON().
4.6. Hypermap
The Hypermap interface is the top-level entry point,
representing a parsed HyperMap resource. It extends MapNode.
[Exposed=*]interface :Hypermap MapNode {readonly attribute Document ?;document static Hypermap (fromJSON USVString );json Promise <sequence <any >>();start ValueNode (input sequence <(DOMString or long )>,path any );value HypermapNode (use sequence <(DOMString or long )>);path HypermapNode (nodeFromPath sequence <(DOMString or long )>); };path
hypermap.document-
Returns the
Documentof the browsing context in which thisHypermapwas loaded, or null if running outside a browser environment. Hypermap.fromJSON(json)- Parses a JSON string into a
Hypermapnode tree. See § 4.7 Parsing. hypermap.start()- Walks the node tree and loads scripts for
every
MapNodethat declares them. See § 6.2 Script Loading. hypermap.input(path, value)-
Resolves path to a
ValueNode, sets its value, and dispatches an input event on the target node. Returns the target node. hypermap.use(path)-
Resolves path to a
HypermapNodeand dispatches a use event on it. Returns the target node. hypermap.nodeFromPath(path)-
Resolves an array of keys and indices to a node by traversing the tree.
Each element of path is used as a key (for
MapNode) or index (forListNode) to descend one level.
4.7. Parsing
The Hypermap.fromJSON(json) static method parses a JSON
string into a Hypermap node tree. The algorithm is as follows:
To parse a HyperMap resource from a JSON string json:
-
Let result be the result of parsing json using a JSON reviver that applies the following rules to each value:
-
If value is a JSON primitive (null, boolean, number, or string), return a new
ValueNodewith its value set to value. -
If value is a JSON array, return a new
ListNodewhose elements are the recursively converted items of the array. -
If value is a JSON object:
-
If value has a
#key, let attrs be its value. attrs MUST be a JSON object. Extract from attrs thehref,method, andscriptsmembers and construct aHypermapAttributesdictionary. -
Remove the
#key from value. -
Return a new
MapNodewhose attributes are the extractedHypermapAttributes(or an empty dictionary if no#key was present), with the remaining key-value pairs as children, each recursively converted.
-
-
-
Assert: result is a
MapNode(since the top level MUST be a JSON object per § 3.2 Top-Level Structure). -
Wrap result in a new
Hypermapinstance and return it.
5. Events
The HyperMap data model uses events to signal changes and interactions.
5.1. Mutation Events
A mutation event is an Event with the name "mutation".
Whenever a tree-modifying operation occurs on a MapNode or
ListNode — specifically set(), delete(),
set(), append(), prepend(),
insert(), or delete() — a mutation event MUST
be dispatched on the global object (window in browser environments).
MutationObserver in [DOM] due to performance concerns. Future versions
of this specification are expected to adopt an observer-based model, where
scripts register observers on specific nodes and receive batched, asynchronous
notifications of changes within their subtree. This would align with both the
DOM’s current approach and HyperMap’s scoped script model, where each script
naturally observes only its own portion of the tree.
5.2. Input Events
An input event is a CustomEvent with the name "input".
It is dispatched when input() is called.
The event’s detail object contains a target property
referencing the ValueNode whose value was set.
Input events are dispatched on the target node and bubble up the tree.
5.3. Use Events
A use event is a CustomEvent with the name "use". It
is dispatched when use() is called.
The event’s detail object contains a target property
referencing the HypermapNode that was used.
Use events are dispatched on the target node and bubble up the tree.
5.4. Event Bubbling
As defined in § 4.1.1 Event Dispatch, events dispatched on any
HypermapNode propagate through the tree via capture and bubble phases.
This enables listeners at any level of the tree to observe events from their
descendants.
6. Script Execution
HyperMap supports code on demand: servers can include script references in their responses, and clients execute those scripts.
6.1. Script Declaration
Scripts are declared in the attributes of any MapNode
using the scripts key. The value MUST be an array of strings, each being a
URL (absolute or relative) pointing to a script resource.
{ "#" : { "href" : "/stocks" , "scripts" : [ "/js/portfolio-summary.js" ] }, "AAPL" : { "#" : { "href" : "/stocks/AAPL" , "scripts" : [ "/js/price-stream.js" ] }, "price" : 187.42 }, "GOOG" : { "#" : { "href" : "/stocks/GOOG" , "scripts" : [ "/js/price-stream.js" ] }, "price" : 141.80 } }
Each stock node loads its own price streaming script. The root loads a portfolio summary script. The same script URL may appear on multiple nodes —each instance runs independently with its own scoped reference.
6.2. Script Loading
When start() is called, the HyperMap user agent walks the
node tree and begins loading scripts for every MapNode that declares them.
To load scripts for a MapNode node:
-
Let scripts be the
scriptsarray from node’s attributes, or an empty array if not present. -
For each scriptURL in scripts, in parallel:
-
Resolve scriptURL against the resource’s base URL.
-
Load the script as an ECMAScript module using dynamic
import(). -
The script’s
hypermapglobal MUST be set to node. See § 6.3 Script Environment.
-
Scripts load asynchronously. No ordering is guaranteed between scripts on the same node or across different nodes.
6.2.1. Reactive Script Loading
Script loading is reactive to tree mutations. When a tree mutation adds or
modifies a scripts attribute on a MapNode, the HyperMap user agent
MUST load scripts for that node.
When a MapNode is removed from the tree, any scripts that are still loading
for that node MUST be cancelled. Scripts that have already loaded SHOULD be
given an opportunity to clean up (e.g., closing network connections) before
being discarded.
scripts array in a child node’s
attributes, causing new scripts to be loaded for that
node. This enables dynamic composition of behavior.
6.3. Script Environment
Scripts loaded by a MapNode execute as ECMAScript modules. Each script
receives a hypermap global that references the MapNode whose
attributes declared it — not the root of the tree.
This scoping ensures that scripts are composable: a script that manages a stock price stream only needs to know about its own node, not the structure of the larger tree. The same script can be attached to multiple nodes and each instance operates independently.
Scripts interact with their portion of the HyperMap tree through the node interfaces defined in § 4 Data Model. A script may:
-
Modify the tree using
set(),delete(),append(), and related methods -
Listen for events using
EventTarget’saddEventListener()— events from descendant nodes will bubble up through the script’s node -
Establish network connections (e.g.,
EventSource,WebSocket) to update the tree reactively
// hypermap refers to the MapNode this script is attached to, // e.g. the "AAPL" node, not the root. const href= hypermap. attributes. href; const stockSocket= new WebSocket( wss: //example.com${href}/stream ); stockSocket. onmessage= ( event) => { const msg= JSON. parse( event. data); hypermap. set( "price" , new ValueNode( msg. price)); };
7. Processing Model
This section defines the end-to-end lifecycle of a HyperMap resource from initial load through interaction.
7.1. Resource Lifecycle
When a HyperMap user agent loads a resource, it performs the following steps:
-
Fetch the resource from the network, including any applicable credentials for user agent requests.
-
Parse the HyperMap resource from the response body to produce a
Hypermapnode tree. -
Set the base URL to the response’s final URL (after redirects).
-
If running in a browser, set the
Hypermap’sdocumentattribute to the enclosingDocument. -
Call
start()to load scripts for allMapNodes in the tree that declare them. -
The resource is now live — scripts may modify the tree, listen for events, and establish network connections.
When navigation occurs (via control activation or equivalent), the current resource’s lifecycle ends: pending script loads are cancelled, the node tree is discarded, and the lifecycle begins again from step 1 with the new resource.
8. Fetching and Navigation
8.1. Control Activation
When a control is activated (via use() or equivalent client
interaction), the HyperMap user agent SHOULD perform the following:
To activate a control on a MapNode control:
-
Let href be control’s attributes'
href. -
Let method be control’s attributes'
method, defaulting to"GET". -
Resolve href against the resource’s base URL to obtain url.
-
If method is
"GET", serialize the contents of control (excluding the#key) as URL-encoded query parameters and append them to url. Fetch url with method GET. -
Otherwise, serialize the contents of control (excluding the
#key) as JSON. Fetch url with the specified method, aContent-Typeofapplication/json, and the serialized JSON as the request body. -
Process the response as defined in § 8.2 Form Submission.
8.2. Form Submission
A form submission occurs when a control is activated.
The child nodes of the control
(excluding the # key) serve as form fields — analogous to input elements
in an HTML form.
To populate a form, a client calls input() on the ValueNode
children of the control to set their values before activation.
{ "placeOrder" : { "#" : { "href" : "/stocks/AAPL/orders/" , "method" : "POST" }, "quantity" : 0 } }
The client sets the quantity value via hypermap.input(["placeOrder", "quantity"], 10), then activates the control via
hypermap.use(["placeOrder"]).
After submitting the request, the HyperMap user agent processes the response as follows:
-
If the response is a redirect (3xx with a
Locationheader), the user agent MUST follow the redirect and navigate to the target URL. -
If the response is a success (2xx) with a HyperMap resource body, the user agent parses it and navigates to the new resource.
8.3. Content Negotiation
Clients requesting HyperMap resources SHOULD include
application/vnd.hypermap+json in their Accept header.
Servers MUST respond with the application/vnd.hypermap+json content type when
the client requests a HyperMap resource with a matching Accept header.
Servers MAY also respond with text/html when the client accepts HTML (see
§ 9.1 Browser-Based User Agents).
9. User Agents
This section is non-normative where noted.
A HyperMap user agent is any software that fetches, parses, and presents HyperMap resources. This includes browser-based clients, command-line tools, and programmatic API clients.
A conforming HyperMap user agent MUST:
-
Fetch resources and parse them according to § 4.7 Parsing.
-
Construct the node tree defined in § 4 Data Model.
-
Support event dispatch as defined in § 5 Events.
-
Execute scripts as defined in § 6 Script Execution, if the agent supports scripting.
9.1. Browser-Based User Agents
When a HyperMap user agent runs within a web browser, the HyperMap resource is embedded in an HTML document. The user agent script manages the lifecycle of the HyperMap node tree within the browsing context of that document.
To deliver a HyperMap resource to a browser, a server responds with an HTML document that:
-
Embeds the JSON representation of the resource in the document body.
-
Includes a user agent script that parses the embedded JSON, constructs the node tree, and calls
start()to begin script execution.
The root Hypermap node’s document attribute MUST be set to the
Document of the enclosing browsing context. This gives scripts access to
same-origin platform APIs such as localStorage and other APIs scoped to
the document’s origin.
<pre> element or an inline script — is
left to the user agent implementation.
A minimal HTML document delivering a HyperMap resource:
<!DOCTYPE html> < html > < head > < script type = "module" src = "/hypermap-shim.js" ></ script > </ head > < body > < pre > {"#": {"href": "/todos"}, "todos": []}</ pre > </ body > </ html >
9.2. Navigation
When control activation produces a new HyperMap resource, the HyperMap user agent replaces the current resource with the new one. The previous node tree is discarded and a new tree is constructed from the response.
In browser-based user agents, navigation updates the browsing context’s URL and browsing history using the mechanisms defined in [HTML].
10. Security Considerations
The security goal for HyperMap is that scripts operate with minimal authority by default. This section describes the desired security properties and the constraints on achieving them.
10.1. Credential Model
This specification distinguishes between two classes of network request:
- User agent requests
-
Requests made by the HyperMap user agent itself — during control activation, navigation, and initial resource loading. These represent the user’s intent and SHOULD include ambient credentials (cookies, session tokens) as normal, analogous to a browser navigating to a URL when the user clicks a link.
- Script-initiated requests
-
Requests made by scripts via
fetch(),WebSocket,EventSource, or similar APIs. These run autonomously and SHOULD NOT include ambient credentials. Specifically, script-initiated requests SHOULD omit cookies (credentials: "omit"), theRefererheader (referrerPolicy: "no-referrer"), and anyAuthorizationheaders from the browsing context.
fetch() as the user agent itself, making this distinction unenforceable
without additional isolation mechanisms such as Secure ECMAScript (SES)
compartments. Implementations that achieve script isolation MUST enforce
credential stripping; implementations without isolation SHOULD document this
limitation.
10.2. Script Scope Isolation
As described in § 6.3 Script Environment, each script’s hypermap global
references only the MapNode that declared it. When script isolation is
enforced, implementations SHOULD additionally ensure:
-
A script cannot traverse above its declaring node via
parentNodeto access other parts of the tree. -
A script does not have direct access to the global
windowordocumentobjects. Access to the hostDocumentis available through the rootHypermapnode’sdocumentattribute when explicitly needed, but scripts scoped to subtrees should not implicitly receive it.
10.3. Open Questions
This section is non-normative.
The following security topics are recognized but not yet addressed by this specification:
-
Resource transclusion: A future
relattribute (e.g.,rel="embed") may allow a node to transclude another HyperMap resource inline. This raises questions about credential scope — should the transcluded resource inherit the parent’s credentials, use its own origin’s credentials, or have none? -
Cross-origin scripts: The current specification does not define restrictions on loading scripts from different origins than the resource that declared them.