VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Get started
Introduction
Quickstart
How Voidhash works
Glossary
Versions and releases
Concepts
Teams, projects and environments
API keys
Products and perks
Offers
Subscription lifecycle
People and identity
Groups
Events
Features
Paywalls in your app
Feature flags and A/B tests
Paywall components
Push notifications
Ad attribution in your app
Client SDKs
React Native
Swift
Kotlin
Web
Your backend
Node.js
PHP
Go
Rust
Other languages
Payment sources
Forward store notifications
Developer tools
CLI
MCP server
Query analytics with VoidQL
Components
Develop a component
Editor panels
Panel handlers and state
Component definition
Component props
Publishing
Installing and capabilities

Component definition

The component.json reference.

A component's definition, component.json, describes it to the paywall designer, the Voidhash agent and publishing. #[component] builds the same definition into your module. Publishing checks that both agree on its traits, props, events, preview states, capabilities and size. The id, version, title, description, icon, category and keywords are the bundle's own.

component.json
{
  "id": "acme/plan-picker",
  "version": "1.0.0",
  "title": "Plan picker",
  "description": "Selectable plans with a purchase button.",
  "icon": { "kind": "builtin", "name": "list" },
  "category": "Pricing",
  "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", "title": "Accent", "type": { "kind": "color" }, "default": "#7c3aed" }
  ],
  "events": [
    { "name": "selected", "payload": [{ "name": "productId", "type": { "kind": "string" } }] }
  ],
  "previewStates": ["default"],
  "capabilities": ["commerce"],
  "panel": false,
  "fallback": "placeholder",
  "defaultSize": { "width": "fill", "height": "hug" }
}

A bundle you publish to your organization needs a component.json. Components in your project's .voidhash working copy use the definition #[component] builds instead.

Fields

FieldMeaning
id<namespace>/<name>. The namespace is your organization's; the name uses lowercase letters, digits and dashes.
versionA semantic version such as 1.2.0. Each published version is permanent, so bump it for every publish.
titleThe name the designer shows.
descriptionOne or two sentences about what the component does.
icon{ "kind": "builtin", "name": "list" } (such as component, list, timer, gift), or ship an icon.png.
categoryThe group the designer lists the component in, such as Pricing.
keywordsWords people search for in the designer.
implementation{ "kind": "wasm", "abi": 2 } for components built with voidhash-ui.
traitsThe designer sections and canvas behavior the component takes part in. See Traits.
children{ "kind": "none" }, { "kind": "any" }, or { "kind": "slots", "slots": [{ "name": "default" }] }.
propsThe settings the designer shows. See Props.
eventsWhat the component emits, such as a selection, that paywalls bind to actions.
previewStatesThe states the designer can preview the component in. The first is the default.
capabilitiesWhat the component can do for the person using your app. See Capabilities.
panelWhether the component ships its own editor panel. Without one, the designer shows its props.
fallbackWhat an app that cannot run the component shows instead: placeholder, hide or children.
defaultSizeThe size a new instance starts at: fill, hug or a number of points, per axis.

Traits

A trait gives every instance a designer section and its canvas behavior, as built-in layers have them. An instance keeps only the settings of the traits its definition declares.

TraitDesigner sectionWhat instances get
framePositionPosition, size, margins and how the instance sits in its parent.
layoutLayoutPadding, gap, alignment and clipping of what the component holds.
appearanceAppearanceOpacity, blend mode, visibility and corner radius.
fillFillBackgrounds: colors, gradients and image fills.
borderBorderA border.
outlineOutlineAn outline.
effectsEffectsShadows, blurs, rotation and flips.
statesStatesStates that restyle the instance when their condition holds.
interactionsInteractionsTap actions on the instance itself.
variablesVariablesVariables declared on the instance.
sharedElementShared elementA shared element id, so the instance flies between screens.

Text layers also have typography, but component instances have no text style, so declaring it gives them nothing. Expose text settings as props instead. An editor panel edits only the style of the declared traits.

Resizing

frame takes resize, the canvas's resize handles: both, width or height for one axis, or none to keep the instance's size. Add aspect: "intrinsic" to keep its proportions while resizing. A component without frame sizes itself from defaultSize and can't be resized on the canvas. In Rust, declare traits on the component:

#[component(traits(frame(resize = "width"), appearance, effects), size(width = "fill", height = "hug"))]

Slots

A component with children: { "kind": "slots" } shows its inner layers where its code renders slot() (the default slot) or slot_named("footer"). In the designer, each inner layer names its slot.

Props

Each prop has a name, a type and a default. The type sets what the prop holds and the control the designer shows; other fields label, group and localize it. See Component props for every field, type and its Rust form.

The definition must declare what the component's code declares: the same traits, props, events, preview states, capabilities, panel and default size. Publishing names every difference it finds.

Older app versions

People keep using older versions of your app after you deploy a paywall. Voidhash sends a paywall only to an app whose SDK can run every component in it. An older SDK gets the answer for a location with nothing assigned, so it shows your own fallback instead of a broken paywall. Update the SDK in your app to reach it.

The same check applies to A/B tests. An app that can't show its assigned arm is shown nothing at that location and isn't counted as exposed.

Some components run natively inside the SDK. A recent SDK without such a component still gets the paywall and shows the component's fallback in its place, so choose placeholder, hide or children with that in mind.

Next steps

  • Develop a component
  • Declare props
  • Add an editor panel
  • Publish to your organization

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousPanel handlers and stateNextComponent props

On this page

FieldsTraitsResizingSlotsPropsOlder app versionsNext steps