Pickers, presets and prompts
Read picker options, fill pickers from a description, manage node presets, palettes and moodboards, and improve prompts with the Prompt Wizard from TypeScript.
Pickers are the Creative Controls nodes, such as Mood or Lens, whose choice adds tested wording to a prompt. client.pickerCatalogs and client.catalogs return each picker's valid options, and analyzeText() fills pickers from a scene description. client.presets manages saved node settings, client.palettes and client.moodboards hold your own colors and style references, and client.promptHelper is the Prompt Wizard, which improves prompts for any generation node. See Creative controls and Picker catalogs for the concepts.
Methods
| Method | What it does |
|---|---|
pickerCatalogs.list() | List every picker and its catalog |
pickerCatalogs.get(nodeType, opts?) | Read one picker's options |
pickerCatalogs.analyzeText(params) | Fill picker choices from a text description |
catalogs.list(opts?) | Read every catalog of this deployment in one call |
presets.list(nodeType?) | List your saved presets |
presets.listGroups(nodeType?) | List your preset folders and sections |
presets.listFactory(nodeType) | List the built-in presets of a node type, without sign-in |
presets.listFactoryGroups(nodeType) | List the categories of the built-in presets, without sign-in |
presets.create(input) and more | Create, update, delete, import and reorder your presets |
presets.favorites(nodeType) and more | Manage preset favorites and folders |
palettes.create(input) and more | Save color palettes to use on Generate Image |
moodboards.create(input) and more | Save moodboards to use on Generate Image |
promptHelper.analyze(input) | Turn a rough idea into questions |
promptHelper.generate(input) | Build a prompt from the answers |
promptHelper.enhance(input) | Improve a prompt in one step |
client.pickerCatalogs
The option lists of the picker nodes. Both read methods are public, need no token, and can be cached by the server for 5 minutes. If your code can import @nodaro/prompts, the same catalogs ship there as typed data; these methods are for clients that cannot bundle it.
pickerCatalogs.list()
Lists every picker (GET /v1/picker-catalogs).
list(): Promise<{ data: PickerCatalogSummary[] }>const { data: pickers } = await client.pickerCatalogs.list()
const mood = pickers.find((p) => p.nodeType === "mood")Each entry has nodeType, label, catalogId, kind (single or multi), valueField for a single-dimension picker or fields for a multi-dimension one, optionCount, and imageCount, the number of options with a picture.
pickerCatalogs.get(nodeType, opts?)
Reads one picker's options (GET /v1/picker-catalogs/:nodeType). A single-dimension picker, such as Mood, has options. A multi-dimension picker, such as Person, has dimensions, each { field, label, options }. A few single-dimension pickers also have extra settings next to the main choice: transition and character-fx have position, duration and intensity, and character-motion has position and pace.
get(nodeType: string, opts?: { detail?: "compact" | "full"; category?: string; field?: string }): Promise<{ data: PickerCatalog }>Prop
Type
const { data } = await client.pickerCatalogs.get("mood", { detail: "full" })
const serene = data.options?.find((o) => o.id === "serene")
console.log(serene?.term) // the short phrase, added in Compact mode
console.log(serene?.promptHint) // the full sentence, added in Full modeThrows NotFoundError for an unknown node type.
Show label, add term. Every option has a term at both detail levels: the short professional phrase to put in a prompt. label is for display only. Never derive one from the other. An option that adds nothing, such as auto or none, has an empty term.
Image and video styles. A style option offered for one kind of output only carries media, at both detail levels: ["video"] on the video-only styles. Without media, the option is offered for both. An image style list leaves out an option whose media lacks "image". A style option that a list for restyling a filmed clip leaves out carries restyle: false. A style option's promptHint is its image wording. A video prompt adds the style's video wording instead, which the API does not return. See Video prompts and the Style wording.
Pictures. An option with a picture has an absolute imageUrl, at both detail levels. person and styling also return sections: the topics the editor groups their settings under, each { label, fields, imageUrl? }. Photos are served by the installation itself. The rendered look previews, and on Nodaro Cloud the photographic renders of the music and voice pickers, come from the Nodaro CDN. A self-hosted install serves its own music and voice art instead. A picture from the Nodaro CDN also carries imageSrcSet, the same picture at 240 to 1,920 px wide, for <img srcset>. File names carry a content hash, so you can cache the pictures without expiry. A deployment that shows its own pictures (catalogs.images: "own") returns its own imageUrl for the options it gave one, on its own media host. It may add imageLargeUrl, an enlarged picture for where your picker shows the option big, such as on hover. Draw imageUrl everywhere else, and wherever an option has no imageLargeUrl.
const { data: person } = await client.pickerCatalogs.get("person")
for (const section of person.sections ?? []) {
const settings = person.dimensions?.filter((d) => section.fields.includes(d.field)) ?? []
for (const setting of settings) {
for (const option of setting.options) renderTile(option.label, option.imageUrl)
}
}Character Motion. The options of character-motion carry an optional motion object at both detail levels. It says what a move needs and leaves: requires, startPose and endPose, endVisibility, handsAfter, needsFreeHands, kind, fixedPace, counterpart, search aliases, and deprecated with a replacementId. A missing field means unknown. Keep retired ids when you load saved workflows, and hide them from new choices. See Character Motion.
pickerCatalogs.analyzeText(params)
Fills picker choices from a free-text scene description (POST /v1/text-to-picker), the text version of Describe to Picker. It returns pickerJson, keyed by picker type, then by dimension, then the chosen id or ids. Load pickers from it as it is, then let the user adjust them. It is charged for the tokens its calls used, at the model's rate on the lane that served each call, like Describe to Picker. A default run reads every picker's catalog, so it uses more text than one image read. A hold is reserved first, and a balance below it is refused. Claude Opus 5.5 runs at high by default, on Anthropic's own API. To quote a typical run, pass llmPriceKey("text-to-picker", model) to credits.modelCosts(). See How LLM calls are charged.
analyzeText(params: TextToPickerParams): Promise<{
jobId: string
pickerJson: Record<string, Record<string, string | string[]>>
gaps?: { missingItems: object[]; missingCategories: object[] }
}>Prop
Type
const { pickerJson, gaps } = await client.pickerCatalogs.analyzeText({
text: "Neon-soaked Tokyo alley at night, rain, handheld tracking shot, moody synthwave",
})
console.log(pickerJson["setting"], pickerJson["camera-motion"])Dimensions the text says nothing about are left out: the analysis does not guess. gaps lists what the text describes that no catalog option represents well, in missingItems and missingCategories. Show it to the user as "we could not match X; choose one yourself".
client.catalogs
catalogs.list(opts?)
Returns every picker catalog in one call, as this deployment serves it (GET /v1/catalogs). A deployment can register catalog packs that replace, extend or hide options. A client that draws its own pickers should read this list to respect them. It is public and can be cached for 5 minutes.
list(opts?: { detail?: "compact" | "full" }): Promise<{
curated: boolean
packs: number
version: number
data?: ProjectedCatalog[]
}>Prop
Type
const { curated, data } = await client.catalogs.list({ detail: "full" })
if (curated) {
const setting = data?.find((c) => c.catalogId === "setting")
console.log(setting?.options?.[0]?.term)
}data is present when the deployment registered catalog packs (curated: true). Without packs, the catalogs are the standard ones: read them one by one with pickerCatalogs.get(). One exception: a deployment that shows its own picker pictures returns data with curated: false, the standard catalogs with its pictures. Options carry the same imageUrl (and, on such a deployment, imageLargeUrl), and person and styling the same sections.
client.presets
Your saved node presets, your favorites and the built-in catalog. A preset's data is a node's saved settings. To apply a preset, merge its data into a node's data when you build a workflow, or pass presetId to a Generate Image run. See Presets.
The built-in catalog from listFactory() is public and needs no token. For your own presets, an OAuth token needs presets:read for reads and assets:write for writes. Your own API token needs neither, but a token limited to some workflows cannot reach your presets.
Every preset, yours and the built-in ones, carries two more fields, and a built-in preset can carry three more. Servers that predate them leave them out.
minReferencesis how many connected reference images the preset works from, andneedsImageistruewhen that is above 0. Reference Sheet, Portrait Transformations, Edits and every template that uses{image:N}need at least one. Run such a preset with that many reference images.previewUrlis an example picture, ornull. On your own presets, it is the image you stored with the preset.previewSrcSet, on a built-in preset, is the same picture in its two sizes, 512 px and up to 1024 px wide, for an<img srcset>. It is present only when the preset has both sizes.previewWidthandpreviewHeight, on a built-in preset, are the tile picture's real size. Draw the picture in a box of its own shape before it loads. A preset'sdata.aspectRatiois what it generates, not always its picture's shape.sectionandplatforms, on a built-in preset, are its sub-heading inside itsgroup, such as a YouTube topic, and where the result is meant to be posted, most fitting first. They are present only on a preset that has them, so group and filter by them only then.
presets.list(nodeType?)
Lists your saved presets, newest first (GET /v1/node-presets).
list(nodeType?: string): Promise<NodePreset[]>Prop
Type
const presets = await client.presets.list("generate-image")
const cinematic = presets.find((p) => p.name === "Cinematic Portrait")
// apply: spread cinematic.data into the node's data when you create the workflowA NodePreset has id, nodeType, name, description, data, groupId, tags, sortOrder, minReferences, needsImage, previewUrl, createdAt and updatedAt.
presets.listGroups(nodeType?)
Lists your preset folders and sections (GET /v1/node-preset-groups).
listGroups(nodeType?: string): Promise<NodePresetGroup[]>Prop
Type
const groups = await client.presets.listGroups("generate-image")Each group has id, nodeType, name, kind (folder or section), sortOrder, createdAt and updatedAt.
presets.listFactory(nodeType)
Lists the built-in presets of a node type (GET /v1/node-presets/factory).
listFactory(nodeType: string): Promise<{ data: FactoryPreset[] }>Prop
Type
Sign-in is optional. The catalog is public, so a client without a credential can read it. Show the names, example pictures and photo counts to a visitor, and ask for a sign-in only when they generate.
// Signed out: a client with no credential can still read the catalog
const visitor = createClient({ baseUrl, auth: { getToken: async () => null } })
const { data } = await visitor.presets.listFactory("generate-image")
const withPicture = data.filter((p) => p.previewUrl)
// A preset that works from photos runs with at least minReferences of them.
const board = data.find((p) => p.id === "generate-image/character-board")!
if (board.needsImage && photos.length >= (board.minReferences ?? 0)) {
await client.nodes.runAndWait("generate-image", { presetId: board.id, referenceImageUrls: photos })
}presets.listFactoryGroups(nodeType)
Lists the categories of the built-in presets of a node type (GET /v1/node-presets/factory/groups).
listFactoryGroups(nodeType: string): Promise<FactoryPresetGroupSummary[]> // [{ group, shelf, count, previewUrl, previewUrls }]Each entry has the group's name (group), the shelf a picker files it under (shelf), the number of presets in the group (count), and the example picture of its first preset that has one (previewUrl, or null). previewUrls holds the group's first three example pictures in catalog order, each once, and is empty when none has one. The shelves are "video", "marketing", "photos", "creative", "life", "pro" and "more", in that order. "more" holds any group that no shelf names yet. The groups come in the order a picker shows them, shelf by shelf.
The answer is a few KB, where listFactory() returns the whole catalog. Use it for a menu or a shelf that lists the categories. Like listFactory(), it is public and needs no sign-in.
const groups = await visitor.presets.listFactoryGroups("generate-image")
const video = groups.filter((g) => g.shelf === "video")presets.create(input)
Saves a preset (POST /v1/node-presets).
create(input: CreateNodePresetInput): Promise<NodePreset>Prop
Type
const preset = await client.presets.create({
nodeType: "generate-image",
name: "Studio portrait",
data: { provider: "gpt-image-2", aspectRatio: "3:4", prompt: "{image:1:person}, studio portrait" },
previewUrl: imageUrl, // the image of one of your completed jobs, or an upload
})
preset.needsImage // true, because the prompt names {image:1}The preset keeps its own copy of the picture, so the returned previewUrl is the copy's address. An account keeps at most 5,000 presets. Past that, the call fails with 409 limit_reached.
presets.update(id, patch, opts?) and delete(id, opts?)
update(id: string, patch: UpdateNodePresetInput, opts?: { expectedUpdatedAt?: string }): Promise<NodePreset>
delete(id: string, opts?: { expectedUpdatedAt?: string }): Promise<{ success: true }>update() changes any of name, description, data, groupId, tags, sortOrder and previewUrl (null removes the picture). delete() removes the preset, and fails with 404 when the preset is not yours. Pass the updatedAt you read as expectedUpdatedAt, and the call fails with 409 conflict when someone changed the preset since.
presets.import(presets) and reorder(input)
import(presets: ImportNodePresetInput[]): Promise<{ imported: number }>
reorder(input: ReorderNodePresetsInput): Promise<{ ok: true }>import() saves up to 500 presets at once, each { nodeType, name, description?, data, previewUrl? }. A name you already use is kept as "… (imported 2)". A preset whose data is empty after the runtime keys are removed is skipped. The entries of the file that nodaro presets export writes fit as they are.
reorder() takes { groups?: [{ id, sortOrder }], presets?: [{ id, groupId?, sortOrder }] }. It sets positions and moves presets into a folder, or out of one with groupId: null.
Preset favorites and folders
favorites(nodeType: string): Promise<string[]>
addFavorite(input: { nodeType: string; presetId: string }): Promise<{ success: true }>
removeFavorite(input: { nodeType: string; presetId: string }): Promise<{ success: true }>
createGroup(input: { nodeType: string; name: string; kind?: "folder" | "section"; sortOrder?: number }): Promise<NodePresetGroup>
updateGroup(id: string, patch: { name?: string; sortOrder?: number }): Promise<NodePresetGroup>
deleteGroup(id: string): Promise<{ success: true }>A favorite is a built-in id, such as "generate-image/character-board", or one of your preset ids. Adding and removing a favorite are safe to repeat. Deleting a folder moves its presets out of it and deletes none of them.
client.palettes
A palette is 2 to 12 colors in #RRGGBB form under a name. On generate-image, a palette is a direction value: set direction.colorLook to paletteDirectionId(id), which is "palette:<id>". Every color then goes into the prompt, by an everyday name and its hex code. Palettes are yours only: another person's id answers 404.
import { paletteDirectionId } from "@nodaro/sdk"
const palette = await client.palettes.create({ name: "Harbour", colors: ["#0F4C5C", "#E3B23C", "#F28C7A"] })
await client.nodes.runAndWait("generate-image", {
prompt: "a lighthouse at dusk",
direction: { colorLook: paletteDirectionId(palette.id) },
})| Method | Returns | Notes |
|---|---|---|
list(params?: ListPalettesParams) | Promise<PalettePage> | Newest first. limit (1 to 100, default 50) and cursor. The page carries nextCursor. |
get(id: string) | Promise<ColorPalette> | One palette. |
create(input: CreatePaletteInput) | Promise<ColorPalette> | name (1 to 80 characters), colors (2 to 12 #RRGGBB values in any case, stored upper case) and description (your note, up to 500 characters, never put into the prompt). |
update(id: string, input: UpdatePaletteInput) | Promise<ColorPalette> | Writes only the fields you pass. description: null clears it. |
delete(id: string) | Promise<void> | A later run that names the palette fails with 404 direction_not_found. |
client.moodboards
A moodboard is a description and 1 to 12 images. On generate-image, a moodboard is a direction value: set direction.style to moodboardDirectionId(id), which is "moodboard:<id>". The description goes into the prompt, and the images go to the model as style references. They come after your own references, up to the model's reference limit. Images are links, never copies, and must be http(s) addresses that are not localhost or a private address.
import { moodboardDirectionId } from "@nodaro/sdk"
const board = await client.moodboards.create({
name: "Nordic calm",
description: "Moody Nordic interiors, soft daylight, linen and oak",
images: [{ url: "https://example.com/a.png", caption: "soft daylight" }, { url: "https://example.com/b.png" }],
})
const out = await client.nodes.run("generate-image", {
prompt: "a reading corner",
direction: { style: moodboardDirectionId(board.id) },
})
// out.warnings: [{ code: "moodboard_images_not_sent", message }] when some images did not fit| Method | Returns | Notes |
|---|---|---|
list(params?: ListMoodboardsParams) | Promise<MoodboardPage> | Newest first. limit (1 to 100, default 50) and cursor. The page carries nextCursor. |
get(id: string) | Promise<Moodboard> | One moodboard. |
create(input: CreateMoodboardInput) | Promise<Moodboard> | name (1 to 80 characters), description (up to 1,000 characters, the words that go into the prompt) and images (1 to 12 { url, caption? }, each caption up to 200 characters). |
update(id: string, input: UpdateMoodboardInput) | Promise<Moodboard> | Writes only the fields you pass. images replaces them all. description: null clears it. |
delete(id: string) | Promise<void> | Deletes the moodboard only. The linked images are untouched. |
Rules for both
PALETTE_DIRECTION_PREFIX("palette:"),MOODBOARD_DIRECTION_PREFIX("moodboard:"),paletteDirectionId(id)andmoodboardDirectionId(id)build the direction values.GenerateImageParams.directionis typedDirectionFields, and a run's answer typeswarnings?: RunNodeWarning[].- A malformed value answers 400. An id that is not yours answers 404
direction_not_found. Both fail before anything is created or charged. - A palette or moodboard cannot be combined with
presetId. That run answers 400validation_error, and so does a preset saved with one in its direction. - OAuth app tokens need
assets:readfor reads andassets:writefor writes.
client.promptHelper
The Prompt Wizard: AI help to write prompts for generation nodes. All three methods send their request to POST /v1/prompt-helper/wizard, and each call is charged for the tokens it used, at the model's rate. See Prompt Wizard for the REST view.
All three take these common fields:
Prop
Type
The CLI offers the same through nodaro prompt wizard, analyze, generate and enhance, with --llm-model and --reasoning-effort. See the CLI.
promptHelper.analyze(input)
Turns a rough idea into guided questions for a node type. Answer them, then pass the answers to generate().
analyze(input: AnalyzeInput): Promise<{ jobId: string; questions: WizardQuestion[] }>Prop
Type
const { questions } = await client.promptHelper.analyze({
nodeType: "generate-image",
prompt: "a snow leopard",
})promptHelper.generate(input)
Builds one optimized prompt from the chosen answers. Each selection is { category, value, isCustom }.
generate(input: GenerateInput): Promise<{ jobId: string; prompt: string; recommendedModel?: RecommendedModel }>Prop
Type
const { prompt, recommendedModel } = await client.promptHelper.generate({
nodeType: "generate-image",
selections: [{ category: "subject", value: "snow leopard", isCustom: false }],
})promptHelper.enhance(input)
Improves a prompt in one step, without the questions.
enhance(input: EnhanceInput): Promise<{ jobId: string; prompt: string; recommendedModel?: RecommendedModel }>Prop
Type
const { prompt } = await client.promptHelper.enhance({
nodeType: "generate-image",
prompt: "snow leopard on a rock",
reasoningEffort: "high",
})Frequently asked questions
How do I find the valid ids for the direction object?
Call client.pickerCatalogs.list() for every picker, then client.pickerCatalogs.get(nodeType) for one picker's options. Each option's id is what direction and picker nodes accept.
What is the difference between label, term and promptHint?
label is the display name. term is the short professional phrase the picker adds in Compact mode. promptHint is the longer sentence it adds in Full mode, returned only with detail full.
How do I improve a prompt with the SDK?
Call client.promptHelper.enhance with the target nodeType and your prompt. It returns an improved prompt and, sometimes, a recommended model. Each call costs credits.
Can the SDK create or change node presets?
Yes. client.presets creates, updates, deletes, imports and reorders your presets, and manages favorites and folders. The built-in catalog from listFactory needs no sign-in.
How do I use my own colors on a Generate Image run?
Save a palette with client.palettes.create, then pass direction.colorLook set to paletteDirectionId(palette.id) on generate-image. Every color goes into the prompt by an everyday name and its hex code.
Related
Picker catalogs
Creative controls
Presets
Prompt Wizard
Run nodes
Last updated on
Models and credits
List the AI models a Nodaro instance offers, read your credit balance, look up a model's price before you run it, and read plans and your subscription.
Organizations and workspaces
Manage Nodaro organizations and workspaces from TypeScript. Create schools and teams, invite members, hand out join codes, and read credit usage reports.