How to write your own extension
Published May 21, 2026 · 6 min read
Writing your own extension is a powerful way to add custom geometric functions to the Geomatic editor. This guide walks you through the process step by step.
Quick start
The fastest way to get started is to use the template repository. Fork it, and you'll have a working extension structure ready to customize.
What is an extension?
An extension is a JavaScript module that exports one or more geometric functions. Each function follows a standard interface and runs in a sandboxed Web Worker, keeping it isolated from the editor's internal state.
The basic structure
Every exported function in your extension must have:
{
name: string, // PascalCase display name
keyword: string, // kebab-case command name
parameters: UserParam[],
outputType: string, // Primary output type: 'Point', 'Scalar', 'Line', etc.
compute: (inputs) => Record<string, any>,
}
Parameters define what inputs your function accepts:
{
argName: string, // Key name in compute inputs
type: string, // 'Scalar', 'Point', 'Line', 'Circle', etc.
defaultValue: string | number,
variadic: boolean, // If true, captures remaining args
}
Writing the compute function
The compute function receives inputs as plain objects and must return a blueprint object:
compute(inputs) {
// inputs is Record<string, any> keyed by argName
// return Record<string, any> blueprint
}
Reading inputs
Different node types arrive as different structures:
- Scalar: A plain number
- Point:
{ type: 'Point', x: number, y: number } - Line:
{ type: 'Line', p1: { type: 'Point', x, y }, p2: { type: 'Point', x, y } }
Returning outputs
Always return plain objects with plain JS numbers:
return {
main: { type: 'Point', x: number, y: number } // 'main' is required
};
You can also return auxiliary nodes by adding extra keys:
return {
midpoint: { type: 'Point', x: mx, y: my }, // auxiliary
main: { type: 'Line', p1, p2 }, // primary output
};
Example: Distance between two points
export const Distance = {
name: 'Distance',
keyword: 'distance',
outputType: 'Scalar',
parameters: [
{ argName: 'p1', type: 'Point', defaultValue: 0, variadic: false },
{ argName: 'p2', type: 'Point', defaultValue: 0, variadic: false },
],
compute({ p1, p2 }) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
return { main: { type: 'Scalar', value: Math.sqrt(dx * dx + dy * dy) } };
}
};
Example: Midpoint with auxiliary line
export const MidpointWithLine = {
name: 'MidpointWithLine',
keyword: 'midpoint-with-line',
outputType: 'Point',
parameters: [
{ argName: 'p1', type: 'Point', defaultValue: 0, variadic: false },
{ argName: 'p2', type: 'Point', defaultValue: 0, variadic: false },
],
compute({ p1, p2 }) {
const mx = (p1.x + p2.x) / 2;
const my = (p1.y + p2.y) / 2;
const midpoint = { type: 'Point', x: mx, y: my };
const line = { type: 'Line', p1, p2 };
return {
midpoint_line: line, // auxiliary
main: midpoint, // primary output
};
}
};
Module format
Save your functions in a .mjs or .js file using ES module syntax:
// my-extension.mjs
export const MyFunction = { /* ... */ };
export const AnotherFunction = { /* ... */ };
Recommended: Use a manifest.json to organize multiple files:
{
"name": "My Geometry Pack",
"version": "1.0.0",
"description": "Custom geometric functions",
"author": "Your Name",
"homepage": "https://your-name.github.io/geo-pack",
"extensions": [
{
"id": "my-function",
"name": "MyFunction",
"keyword": "my-function",
"entry": "my-function.mjs",
"parameters": [
{ "name": "p1", "type": "Point", "default": 0 },
{ "name": "p2", "type": "Point", "default": 0 }
],
"outputType": "Point"
}
]
}
Hosting your extension
Extensions must be hosted on GitHub Pages (*.github.io) with HTTPS. The recommended structure:
your-name.github.io/geo-pack/
manifest.json ← load this URL
my-function.mjs
another-function.mjs
Enable GitHub Pages in your repository settings (source: main branch, root or docs directory).
Important constraints
- No network requests:
fetch,XMLHttpRequest,WebSocket,EventSource, andimportScriptsare blocked in the worker - No DOM access: Extensions run in isolation
- Plain objects only: All values must be plain JS numbers — no class instances or internal types
- Degenerate cases: Return
{ main: { type: 'Dummy' } }for undefined results (e.g., parallel lines)
Loading your extension
Once hosted, load your extension by visiting the extensions page and pasting the URL to your manifest.json or .mjs file.
Loaded extensions are persisted to localStorage and automatically reloaded on future sessions.
Next steps
Ready to build? Check out the template repository to get started quickly, or read the full implementation guide for detailed documentation.