# Editions and surface profiles

> Compare the Community, Business and Cloud editions, switch a self-hosted Nodaro to Business, and narrow its interface with a NODARO_SURFACE_PROFILE.

Source: https://nodaro.ai/docs/self-hosting/editions-and-profiles

Nodaro has three **editions** — Community, Business and Cloud — built from the same code and selected with the `EDITION` variable. On the Business edition, a **surface profile** then narrows what the install shows, without a rebuild. It can hide parts of the interface, remove nodes and models, rename the product and restrict sign-in. This page covers both.

## The three editions

| | Community | Business | Cloud |
| --- | --- | --- | --- |
| **Self-hostable** | Yes | Yes | No, managed by Nodaro |
| **Admin panel** | No | Yes | Yes |
| **User management** | No | Yes | Yes |
| **Credit ledger** | No | No | Yes |
| **Billing** | No | No | Yes |
| **Credit prices set by admins** | No | No | Yes |
| **Surface profile** | Ignored | Yes | Yes |

- **Community** (`EDITION=community`, the default) is the free self-hosted edition. Anyone who signs up becomes a regular user.
- **Business** (`EDITION=business`) adds the admin panel and user management. It is still self-hosted and still has no billing.
- **Cloud** (`EDITION=cloud`) adds credits and billing. It powers app.nodaro.ai and is not meant for self-hosting.

The Business edition's admin panel and user management are Enterprise features. You may run them for development and testing, but using them in production requires a Nodaro Enterprise subscription. See [License](https://nodaro.ai/docs/self-hosting/license).

## Switch a self-hosted install to Business

The API reads `EDITION` at startup. The editor in the browser reads its edition from `VITE_EDITION` when the image is built. The published image is built as the Community Edition, so a Business install is built from source.

Moving from Community to Business has no database cost: the schema is the same.

### Change both values in the compose file

In `docker-compose.community.yml`, under the `nodaro` service, change the two lines that say `community`:

```yaml
nodaro:
build:
args:
VITE_EDITION: business      # was: community
environment:
EDITION: business             # was: community
```

Both lines are fixed in the compose file, so setting `EDITION` in `.env` has no effect.

### Build and start

```bash
docker compose -f docker-compose.community.yml build --no-cache nodaro
docker compose -f docker-compose.community.yml up -d
```

### Promote your first admin

See [First user and admin](https://nodaro.ai/docs/self-hosting/first-admin).

Update a source-built install with `git pull` and `build`. `docker compose pull` would download the published Community image again.

The build refuses an empty or unknown `VITE_EDITION`. Without that check, an unset value would build cleanly and fall back to `community`, and a Business image would ship without its admin panel. The compose file passes the value for you. A hand-written `docker build` needs `--build-arg VITE_EDITION=community`, `business` or `cloud`.

### What is fixed at build time and what is not

Three browser settings are **not** fixed at build time: the API address, the browser's sign-in address and the anon key. At boot, the container writes them into `/config.js` from `PUBLIC_URL`, `FRONTEND_SUPABASE_URL` and `SUPABASE_ANON_KEY`, and the browser reads that file before the app starts. So the published image serves any port or domain after a restart. The `VITE_*` build arguments are only fallbacks for unset runtime values.

Other build-time browser settings:

| Build argument | What it does | Default |
| --- | --- | --- |
| `VITE_STUDIO_URL` | The address of the Studio app, for **Open in Studio** links | `https://studio.nodaro.ai` |
| `VITE_PERSON_URL` | The address of the Person app, for the **Open Person** card on the home screen | `https://person.nodaro.ai` |

## Surface profiles

`NODARO_SURFACE_PROFILE` narrows the interface of a **Business** or **Cloud** install without a rebuild. The Community Edition ignores it and always shows the full interface.

- **Format.** Inline JSON, or `@/path/to/profile.json` to read a file. To use a file on the compose stack, mount it into the container and give its path inside the container.
- **Narrow only.** A profile can hide and remove. It never enables something that the edition disables.
- **Every field is optional.** An empty list means "keep the default". Unset, the install shows the full interface.
- **Errors.** A malformed field falls back to that field's default, with a warning in the log. A profile that does not load at all stops a Business or Cloud install from booting, with `[surface-profile] FATAL … Refusing to boot a narrowing deployment mainline-open`. That happens with an unreadable file, invalid JSON, or a value that fails validation as a whole. A narrowing install must never come up showing everything.
- **Apply.** Restart the app. The profile reaches the browser through `/config.js`.

### Example

```bash
NODARO_SURFACE_PROFILE={"nav":{"hide":["gallery"]},"brand":{"productName":"Studio"},"outputs":{"allowPublic":false},"voice":{"allowedGenders":["male"]}}
```

This hides the gallery, renames the product to Studio, keeps every output private and offers male voices only.

### Fields

| Field | What it does |
| --- | --- |
| `nav.hide` | Hides sidebar entries. Values: `gallery`, `explore`, `pricing`, `templates`, `apps`, `community`, `integrations`. |
| `dashboard.tabs` | One ordered whitelist of the home screen's sections. See [Home-screen sections](#home-screen-sections). |
| `nodes.deny`, `models.deny` | Node types and model IDs to remove everywhere: the node picker, `GET /v1/nodes`, `GET /v1/models`, the MCP tools, and at run time. A removed node fails with `node_not_available`. |
| `nodes.allow`, `models.allow` | Whitelists. When a list is not empty, only the listed node types or model IDs are offered, and `deny` still subtracts from them. See [Allow lists](#allow-lists). |
| `auth.methods`, `auth.ssoLabel` | The sign-in methods to offer: `email`, `google`, `sso`. `sso` is dropped unless `auth.ssoLabel` is set. See [Sign-in methods](#sign-in-methods). |
| `siblings.apps` | Replaces the Nodaro links in the product switcher: `[{ "label": "...", "url": "..." }]`. |
| `brand.productName` | Replaces the wordmark and the page title. Absent, the page title stays as shipped. |
| `brand.wordmark` | Short text shown beside your own logo in the sidebar header, for an install that ships its own logo files. For example, product name `Acme Studio` with wordmark `Studio`. |
| `brand.description` | Replaces the page's meta description. |
| `brand.platformLinks` | `false` hides the platform's own legal, documentation, release-note and product-promotion links. Links you set in `siblings.apps` stay. |
| `locale.default` | The language a new visitor starts in. |
| `locale.picker` | `false` hides the language picker. The default is `true`. |
| `outputs.allowPublic` | `false` makes every output private, whatever the user chooses. |
| `voice.allowedGenders` | Restricts voices to any subset of `male`, `female` and `neutral`. Empty means every gender. See [Voice genders](#voice-genders). |
| `features.hide` | Switches whole features off: `copilot` removes the Workflow Copilot, and `presentation` hides the canvas **Present** tab. |
| `catalogs` | `{ "required": true, "factoryPresets": false }` for installs that need reviewed picker content. See [Picker catalogs](#picker-catalogs). |

Logos and the favicon are not part of the profile. Replace them with a layer of static files in your Docker image.

### Home-screen sections

`dashboard.tabs` is one ordered whitelist. The full key set is `workflows`, `projects`, `apps`, `miniapps`, `templates`, `tutorials`, `statistics`, `gallery`, `studio` and `mcp`.

- **`workflows`, `projects`, `studio`** — the lists of the **Continue** tab's workspace filter, in the order of your list. If your list names none of the three, all three show, so the main list is never blank.
- **`apps`** — the Nodaro apps band on **Continue**.
- **`templates`** — the "Start from a template" row on **Explore**, and the Templates sidebar entry.
- **`tutorials`** — the **Level up** tutorials on **Explore**, and the Tutorials sidebar entry. Without `templates` and `tutorials`, the Explore tab is not shown.
- **`miniapps`** — the MiniApps sidebar entry.
- **`statistics`** — the statistics overview on the Executions page.
- **`mcp`** — the fourth "Jump back in" list: the workflows an MCP client created in its automatic `mcp` project.

List every section you want to keep. For example, `["workflows","projects","statistics","tutorials"]` keeps the workflow and project lists, the tutorials and the statistics. It drops the Studio list, the apps band, the template row and the Templates and MiniApps sidebar entries. `nav.hide` still applies on top.

### Allow lists

An allow list is the safer shape for a curated install. A new node or model then stays unavailable until you list it, instead of appearing because you did not deny it.

- Utility nodes, such as `sticky-note` and `preview`, are never removed by omission from an allow list. Only an explicit `deny` removes them.
- Admins can change node and model availability at runtime in the admin panel. A saved runtime setting replaces the profile's lists until it is reset to the factory state.
- A node that an admin disables in the admin panel stays usable by admins. A node removed by the profile is removed for admins too. But an admin who enables a profile-removed node again in the admin panel releases it to everyone, so give the admin role only to people you trust with that.
- A disabled model is disabled for admins too.
- A run is checked as the user it runs as. An app or presentation run executes as the person running it. A scheduled or webhook-triggered run executes as the workflow's owner, and an API token or a connected app acts as its own owner. When that owner is an admin, the run can use a node hidden from users, so treat the webhook URL of such a workflow with care.
- An app, a component or a template that contains a hidden node cannot be published: the request fails with `node_not_available`.
- A change of role reaches these checks within about 5 minutes.

### Sign-in methods

`auth.methods` narrows the sign-in methods on the login page, among `email`, `google` and `sso`. Keep `sso` in the list only together with `auth.ssoLabel`, the text of the SSO button.

A list that names **only** `sso` also enables a server-side rule: every signed-in account must have been created or linked through single sign-on. Any other session is refused on its first API call with `403 sso_required`, so an account registered directly on the sign-in service cannot use the install. Adding `email` to the list disables the rule. See [Single sign-on](https://nodaro.ai/docs/self-hosting/sso).

### Voice genders

`voice.allowedGenders` is enforced on the server:

- The voice list and the shared voice library show only voices of the allowed genders.
- A request with a built-in voice of another gender is refused with `voice_not_available`.
- Every default and fallback voice becomes the first voice of an allowed gender.
- The editor hides the vocal-gender tags of the other genders in the Suno nodes.

The output gender of the voice-creation nodes cannot be known in advance. To remove them, add `"nodes":{"deny":["voice-design","voice-remix"]}`.

### Picker catalogs

For installs that must offer only reviewed picker content, set `"catalogs":{"required":true,"factoryPresets":false}`.

- With `required: true`, the editor offers picker choices only after it receives the complete curated catalog from the server, and offers none if that request fails. New nodes and factory resets use only offered values.
- With `factoryPresets: false`, the bundled presets are hidden in the editor and the API. Users' own presets stay.
- The server also rejects excluded catalog choices in imported or previously saved workflows.

## Frequently asked questions

### How do I switch a self-hosted Nodaro from Community to Business?

Set EDITION to business and the VITE_EDITION build argument to business for the nodaro service in docker-compose.community.yml, then build the image from source and start it. The database needs no change.

### Why must I rebuild the image to change the edition?

The editor reads its edition when the image is built, and the published image is built as the Community Edition. The API reads EDITION at startup, but the browser app only shows the admin panel in a Business build.

### What is a surface profile?

A JSON setting, NODARO_SURFACE_PROFILE, that narrows what a Business or Cloud install shows, without a rebuild. It can hide sidebar entries and home-screen sections, remove nodes and models, rename the product, restrict sign-in methods and force outputs private.

### Does the Community Edition read NODARO_SURFACE_PROFILE?

No. The Community Edition ignores the variable and always shows the full interface. Set EDITION=business to use a surface profile.

### Can a surface profile enable a feature that the edition does not have?

No. A profile can only narrow. It never enables something that the edition disables.
