Extension Config and Manifest
@monospace/cli 0.2 and @monospace/extension-kit 0.3. Overview
An extension is a package that adds capabilities to your Monospace instance (a running Monospace deployment). It has two JSON files. You write the extension config (extension.config.json) in your project. The CLI builds it into the manifest (manifest.json), which is the file your instance reads.
extension.config.json | manifest.json | |
|---|---|---|
| Written by | You | monospace extension build |
| Lives in | Your project root | The built extension directory |
manifestVersion | Not allowed | Required, always 1 |
$schema | Optional, for editor validation | Not allowed; the instance rejects it |
Both files share every other field and the same JSON Schema rules. Unknown fields are rejected at every level of both files. Omit optional fields instead of setting them to null.
The instance checks more than the schema when it loads a manifest. Some values pass the schema and monospace extension build but reject the extension at load time. Each entry type's reference lists its own load-time checks.
Write the Extension Config
This extension config belongs to a data connector extension for the Art Institute of Chicago API and declares its one entry. It is the config the Data Connector Quickstart builds. Every field inside the entry except type is defined by the data connector entry type:
{
"$schema": "./node_modules/@monospace/cli/schemas/extension.config.schema.json",
"entries": [
{
"engine": {
"entrypoint": "./src/data-connectors/artic/index.ts",
"permissions": [
{ "permission": "net:api.artic.edu", "reason": "Read artworks and artists from the Art Institute of Chicago API" }
]
},
"icon": "lucide:landmark",
"id": "example/artic-connector",
"name": "Art Institute of Chicago",
"type": "dataConnector",
"version": "0.1.0"
}
],
"icon": "lucide:landmark",
"id": "example/artic",
"name": "Art Institute of Chicago",
"version": "0.1.0"
}
The $schema path resolves to the JSON Schema that ships with @monospace/cli, so your editor validates the file as you type. monospace extension create writes this line for you.
Extension Fields
These fields sit at the root of both files and apply to every extension, whatever its entries are.
| Field | Type | Required | Rules |
|---|---|---|---|
id | string | Yes | The extension's identifier, in id format |
version | string | Yes | The extension's release, in version format |
name | string | Yes | Display name. Must contain at least one non-whitespace character |
description | string | No | Display description |
icon | string | No | Icon name. See Icons |
iconForeground | string | No | Glyph color of a one-color icon. See Icons |
iconBackground | string | No | Tile color behind the icon. See Icons |
entries | array | Yes | At least one entry. See Entry Types |
$schema | string | No | Extension config only. Editor schema location; the build removes it |
manifestVersion | number | Manifest only | Always 1. Versions the manifest format, not your extension |
Entry Types
An entry is one capability your extension contributes. Its type decides which other fields the entry has and what its code must do. The instance and the build reject any type not listed here.
type | Entry | Fields and code contract |
|---|---|---|
dataConnector | A data connector: code that exposes an external data source as collections | Data Connector API |
dataConnector is the only entry type today. Each entry type's reference defines the entry's other fields, how the instance runs it, and what it may access.
Id Format
Extension and entry ids use the same format, namespace/name:
- Exactly one
/separating two segments. - Each segment uses lowercase ASCII letters and digits, with single hyphens between groups.
- No leading, trailing or repeated hyphens, no uppercase, no underscores, no whitespace.
| Id | Valid | Reason |
|---|---|---|
acme/art-institute | Yes | |
123/456 | Yes | |
art-institute | No | No namespace |
Acme/art-institute | No | Uppercase |
acme/art--institute | No | Repeated hyphen |
acme/art_institute | No | Underscore |
acme/art/institute | No | More than one / |
Entry ids are global across the instance. When two installed entries share an id, the instance loads the first one and skips the other with a warning. Two extensions with the same extension id follow the same rule. The instance scans extension directories in sorted order and skips the later one.
Version Format
version takes an exact SemVer 2.0.0 version. Prerelease and build metadata are allowed.
| Version | Valid | Reason |
|---|---|---|
0.1.0 | Yes | |
2.3.0-beta.1+build.7 | Yes | |
1.0.0+001 | Yes | |
^1.0.0, latest | No | Ranges and tags are rejected |
1.0 | No | Incomplete |
01.0.0 | No | Leading zero |
1.0.0-01 | No | Leading zero in a numeric prerelease |
The instance doesn't compare versions. Overwriting an installed extension with a lower version downgrades it without complaint.
Icons
icon, iconForeground and iconBackground are free-form strings in both files. The extension and each entry set their own. Studio renders them as follows:
| Field | Rendered as |
|---|---|
icon | An Iconify name in prefix:name form (e.g., lucide:landmark) |
iconForeground | The color of a one-color icon's glyph (e.g., #ffffff). A full-color logo keeps its own colors and ignores it. Without it, the glyph is white on a filled tile and a muted neutral otherwise |
iconBackground | The color of the tile behind the icon (e.g., #8b5cf6). Without it, the tile uses the theme's neutral surface |
Studio ignores a color value it can't parse. Both files reject iconColor as an unknown field.
Lucide icons (lucide:*) ship with Studio. Studio's extension list shows lucide:blocks for an extension without icon.
Capabilities
A data connector entry declares its query capabilities in engine.capabilities.query. Each value is a promise about what the connector's answers to writes carry, on every collection of every data source that uses the entry:
| Value | The connector promises |
|---|---|
insertReturning | The answer to create carries the created items, each with every field in the request's select |
updateReturning | The answers to updateMany and updateOne carry the updated items that way |
deleteReturning | The answers to deleteMany and deleteOne carry the deleted items that way |
The Stripe connector from Build a Stripe Data Connector answers every write with the items it wrote. Creates and updates answer the objects Stripe returns, and deletes answer the items read before deleting. Its entry declares all three:
{
"capabilities": { "query": ["insertReturning", "updateReturning", "deleteReturning"] },
"entrypoint": "./src/data-connectors/stripe/index.ts",
"permissions": [
{ "permission": "net:api.stripe.com", "reason": "Read and write customers, products and prices through the Stripe API" }
]
}
| Rule | Detail |
|---|---|
| Shape | capabilities is an object whose only key is query. query is an array of unique values from the table above, in any order |
| Absent | An absent capabilities, "capabilities": {} and "query": [] all declare no capability. monospace extension create scaffolds an entry without capabilities |
| Read-only connectors | Declare nothing. The capabilities only change how Engine writes |
| In the manifest | The build copies engine.capabilities into manifest.json unchanged |
Without a capability, Engine gets the written items through extra calls of its own, and the collection's declaration must carry them. Declare Query Capabilities lists those calls and the rules they add.
Engine reads the capabilities when it builds a workspace. A restart loads a build with new capabilities directly. With automatic reloading on, loading a build whose capabilities differ deactivates the data sources that use the entry until the instance restarts or the workspace is rebuilt. See Update Data Sources After an Extension Update.
monospace extension build rejects any other value or key before it writes anything. A value outside the three, including the same name in PascalCase (InsertReturning), fails with:
error: extension.config.json does not match the extension contract:
/entries/0/engine/capabilities/query/0 must be one of "insertReturning", "updateReturning", "deleteReturning"
Nothing was changed.
fix: Correct each field listed above. Unknown fields are rejected.
An unknown key next to query fails with /entries/0/engine/capabilities has an unknown field "schema", and a value listed twice with /entries/0/engine/capabilities/query must NOT have duplicate items (items ## 0 and 1 are identical).
Build the Manifest
monospace extension build validates the extension config, bundles each entry, and writes manifest.json. See CLI for flags and errors.
The build changes three things and copies everything else unchanged, including an entry's engine.capabilities:
- Removes
$schema. - Adds
"manifestVersion": 1after the other fields. - Points each entry at its built code. For a data connector entry,
engine.entrypointbecomes the built file, named<entry id>.js.
The extension config above builds into this manifest, with the config's key order:
{
"entries": [
{
"engine": {
"entrypoint": "example/artic-connector.js",
"permissions": [
{
"permission": "net:api.artic.edu",
"reason": "Read artworks and artists from the Art Institute of Chicago API"
}
]
},
"icon": "lucide:landmark",
"id": "example/artic-connector",
"name": "Art Institute of Chicago",
"type": "dataConnector",
"version": "0.1.0"
}
],
"icon": "lucide:landmark",
"id": "example/artic",
"name": "Art Institute of Chicago",
"version": "0.1.0",
"manifestVersion": 1
}
The build also rejects what the schema alone can't catch:
| Problem | Result |
|---|---|
| A key repeated in the same JSON object | The build fails and names the key and line |
Two entries with the same id | The build fails |
| Comments or trailing commas | The build fails; the file must be plain JSON |
Install Directory Layout
Your instance loads extensions from the directory set by MONOSPACE_EXTENSIONS__INSTALL_LOCATION. It defaults to ./extensions, relative to the instance's working directory. See Install and Manage Extensions for how to place extensions there.
A running instance picks up changed files on its next start. With MONOSPACE_EXTENSIONS__AUTO_RELOAD on, it reloads them automatically once the files stop changing. Auto-reload is off by default and needs the install directory to exist when the instance starts. See Reload Extensions Automatically.
Each extension is one top-level directory with manifest.json at its root:
extensions/
└── example__artic/
├── manifest.json
└── example/
└── artic-connector.js
The instance reads every top-level directory and ignores the directory name. It skips top-level symlinks, so place a real directory there, not a link to your build output. monospace extension build --install-to names the directory <namespace>__<name> after the extension id.
The instance skips an extension whose manifest fails validation, logs a warning, and loads the others. On a reload, an installed extension whose manifest has become invalid is treated as removed. For what that means for data connectors, see Before You Remove an Extension. Each entry type adds its own checks on the files an entry points to. For data connectors, see Entrypoint Rules.
See Also
- CLI:
extension createandextension build - Install and Manage Extensions: place, reload and remove extensions
- Data Connector API: the fields and code contract of a
dataConnectorentry