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/picker-catalogs"| 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)
videoPromptHint?: string // Style only: the clause a video prompt adds instead of promptHint (in the package only; the API does not return it)
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/picker-catalogs"
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({ id: "mood-1", 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.
The cut flags of Transition. In the @nodaro/prompts catalog (getPickerCatalog("transition")), a transition that is a cut carries instant: true. On a cut, duration and intensity add no clause, and the position full adds nothing. The two cuts that can blend, seamless-match and jump-match, also carry blendable: true. The short row of the duration dimension carries blendsCut: true: it is the step that turns a blendable cut into a blended cut. So offer a cut its position without full, and no intensity. Offer a blendable cut its duration as a blend: auto, which keeps the hard cut, plus the rows with blendsCut. For several picks, isInstantTransition and isBlendableTransition, both exported by @nodaro/prompts, are true only when every pick is. The flags exist only in the package's catalog. GET /v1/picker-catalogs/transition, client.pickerCatalogs.get("transition") and the MCP get_picker_catalog tool do not carry them.
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/picker-catalogs"
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({ id: "framing-1", type: "framing", data: value })Turn selections into a prompt
getParameterPromptHint({ id, 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({ id: "mood-1", type: "mood", data: { mood: "serene" } }),
getParameterPromptHint({ id: "lens-1", type: "lens", data: { lens: "portrait-85mm" } }),
getParameterPromptHint({ id: "framing-1", 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" })Video prompts and the Style wording
Every style has two wordings. Still images read its promptHint. Video prompts read a video wording, videoPromptHint, written for moving shots: it names the medium first, then its techniques. Nodaro's video nodes and video routes add the video wording. To match them, pass the surface as the third argument when the clause goes into a video prompt. The Style picker then returns its video wording. Every other picker returns the same clause for both surfaces.
import { getParameterPromptHint } from "@nodaro/prompts/picker-catalogs"
import { getStyleVideoPromptHint } from "@nodaro/prompts/pickers"
const style = { id: "style-1", type: "style", data: { style: "anime" } }
getParameterPromptHint(style) // still image: the option's promptHint
getParameterPromptHint(style, undefined, { surface: "video" }) // video: the style's video wording
getStyleVideoPromptHint("anime") // the same video wording, by idWithout a third argument, or with surface: "image", the function returns the image wording. See Styles for images and for video.
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({ id, type, data }) | Turns any selection into its composed clause. Pass { surface: "video" } as the third argument for a video prompt. |
getStyleVideoPromptHint(id) | Returns the video wording of one style. A style with no video wording returns its promptHint, and an unknown id returns "". |
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. On Nodaro Cloud, all five show a photographic render of each option instead of their 3D emoji and flags. The language and accent renders carry their flags.
- 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. On the REST routes, and so in the SDK, a render from the Nodaro CDN also carries imageSrcSet, from 240 to 1,920 px wide, so a large tile stays sharp. The MCP tool leaves it out. 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.
A deployment can turn the pictures off with catalogs.images in its surface profile. Its replies then carry no imageUrl or imageSrcSet, every imageCount is 0, and its editor shows none of these pictures. See Editions and surface profiles.
A deployment can also show pictures of its own with catalogs.images: "own". Its replies and its editor then show the pictures it registered, on its own media host. On the REST routes, an option it gave an enlarged picture also carries imageLargeUrl.
In the library, @nodaro/prompts builds the same URLs from the same data:
pickerOptionImageUrl(catalog, field, id, { baseUrl })for an option, orpickerOptionImage(...)for{ url, srcSet? }. PasssrcSet: truefor the widths;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 (0 on a deployment that turned pictures off). |
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. |
For a style option, promptHint is the image wording. A video prompt adds the style's video wording, which no catalog endpoint returns.
Image and video styles. A style option offered for one kind of output only carries media, at both detail levels. The five video-only styles (felt-puppet, knitted-yarn, toy-figure-3d, painterly-3d and painted-clay) carry ["video"]. An option without media is offered for images and video. An image style list leaves out an option whose media lacks "image". A style sent to an image route anyway is still accepted, and the image receives its image wording.
Styles for restyling a clip. A style option that does not work well as a restyle of filmed footage carries restyle: false, at both detail levels: today fantasy, surrealism and concept-art. A list of styles for restyling a clip leaves it out. Video generation still offers it, and a restyle that names it is still accepted.
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/picker-renders/710df65a-3c1e-485b-b8b7-29f7b3baf479.png",
"imageSrcSet": "https://cdn.nodaro.ai/cdn-cgi/image/width=240,format=auto,quality=80/picker-renders/710df65a-3c1e-485b-b8b7-29f7b3baf479.png 240w, https://cdn.nodaro.ai/cdn-cgi/image/width=480,format=auto,quality=80/picker-renders/710df65a-3c1e-485b-b8b7-29f7b3baf479.png 480w, https://cdn.nodaro.ai/cdn-cgi/image/width=960,format=auto,quality=80/picker-renders/710df65a-3c1e-485b-b8b7-29f7b3baf479.png 960w, https://cdn.nodaro.ai/cdn-cgi/image/width=1920,format=auto,quality=80/picker-renders/710df65a-3c1e-485b-b8b7-29f7b3baf479.png 1920w" }<img src="{imageUrl}" srcset="{imageSrcSet}" sizes="160px" alt="Happy">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 | On Nodaro Cloud, a square photographic render from the Nodaro CDN: a 480 px still, plus imageSrcSet up to its full size. A language or accent render carries its flag. On a self-hosted install, 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, plus imageSrcSet; 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. - Sizes (
imageSrcSet). On the REST routes, a picture from the Nodaro CDN also carriesimageSrcSet: the same picture at 240, 480, 960 and 1,920 px wide, as an HTMLsrcsetvalue. The CDN never enlarges a picture, so the largest copy is at most the size of the source. The MCP tools leaveimageSrcSetout. Give it to<img srcset>with asizesthat says how wide the tile is. The browser then fetches the smallest copy that stays sharp on that screen.imageUrlis the 480 px copy. Self-hosted pictures have one size and noimageSrcSet. - Topics.
personandstylingalso returnsections, each with alabel, thefieldsit groups and an optional roundimageUrl. - A deployment without pictures. On a deployment that turned the pictures off (
catalogs.images), the replies carry no picture at all. No option hasimageUrlorimageSrcSet, no topic hasimageUrl, and every picker in the directory has animageCountof0. The same is true forGET /v1/catalogsand the MCPget_picker_catalogtool. Everything else is unchanged, so a client that draws a label for an option with noimageUrlneeds no change.
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.d6bed999.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. A run makes one LLM call per family of pickers and is charged once. The charge is for the tokens its calls actually used, at the selected model's rate on the lane that served each call, the same rule as Describe to Picker. A default run reads every picker's catalog, so it uses several times the text of one image read.
- Before the run, a hold is reserved: each call's first attempt at the model's output cap. A balance below the hold is refused with
402("Needs up to N credits"). - The quote:
POST /v1/credits/model-costswith the keytext-to-picker:<model>, plus:directon the provider's own API, returns an estimate of a typical run. It is listed inestimates, so show it as≈N. WithoutllmModel, the run uses Gemini 3.8 Flash on the aggregator. Claude Opus 5.5 with no effort runs athighon Anthropic's own API, so its key istext-to-picker:claude-opus-5.5:direct. - Failures: every call the provider billed is charged, a failed one included. A run whose calls the provider did not bill is refunded.
Claude Fable 5 is offered on this route. See How LLM calls are charged. The route 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
Do I need the Nodaro API to use the picker catalogs?
No. The catalogs, and the function that turns a selection into a prompt clause, ship in the @nodaro/prompts npm package, so your app can render the pickers and build prompts offline. The API endpoints exist for clients that cannot bundle the package.
How do I turn a picker selection into prompt text?
Call getParameterPromptHint with the picker's node type and its data, such as the chosen mood id. Nodaro runs the same function on its servers, so your prompt matches the one the editor builds.
Which languages do the picker labels support?
12 locales, English, Spanish, French, German, Brazilian Portuguese, Russian, Hindi, Japanese, Korean, Simplified Chinese, Hebrew and Arabic. The prompt text and the terms stay in English, because the models read them.
Why do the look pickers have no pictures on my self-hosted install?
The previews of the look pickers, such as Mood and Lighting, come from the Nodaro CDN, and only Nodaro Cloud returns them. Every install serves the photos of Person, Styling, Held Prop, Material and Animal, and the music and voice art.
How many picker catalogs are there?
39. There are 28 single-dimension pickers, such as Mood and Lens, and 11 multi-dimension pickers, such as Framing, Lighting and Person.
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.