Build composable, interconnected web applications using iframes and data pipes
@metapages/metapage is a JavaScript library that lets you create and embed interactive workflows in the browser by connecting independent iframe components together through input/output data pipes.
- 📚 Full Documentation
- 🎨 Live Examples
- 🛠️ Interactive Tools
- đź’» Example Component
A metapage is a web application made up of connected iframes called metaframes. Each metaframe can:
- Receive data from other metaframes (inputs)
- Send data to other metaframes (outputs)
- Run independently (JavaScript, Docker containers, markdown editors, or any web component)
Think of it like a visual programming environment where each component is a full web application that can communicate with others through simple JSON data pipes.
npm install @metapages/metapageOr use directly from CDN:
import { renderMetapage, Metapage, Metaframe } from "https://cdn.jsdelivr.net/npm/@metapages/metapage@1.8.35";The simplest way to embed a workflow is using renderMetapage:
import { renderMetapage } from "@metapages/metapage";
// Fetch a metapage definition (or define your own JSON)
const response = await fetch(
"https://metapage.io/m/87ae11673508447e883b598bf7da9c5d/metapage.json"
);
const definition = await response.json();
// Render it
const { setInputs, dispose } = await renderMetapage({
definition,
rootDiv: document.getElementById("container"),
});The renderMetapage function the react-grid-layout layout in metapage.json:
{
"meta": {
"layouts": {
"react-grid-layout": {
...
}
}
}
}If you're building a component to use in a metapage:
import { Metaframe } from "https://cdn.jsdelivr.net/npm/@metapages/metapage@1.8.35";
const metaframe = new Metaframe();
// Listen for input data from other metaframes
metaframe.onInput("data", (value) => {
console.log("Received:", value);
// Process the data and send output
metaframe.setOutput("result", value.toUpperCase());
});
// Or listen to all inputs at once
metaframe.onInputs((inputs) => {
console.log("All inputs:", inputs);
});A metapage is defined using JSON that specifies which metaframes to load and how they connect:
{
"version": "2",
"metaframes": {
"input": {
"url": "https://editor.mtfm.io/#?hm=disabled"
},
"processor": {
"url": "https://js.mtfm.io/",
"inputs": [
{
"metaframe": "input",
"source": "text",
"target": "code"
}
]
},
"output": {
"url": "https://markdown.mtfm.io/",
"inputs": [
{
"metaframe": "processor",
"source": "output"
}
]
}
}
}This creates a pipeline: input → processor → output
Pipes connect metaframe outputs to other metaframe inputs:
{
"metaframe": "sourceMetaframeId", // Where data comes from
"source": "outputPipeName", // Name of the output pipe
"target": "inputPipeName" // Name of the input pipe (optional, defaults to source)
}The library automatically handles serialization of complex data types:
// In a metaframe - these are automatically serialized when sent between iframes
metaframe.setOutput("file", new File([blob], "data.txt"));
metaframe.setOutput("binary", new Uint8Array([1, 2, 3]));
metaframe.setOutput("buffer", arrayBuffer);
// And automatically deserialized when received
metaframe.onInput("file", (file) => {
console.log(file instanceof File); // true
});<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<style>
body { margin: 0; padding: 0; width: 100vw; height: 100vh; }
#metapage-container { width: 100%; height: 100%; }
</style>
</head>
<body>
<div id="metapage-container"></div>
<script type="module">
import { renderMetapage } from "https://cdn.jsdelivr.net/npm/@metapages/metapage@1.8.35";
const definition = await fetch(
"https://metapage.io/m/87ae11673508447e883b598bf7da9c5d/metapage.json"
).then(r => r.json());
const { setInputs, dispose, metapage } = await renderMetapage({
definition,
rootDiv: document.getElementById("metapage-container"),
onOutputs: (outputs) => {
console.log("Metaframe outputs:", outputs);
},
options: {
hideFrameBorders: true,
hideOptions: true,
}
});
// Send inputs to metaframes
setInputs({
"metaframeId": {
"inputPipeName": "some value"
}
});
// Clean up when done
// dispose();
</script>
</body>
</html>import { Metaframe } from "https://cdn.jsdelivr.net/npm/@metapages/metapage@1.8.35";
const metaframe = new Metaframe();
// Handle inputs
metaframe.onInputs((inputs) => {
const { data, config } = inputs;
// Process inputs
const result = processData(data, config);
// Send outputs
metaframe.setOutputs({
result: result,
timestamp: Date.now()
});
});
// Individual input listener
metaframe.onInput("reset", () => {
metaframe.setOutputs({});
});
// Get a specific input value
const currentValue = metaframe.getInput("data");
// Get all inputs
const allInputs = metaframe.getInputs();
// Clean up
metaframe.dispose();import { Metapage } from "@metapages/metapage";
const metapage = new Metapage({
definition: {
version: "2",
metaframes: {
viewer: {
url: "https://markdown.mtfm.io/"
}
}
}
});
// Listen to metaframe outputs
metapage.on(Metapage.OUTPUTS, (outputs) => {
console.log("Outputs from all metaframes:", outputs);
});
// Set inputs to metaframes
await metapage.setInputs({
viewer: {
text: "# Hello World"
}
});
// Get current outputs
const outputs = metapage.getState().metaframes.outputs;
// Clean up
metapage.dispose();Metaframes can read and write to their URL hash parameters:
import {
getHashParamValueJsonFromWindow,
setHashParamValueJsonInWindow
} from "https://cdn.jsdelivr.net/npm/@metapages/metapage@1.8.35";
// Read from URL hash
const config = getHashParamValueJsonFromWindow("config");
// Write to URL hash
setHashParamValueJsonInWindow("config", { theme: "dark" });Use glob patterns to match multiple outputs:
{
"inputs": [
{
"metaframe": "source",
"source": "data/*", // Matches data/foo, data/bar, etc.
"target": "inputs/"
}
]
}// Send binary data
const imageData = await fetch("/image.png").then(r => r.arrayBuffer());
metaframe.setOutput("image", imageData);
// Receive and use
metaframe.onInput("image", async (data) => {
const blob = new Blob([data]);
const url = URL.createObjectURL(blob);
document.getElementById("img").src = url;
});Render a metapage into a DOM element.
Parameters:
definition: Metapage definition objectrootDiv: DOM element to render intoonOutputs: Callback for metaframe outputs (optional)options: Rendering options (optional)hideBorder: Hide metapage borderhideFrameBorders: Hide individual metaframe bordershideOptions: Hide options panelhideMetaframeLabels: Hide metaframe labels
Returns: { setInputs, setOutputs, dispose, metapage }
Methods:
setInputs(inputs): Set inputs for metaframesgetState(): Get current state (inputs/outputs)dispose(): Clean up and remove all listenerson(event, handler): Listen to events
Events:
Metapage.OUTPUTS: When metaframe outputs changeMetapage.INPUTS: When metapage inputs changeMetapage.DEFINITION: When definition changes
Methods:
setOutput(name, value): Set a single outputsetOutputs(outputs): Set multiple outputsgetInput(name): Get a single input valuegetInputs(): Get all input valuesonInput(name, callback): Listen to specific inputonInputs(callback): Listen to all inputsdispose(): Clean up
Properties:
id: Metaframe ID assigned by parent metapageisInputOutputBlobSerialization: Enable/disable automatic binary serialization
Any web application can become a metaframe by:
- Loading the library
- Creating a
Metaframeinstance - Listening for inputs
- Sending outputs
Example minimal metaframe:
<!DOCTYPE html>
<html>
<head>
<title>My Metaframe</title>
</head>
<body>
<script type="module">
import { Metaframe } from "https://cdn.jsdelivr.net/npm/@metapages/metapage@1.8.35";
const metaframe = new Metaframe();
metaframe.onInputs((inputs) => {
// Your logic here
metaframe.setOutput("result", "processed: " + JSON.stringify(inputs));
});
</script>
</body>
</html>Full TypeScript definitions are included:
import {
Metapage,
Metaframe,
MetapageDefinitionV2,
MetaframeInputMap,
MetapageInstanceInputs
} from "https://cdn.jsdelivr.net/npm/@metapages/metapage@1.8.35";
const definition: MetapageDefinitionV2 = {
version: "2",
metaframes: {
example: {
url: "https://example.com"
}
}
};
const metapage = new Metapage({ definition });- Chrome 78+
- Modern browsers with ES2020 support
- ES modules required
Apache-2.0
Issues and pull requests welcome at https://github.com/metapages/metapage