Picker catalogs
Build Nodaro's pickers in your own app with @nodaro/prompts, turn selections into prompt clauses, translate labels, show pictures or read catalogs over the API.
A picker catalog is the data behind one of Nodaro's pickers, the Creative Controls nodes such as Mood, Lens, Framing and Person. It holds the picker's options, the prompt text each option adds, its categories and the keys of its translations. The catalogs ship as plain data in npm packages, so you can build the same pickers in your own app, in your own style, and assemble exactly the prompts Nodaro assembles.
A picker never calls a model. It adds a descriptive clause to the prompt of the node it feeds; see Creative controls.
Import the package or call the API
| Your app | Use |
|---|---|
| Can bundle an npm package | Import the catalogs from @nodaro/prompts. They are typed, work offline and need no API call. |
| Cannot bundle a package, such as an AI agent over MCP | Read the same data from the discovery surface: the REST endpoint GET /v1/picker-catalogs, client.pickerCatalogs in the SDK, nodaro pickers in the CLI, or the MCP tool get_picker_catalog. |
Prefer the package when you can: it saves a round trip for data that changes only with a release.
npm install @nodaro/prompts @nodaro/shared @nodaro/sdk@nodaro/prompts holds the catalogs and the prompt functions, @nodaro/shared holds the translations, and @nodaro/sdk runs the generation.
The registry
import { PICKER_CATALOGS, getPickerCatalog, listPickerCatalogs } from "@nodaro/prompts"| Export | What it returns |
|---|---|
PICKER_CATALOGS | Every picker catalog: 28 single-dimension and 11 multi-dimension pickers. |
getPickerCatalog(nodeTypeOrCatalogId) | One catalog, by node type (such as "mood") or by catalog id. |
listPickerCatalogs() | All of them. |
The catalog types:
interface PickerOption {
id: string
label: string // English display text; translate it with the catalogId (see below)
description?: string
category?: string // group id, matching categoryOrder and categoryLabels
promptHint: string // the clause this option adds ("" for a no-op option such as "auto")
term: string // the short professional term Compact mode adds ("" for a no-op option)
icon?: string // reserved; pictures are served separately (see "Pictures")
}
interface PickerDimension { // multi-dimension pickers, and the secondary fields of a single-dimension picker
field: string // for example "shotSize"
label: string
options: readonly PickerOption[]
}
interface PickerCatalog {
nodeType: string // "mood", "framing", ...
label: string
catalogId: string // the key of the translations
kind: "single" | "multi"
valueField?: string // single: the field a selection writes
defaultValue?: string
categoryOrder?: readonly string[]
categoryLabels?: Readonly<Record<string, string>>
options?: readonly PickerOption[] // single-dimension pickers
fields?: readonly string[] // multi-dimension pickers: the dimension fields
dimensions?: readonly PickerDimension[] // multi-dimension pickers, and secondary fields
}Render label and send term or promptHint to the model. Never derive one from another.
Build a single-dimension picker
A single-dimension picker, such as Mood, is one choice from a flat list, optionally grouped. options carries everything you need to draw the grid.
import { getPickerCatalog, getParameterPromptHint } from "@nodaro/prompts"
import { createClient, StaticTokenAuth } from "@nodaro/sdk"
const client = createClient({
baseUrl: "https://app.nodaro.ai",
auth: new StaticTokenAuth(process.env.NODARO_ACCESS_TOKEN!),
})
const mood = getPickerCatalog("mood")! // { nodeType: "mood", valueField: "mood", options, categoryOrder, categoryLabels }
// 1. Render your own tile grid, grouped by category
function MoodPicker({ value, onChange }: { value?: string; onChange: (id: string) => void }) {
return (mood.categoryOrder ?? [undefined]).map((cat) => (
<section key={cat ?? "all"}>
{cat && <h4>{mood.categoryLabels?.[cat]}</h4>}
{mood.options!
.filter((o) => !cat || o.category === cat)
.map((o) => (
<button key={o.id} aria-pressed={value === o.id} title={o.description} onClick={() => onChange(o.id)}>
{o.label}
</button>
))}
</section>
))
}
// 2. Selection, then prompt clause, then generation
const selected = "serene"
const clause = getParameterPromptHint({ type: "mood", data: { mood: selected } })
// or: mood.options!.find((o) => o.id === selected)!.promptHint
const prompt = ["a portrait of a woman", clause].filter(Boolean).join(", ")
const result = await client.nodes.runAndWait("generate-image", { prompt })
console.log(result.imageUrl)Secondary parameters of Transition, Character FX and Character Motion
Three single-dimension pickers carry extra fields beside the main choice. Transition and Character FX each have position, duration and intensity. Character Motion has position and pace.
Those fields are catalogs too, with the same rows as every other option, each list led by a no-op auto. The catalog exposes them as dimensions, in addition to options. Render the main picker from options and the dropdowns from dimensions; a client that sends only ids never writes the timing clause itself.
const fx = getPickerCatalog("character-fx")!
fx.options // the effects of the main picker
fx.dimensions // [{ field: "position", ... }, { field: "duration", ... }, { field: "intensity", ... }]
// Each dimension's rows are also exported directly:
// TRANSITION_POSITIONS / TRANSITION_DURATIONS / TRANSITION_INTENSITIES
// CHARACTER_FX_POSITIONS / CHARACTER_FX_DURATIONS / CHARACTER_FX_INTENSITIES
// CHARACTER_MOTION_POSITIONS / CHARACTER_MOTION_PACESTransition and Character FX share the same ids: start, middle, end and full; instant, short, medium and long; subtle, natural, dynamic and crazy. They do not share the wording: a transition occurs and spans the clip, while an effect appears and persists. Always read the rows from the node's own catalog. Character Motion shares the position ids and adds its own pace ids, slow-motion, slow, natural, fast and explosive, with its own wording.
Build a multi-dimension picker
A multi-dimension picker sets several independent fields at once. Framing, for example, sets the shot size, the angle, the coverage, the composition and the vantage. Its catalog has one { field, label, options } entry per field in dimensions.
import { getPickerCatalog, getParameterPromptHint } from "@nodaro/prompts"
const framing = getPickerCatalog("framing")!
// framing.dimensions = [
// { field: "shotSize", label: "Shot Size", options: [{ id: "close-up", ... }, ...] },
// { field: "angle", label: "Angle", options: [{ id: "low-angle", ... }, ...] },
// { field: "coverage", label: "Coverage", options: [...] },
// { field: "composition", label: "Composition", options: [...] },
// { field: "vantage", label: "Vantage", options: [...] },
// ]
function FramingPicker({ value, onChange }: {
value: Record<string, string>
onChange: (v: Record<string, string>) => void
}) {
return framing.dimensions!.map((dim) => (
<section key={dim.field}>
<h4>{dim.label}</h4>
{dim.options.map((o) => (
<button
key={o.id}
aria-pressed={value[dim.field] === o.id}
title={o.description}
onClick={() => onChange({ ...value, [dim.field]: o.id })}
>
{o.label}
</button>
))}
</section>
))
}
// Selection to clause: getParameterPromptHint composes every field that is set
const value = { shotSize: "close-up", angle: "low-angle", composition: "rule-of-thirds" }
const clause = getParameterPromptHint({ type: "framing", data: value })Turn selections into a prompt
getParameterPromptHint({ type, data }) turns any selection, single or multi, into its clause. It is the same function Nodaro runs on its servers, so your prompts match the editor's. Per-catalog builders exist too, such as buildFramingHints(value) for a multi-dimension picker and getMoodPromptHint(id) for one option.
// Several pickers, one prompt, one generation
const clauses = [
getParameterPromptHint({ type: "mood", data: { mood: "serene" } }),
getParameterPromptHint({ type: "lens", data: { lens: "portrait-85mm" } }),
getParameterPromptHint({ type: "framing", data: { shotSize: "close-up", angle: "eye-level" } }),
]
const prompt = ["a portrait of a woman in a garden", ...clauses].filter(Boolean).join(", ")
const result = await client.nodes.runAndWait("generate-image", { prompt, provider: "nano-banana-2" })Add hintMode: "compact" to a picker's data to get its short term instead of the full clause. This is the Compact mode of the editor's Prompt hint toggle; see Full or Compact.
| Function | What it does |
|---|---|
getParameterPromptHint({ type, data }) | Turns any selection into its composed clause. |
build<Name>Hints(value) | Builds the clause of one catalog, such as buildFramingHints. |
get<Name>PromptHint(id) | Returns the clause of one option, such as getMoodPromptHint. |
PICKER_CATALOGS, getPickerCatalog, listPickerCatalogs | The registry. |
Translate the labels
Catalog labels are English. Translations for 12 locales ship in @nodaro/shared, keyed by each catalog's catalogId: en, es, fr, de, pt-BR, ru, hi, ja, ko, zh-CN, he and ar. The promptHint and term values stay English, because the models read them.
English needs no setup. For the other locales, register the translation bundles once at startup, then resolve labels synchronously, with English as the fallback:
import { registerSidecarLoaders, ensureLocaleCatalogLoaded, resolveLabel } from "@nodaro/shared"
// 1. Once at startup, in a Vite app: wire the lazily loaded locale bundles
registerSidecarLoaders(import.meta.glob("/node_modules/@nodaro/shared/src/i18n/*.*.ts"))
// 2. Before rendering a locale, load that catalog's bundle
await ensureLocaleCatalogLoaded(mood.catalogId, "fr")
// 3. Resolve a label, synchronously; English when a translation is missing or not loaded yet
const label = resolveLabel(mood.catalogId, option.id, option.label, "fr")The bundles load on demand, so registerSidecarLoaders takes a map of loaders, such as the one Vite's import.meta.glob returns. Backends and tests can skip translation and use the English label.
Show pictures
Your app can show the same pictures the editor shows on these pickers:
- Photos on Person, Styling, Held Prop, Material and Animal.
- Art on the music and voice pickers: Music Genre, Music Mood, Instrumentation, Voice Character and Voice Delivery.
- Preview stills on the look pickers, on Nodaro Cloud only. These are Style, Color / Look, Era / Period, Lens, Mood, Atmosphere, Composition Effect, Camera / Film, Framing, Lighting and Camera Motion.
Over the API, every option with a picture carries an absolute imageUrl on the install you asked, and an option without one has none. Person and Styling also return sections: their topics in order, each with a round picture. The URL rules follow in Read the catalogs over the API.
In the library, @nodaro/prompts builds the same URLs from the same data:
pickerOptionImageUrl(catalog, field, id, { baseUrl })for an option;pickerSectionImageUrl(topicLabel, { baseUrl })for a Person or Styling topic.
baseUrl is the Nodaro install that serves the files: the pictures belong to the install, not to the npm package. Pass lookPreviews: true only against Nodaro Cloud. For every other picker, draw your own visuals from label, description and category.
Read the catalogs over the API
Both endpoints are public, need no token, and are cached for 5 minutes.
| Method | Path | What it returns |
|---|---|---|
GET | /v1/picker-catalogs | A directory of every picker: nodeType, label, catalogId, kind, valueField or fields, optionCount, and imageCount, the number of options with a picture. |
GET | /v1/picker-catalogs/:nodeType | One picker's catalog. An unknown type answers 404 not_found. |
GET /v1/picker-catalogs/:nodeType takes three query parameters. A bad value answers 400 validation_error.
| Parameter | Values | What it does |
|---|---|---|
detail | compact (default) or full | compact returns id, label, category, term, icon and imageUrl. full adds each option's description and promptHint. |
category | A category id | Filters a single-dimension picker to one category. |
field | A field name | Returns one dimension of a multi-dimension picker, or one secondary field of Transition, Character FX or Character Motion. |
curl -s https://app.nodaro.ai/v1/picker-catalogs/mood | jq '.data.options[0]'{ "id": "happy", "label": "Happy", "category": "positive", "term": "happy expression",
"imageUrl": "https://cdn.nodaro.ai/cdn-cgi/image/width=480,format=auto,quality=80/images/710df65a-3c1e-485b-b8b7-29f7b3baf479.png" }The picture URLs
| Pickers | Picture |
|---|---|
person, styling, held-prop, material, animal | A photo, WebP, up to 480 px wide. |
music-genre, music-mood, instrumentation, voice-character, voice-delivery | A 3D emoji picture (WebP, 128 px) or a flag (WebP, 120 px wide). |
Look pickers, such as style, color-look, lens, framing, lighting, mood, camera-format and camera-motion | On Nodaro Cloud, a 480 px still of the rendered preview from the Nodaro CDN; for camera-motion, a frame of the clip. A self-hosted install returns none. |
- Host. An install serves its own pictures under
/picker-art/, soimageUrluses its public address:https://app.nodaro.aion Nodaro Cloud, or thePUBLIC_URLof a self-hosted install. Use each URL as given; never build one from an option id. - Caching. File names carry a content hash, so a changed picture gets a new URL. The files are served with
Cache-Control: public, max-age=31536000, immutableandAccess-Control-Allow-Origin: *, so any origin can load them in an<img>, withfetchor on a canvas. - Topics.
personandstylingalso returnsections, each with alabel, thefieldsit groups and an optional roundimageUrl.
curl -s https://app.nodaro.ai/v1/picker-catalogs/person | jq '.data.sections[0], .data.dimensions[0].options[0]'{ "label": "Identity", "fields": ["type", "age", "ethnicity", "regionalAesthetic"],
"imageUrl": "https://app.nodaro.ai/picker-art/character/sections/identity.2d5ec1a4.webp" }
{ "id": "man", "label": "Man", "term": "man",
"imageUrl": "https://app.nodaro.ai/picker-art/character/person/man.441363db.webp" }A deployment's curated catalogs
GET /v1/catalogs returns every catalog in one call, as a single flat shape. A deployment can curate its catalogs with vendored packs that replace, extend or deny options, and this endpoint reflects that curation. When the deployment registered packs, the response carries curated: true and the catalogs in data. With none, it carries curated: false, and you read the bundled catalogs per picker from /v1/picker-catalogs/:nodeType. The SDK method is client.catalogs.list().
Fill pickers from a description
POST /v1/text-to-picker chooses picker values from a free-text description of a scene or a shot. This is the AI Fill feature. It needs a token and is billed as an LLM call. It returns pickerJson, keyed by picker type, then dimension, then the chosen ids, and gaps, the described details no catalog option represents. The SDK method is client.pickerCatalogs.analyzeText(), and the CLI command is nodaro pickers analyze.
Character Motion metadata
The options of the Character Motion catalog can carry motion metadata, at both detail levels. The metadata describes the move:
- Its requirements, its start and end poses, and the visibility and hands after the move.
- Whether it needs free hands, its kind, a fixed pace and its counterpart.
- Search aliases, and whether the id is retired and which id replaces it.
A missing field means unknown.
Keep retired ids working when you load saved workflows, and hide them from new choices. The type is CharacterMotionMetadata, exported by @nodaro/prompts.
Frequently asked questions
Related
Creative controls
Mood
TypeScript SDK
MCP tools reference
Commands
Last updated on
External sign-in (SSO)
Let a trusted identity provider sign users in to a Nodaro install with a signed JWT assertion or the OIDC and SAML options, and control how accounts are linked.
Embeds
The two ways to put Nodaro inside your own product, a MiniApp that runs a published workflow and the stateless 3D scene viewport, and when to use each one.