Getting started

Two paths through Commerce Studio in under ten minutes — call a commerce route, then render your first operator screen from a cockpit.json. No platform account needed for the first four steps.

Two things a developer does with Commerce Studio. Both are on this page.

1. What Commerce Studio is

Commerce Studio is the operator surface for catalog, customers and orders. It is generated: the studio itself contributes no pages, and every list, detail page, form and widget an operator sees comes from an installed app's cockpit.json. Behind it sit 13 independent commerce apps — products, prices, carts, orders, orderlists, customers, payments, shipping, inventories, markets, channels, pages, forms — publishing 464 routes on the public gateway.

So there are exactly two things to do here:

  • Call the commerce APIs. Nothing about Commerce Studio is in the way; the routes are ordinary gateway routes.
  • Ship a manifest that becomes a screen. You declare views in JSON and the studio renders them. No frontend code.

Path A needs a tenant and a credential. Path B needs neither — it runs entirely on your machine.

2. Path A — call the API (2 minutes)

Read the catalog:

Request
curl "https://api.revenexx.com/v1/products?limit=3" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..."

CRUD is the least interesting half of these apps. The routes worth knowing are the ones that answer a question — availability, prices, eligible payment methods, shipping rates. Ask the inventories app what is orderable:

Request
curl -X POST https://api.revenexx.com/v1/inventories/availability \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{"items":[{"sku":"ACME-4711-BLK","quantity":5}]}'

Each item comes back with on_hand, reserved, the derived available, a per-location breakdown, and orderable — whether that quantity can be promised right now. An item the app has never seen is not an error: it answers tracked: false, and the caller decides whether an untracked item sells freely.

Pagination, error shapes, retries and idempotency are identical across all 464 routes — see API usage. Every route is browsable in the API Explorer.

3. Path B — render a surface (8 minutes)

The renderer runs standalone. @revenexx/studio-commerce-devkit is published on the public npm registry and boots the same Commerce Studio module the real Cockpit runs, against mock data generated from your app's schema.json.

Terminal
mkdir my-commerce-app && cd my-commerce-app
npm init -y
npm i -D @revenexx/studio-commerce-devkit

Create the two files the preview reads. schema.json declares the entity (this is App Studio's file — see Schema reference):

schema.json
{
  "$schema": "https://schemas.revenexx.com/schema.schema.json",
  "entities": {
    "suppliers": {
      "columns": {
        "id": { "type": "uuid", "pk": true, "default": "gen_random_uuid()" },
        "code": { "type": "text", "notNull": true },
        "name": { "type": "text", "notNull": true },
        "country": { "type": "text" },
        "status": { "type": "text", "notNull": true, "default": "'active'",
          "check": "status in ('active', 'paused', 'retired')" },
        "active": { "type": "boolean", "notNull": true, "default": "true" },
        "created_at": { "type": "timestamptz", "notNull": true, "default": "now()" }
      }
    }
  }
}

The preview generates mock rows from exactly this: column types decide the shape of a value, a check with an in (…) list becomes the set of values a badge or select cycles through, and a references entry is satisfied with a real id from the referenced entity's mock rows.

A minimal manifest.json so the preview can identify the app:

manifest.json
{
  "$schema": "https://schemas.revenexx.com/manifest.schema.json",
  "name": "suppliers",
  "vendor": "acme",
  "title": "Suppliers",
  "type": "private",
  "version": "0.1.0"
}

4. Write your first view

Three declarations: one sidebar entry, one list, one create form.

cockpit.json
{
  "$schema": "https://schemas.revenexx.com/cockpit.schema.json",
  "navigation": [
    {
      "label": { "en": "Suppliers", "de": "Lieferanten" },
      "icon": "building",
      "route": "/suppliers",
      "position": 50,
      "group": "pim"
    }
  ],
  "views": [
    {
      "route": "/suppliers",
      "type": "list",
      "entity": "suppliers",
      "title": { "en": "Suppliers" },
      "columns": [
        { "name": "code", "label": "Code", "type": "text", "width": "140px", "sortable": true },
        { "name": "name", "label": { "en": "Name" }, "type": "text" },
        { "name": "active", "label": { "en": "Active" }, "type": "boolean", "width": "100px" }
      ],
      "filters": [
        { "name": "q", "label": { "en": "Search" }, "type": "text", "columns": ["code", "name"] }
      ],
      "sort": { "column": "created_at", "direction": "desc" },
      "row_action": "/suppliers/:id/edit",
      "actions": [
        { "label": { "en": "New supplier" }, "icon": "add-circle", "kind": "navigate",
          "to": "/suppliers/new", "variant": "primary" }
      ]
    },
    {
      "route": "/suppliers/new",
      "type": "form",
      "entity": "suppliers",
      "mode": "create",
      "title": { "en": "New supplier" },
      "fields": [
        { "name": "code", "label": "Code", "type": "text", "required": true },
        { "name": "name", "label": { "en": "Name" }, "type": "text", "required": true },
        { "name": "country", "label": { "en": "Country" }, "type": "text" }
      ],
      "submit_label": { "en": "Create" },
      "on_success": "/suppliers"
    }
  ]
}

Run the preview from the app directory:

Terminal
npx commerce-studio

Open http://localhost:3030/commerce. The sidebar shows Suppliers under the PIM section; the list renders with mock rows generated from the column types in schema.json; the search filter narrows them; New supplier opens the form and the created row appears in the list.

You wrote no Vue. The columns, the filter, the pagination, the loading and empty states, the form controls and the validation all come from the studio's built-in renderers. Edit cockpit.json, reload, and the manifest is re-read from disk — there is no build step in the loop.

Mock data is in-memory and regenerated when the server restarts. The media picker and import/export are stubbed standalone, because a local preview has no Storage service behind it.

5. Wire one action to your own logic

The renderer writes rows for you. When a change has a side effect a row write cannot produce, you send it to your own code instead — an api-kind action:

cockpit.json
{
  "label": { "en": "Deactivate" },
  "kind": "api",
  "method": "POST",
  "endpoint": "/suppliers/:id/deactivate",
  "variant": "danger",
  "confirm": { "title": { "en": "Deactivate this supplier?" } },
  "fields": [
    { "name": "reason", "label": { "en": "Reason" }, "type": "text", "required": true }
  ],
  "submitLabel": { "en": "Deactivate" }
}

Add it to the list view's actions, then implement the route in the app's function. Note the route pattern here uses {id} braces — the :id form belongs to cockpit.json routes, not to capability routes:

src/main.js
const { createApp } = require('@revenexx/app-sdk/router');
const { createDb } = require('./db.generated');

module.exports = (context) => {
  const app = createApp({ name: 'suppliers' });
  const db = createDb({ adapter: 'runtime', context });

  app.post('/suppliers/{id}/deactivate', async (c) => {
    const supplier = await db.suppliers.update(c.params.id, { active: false });
    return c.json({ supplier, reason: c.body?.reason ?? null });
  });

  return app.handler()(context);
};

The devkit's /functions console runs src/main.js against a simulated gateway that shares the same mock store as the views, so you can send the request and see the response status, body, timing and log output — then watch the row change in the list. It needs the app's own dependencies installed, since it requires your function.

The endpoint has to exist as a capability. endpoint resolves against your app's own routes under /v1, so /suppliers/:id/deactivate reaches the capability declared at POST /suppliers/{id}/deactivate in manifest.capabilities.json. Declare it there, or the operator clicks the button and meets a 404:

manifest.capabilities.json
[
  {
    "type": "define",
    "capability": "suppliers.deactivate",
    "version": "1.0.0",
    "route": { "method": "POST", "path": "/suppliers/{id}/deactivate" },
    "response": { "title": "SupplierDeactivated", "type": "object" }
  }
]

See Capabilities for the full contract.

6. What happens next

To put the same screens in front of a tenant's operators, the app has to be deployed and installed. revenexx deploy app registers, uploads, builds, publishes and installs in one command, and the same cockpit.json then renders in the real Cockpit — see App lifecycle.

Be aware of where the partner path currently stops.POST /v1/apps and POST /v1/apps/{functionId}/deployments explicitly defer manifest validation and registry coupling to later phases, while POST /v1/apps/{functionId}/publish requires a deployment with a registered manifest. In practice an external partner cannot today self-serve a Commerce Studio app from zero to installed without revenexx involvement.This is about publishing and installing an app — nothing else. Getting an account and a provisioned Tenant is a separate question, answered in Prerequisites; so is running code you deployed yourself. Read this warning as "the last mile needs us", not as "you cannot start".What is fully available to you right now is the devkit loop on this page: write the manifest, render it, iterate, and hand over a manifest that is already correct. Plan for that, and talk to us about the install.

7. Where to go next

Was this page helpful?