Publish a component bundle
Publishes a component bundle as a new version, creating the registry component, owned by this project, on its first version.
The bundle must pass the automated checks: the definition schema, the module and its agreement with the definition, size limits, the panel module when the definition declares one, and the conformance suite. Only people who may publish paywalls in the owner project, and organization admins, can publish versions of an existing component. Publishing the same bundle again returns the existing version.
Guide: Publishing
A project secret key: vh_sk_... for production, vh_sk_test_... for test. Keep it on your servers.
In: header
Path parameters
The project's id, proj_.... A secret key works only in the project it belongs to.
Request body
application/json
TypeScript definitions
Use the request body type in TypeScript.
The text of component.json, at most 64 KiB. Its id must be <namespace>/<name> in the organization's namespace and its version a semantic version.
1 <= lengthThe component module (module.wasm), base64 encoded.
1 <= lengthThe editor panel module (panel.wasm), base64 encoded. Required exactly when the definition declares "panel": true.
The icon the dashboard shows: a PNG, SVG or WebP image of at most 256 KiB.
At most one preview image per preview state.
Markdown documentation people read before installing, at most 128 KiB.
Markdown guidance the Voidhash agent reads before using the component, at most 32 KiB.
Examples
curl -X POST "https://api.voidhash.com/api/v1/projects/proj_tz4a98xxat96iws9zmbrgj3a/registry-components/bundles" \ -H "Authorization: Bearer vh_sk_..." \ -H "Content-Type: application/json" \ -d '{ "definitionJson": "{\\"id\\":\\"acme/plan-picker\\",\\"version\\":\\"1.3.0\\",\\"title\\":\\"Plan picker\\",\\"description\\":\\"Selectable plans.\\",\\"icon\\":{\\"kind\\":\\"builtin\\",\\"name\\":\\"component\\"},\\"category\\":\\"\\",\\"keywords\\":[\\"plans\\",\\"pricing\\"],\\"implementation\\":{\\"kind\\":\\"wasm\\",\\"abi\\":2},\\"traits\\":{\\"frame\\":{\\"resize\\":\\"width\\"},\\"appearance\\":{},\\"effects\\":{}},\\"children\\":{\\"kind\\":\\"none\\"},\\"props\\":[{\\"name\\":\\"plans\\",\\"type\\":{\\"kind\\":\\"list\\",\\"of\\":{\\"kind\\":\\"product\\"}},\\"default\\":[]},{\\"name\\":\\"accent\\",\\"type\\":{\\"kind\\":\\"color\\"},\\"default\\":\\"#7c3aed\\"}],\\"events\\":[{\\"name\\":\\"selected\\",\\"payload\\":[{\\"name\\":\\"productId\\",\\"type\\":{\\"kind\\":\\"string\\"}}]}],\\"previewStates\\":[\\"default\\"],\\"capabilities\\":[\\"commerce\\",\\"analytics\\"],\\"panel\\":false,\\"fallback\\":\\"placeholder\\",\\"defaultSize\\":{\\"width\\":\\"fill\\",\\"height\\":\\"hug\\"}}", "moduleBase64": "AGFzbQEAAAAB...", "icon": { "base64": "iVBORw0KGgoAAAANSUhEUgAA...", "contentType": "image/png" }, "thumbnails": [ { "base64": "iVBORw0KGgoAAAANSUhEUgAA...", "contentType": "image/png", "state": "default" } ], "readme": "# Plan picker\\n\\nSelectable plans for a paywall. Tapping a plan selects it and emits `selected` with its product id.\\n" }'{ "id": "cmp_pkg_ver_e5r8t2y6u0i3o7p1a4s9d2f6", "registryComponentId": "cmp_pkg_h2j7k4l9z1x6c3v8b5n0m4q7", "version": "1.3.0", "number": 5, "status": "ready", "statusMessage": null, "bundleHash": "7e2a9c4f1b8d3e6a0c5f9b2d7e4a1c8f3b6d0e9a2c5f8b1d4e7a0c3f6b9d2e5a", "contentHash": "5d9b2e7f4a1c8d3b6e0f9a2c5d8b1e4f7a0c3d6b9e2f5a8c1d4b7e0a3f6c9d2b", "byteSize": 61204, "capabilities": [ "commerce", "analytics" ], "definition": { "id": "acme/plan-picker", "version": "1.3.0", "title": "Plan picker", "description": "Selectable plans.", "icon": { "kind": "builtin", "name": "component" }, "category": "", "keywords": [ "plans", "pricing" ], "implementation": { "kind": "wasm", "abi": 2 }, "traits": { "frame": { "resize": "width" }, "appearance": {}, "effects": {} }, "children": { "kind": "none" }, "props": [ { "name": "plans", "type": { "kind": "list", "of": { "kind": "product" } }, "default": [] }, { "name": "accent", "type": { "kind": "color" }, "default": "#7c3aed" } ], "events": [ { "name": "selected", "payload": [ { "name": "productId", "type": { "kind": "string" } } ] } ], "previewStates": [ "default" ], "capabilities": [ "commerce", "analytics" ], "panel": false, "fallback": "placeholder", "defaultSize": { "width": "fill", "height": "hug" } }, "manifest": { "id": "acme/plan-picker", "version": "1.3.0", "title": "Plan picker", "description": "Selectable plans.", "icon": { "kind": "builtin", "name": "component" }, "category": "", "keywords": [ "plans", "pricing" ], "implementation": { "kind": "wasm", "abi": 2 }, "traits": { "frame": { "resize": "width" }, "appearance": {}, "effects": {} }, "children": { "kind": "none" }, "props": [ { "name": "plans", "type": { "kind": "list", "of": { "kind": "product" } }, "default": [] }, { "name": "accent", "type": { "kind": "color" }, "default": "#7c3aed" } ], "events": [ { "name": "selected", "payload": [ { "name": "productId", "type": { "kind": "string" } } ] } ], "previewStates": [ "default" ], "capabilities": [ "commerce", "analytics" ], "panel": false, "fallback": "placeholder", "defaultSize": { "width": "fill", "height": "hug" } }, "hasPanel": false, "hasIcon": true, "hasReadme": true, "hasAgentGuide": false, "thumbnailStates": [ "default" ], "warnings": [], "conformance": { "hasPassed": true, "checks": [ { "name": "module", "status": "pass", "errors": [], "warnings": [] }, { "name": "manifest", "status": "pass", "errors": [], "warnings": [] }, { "name": "imports", "status": "pass", "errors": [], "warnings": [] }, { "name": "exports", "status": "pass", "errors": [], "warnings": [] }, { "name": "lifecycle", "status": "pass", "errors": [], "warnings": [] }, { "name": "values", "status": "pass", "errors": [], "warnings": [] }, { "name": "tree", "status": "pass", "errors": [], "warnings": [] }, { "name": "determinism", "status": "pass", "errors": [], "warnings": [] }, { "name": "events", "status": "pass", "errors": [], "warnings": [] }, { "name": "timers", "status": "pass", "errors": [], "warnings": [] }, { "name": "layout", "status": "pass", "errors": [], "warnings": [] }, { "name": "actions", "status": "pass", "errors": [], "warnings": [] }, { "name": "frames", "status": "pass", "errors": [], "warnings": [] }, { "name": "memory", "status": "pass", "errors": [], "warnings": [] }, { "name": "golden", "status": "skip", "errors": [], "warnings": [] } ] }, "installCount": 0, "publishedPaywallCount": 0, "createdBy": "user_k3m8q1z6v9c2x5b7n4w0e8r1", "publishedAt": "2026-03-20T14:30:00.000Z"}{ "error": { "type": "invalid_request", "code": "conformance_failed", "message": "acme/plan-picker did not pass the component conformance checks. determinism: sample: equal props rendered different trees" }}{ "error": { "type": "unauthenticated", "code": "invalid_api_key", "message": "This secret key is not valid." }}{ "error": { "type": "permission_denied", "code": "permission_denied", "message": "Insufficient permissions." }}{ "error": { "type": "not_found", "code": "project_not_found", "message": "There is no project proj_tz4a98xxat96iws9zmbrgj3a, or this credential cannot use it.", "param": "projectId" }}{ "error": { "type": "conflict", "code": "publisher_required", "message": "The organization has no component namespace yet. An organization admin must claim a namespace (for example `acme`) before components can be published." }}{ "error": { "type": "internal", "code": "internal_error", "message": "Something went wrong on our side. Try again, and contact support with the Request-Id header if it keeps failing." }}{ "error": { "type": "unavailable", "code": "component_checks_unavailable", "message": "The conformance checks could not run, so nothing was published. Try again in a moment." }}Response body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
Errors
| Status | type | code | When |
|---|---|---|---|
| 400 | invalid_request | conformance_failed | When the conformance suite fails. |
| 400 | invalid_request | invalid_bundle | When another check fails. |
| 409 | conflict | publisher_required | Before a namespace is claimed. |
| 409 | conflict | version_already_published | When the version exists with other contents. |
| 503 | unavailable | component_checks_unavailable | When the conformance checks could not run. |
It can also fail with the errors every operation shares, such as unauthenticated; see Errors.
More in Component registry
- Retrieve the registry publisher
GET - Claim a registry namespace
PUT - List registry components
GET - Publish a project component
POST - Retrieve a registry component
GET - Update a registry component
PATCH - List a registry component's versions
GET - Retrieve a registry component version
GET - Update a registry component version
PATCH - Retrieve a version's file
GET - Review an install
GET - List installed components
GET - Install a registry component
POST - Retrieve an install
GET - Update an install
PATCH - Uninstall a registry component
DELETE
Something wrong or missing on this page? Tell us by email or on Discord.