---
title: "AI configuration"
description: "Configuration reference, credentials, self-hosted endpoints and the model picker."
requested_language: ar
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/ar/next/payload/ai/configuration
version: unreleased (main)
docs_index: https://docs.systhema.app/ar/next/llms.txt
---
> This page isn't translated yet. Showing English.


The AI assistant is configured entirely through the `ai` plugin option. This page covers self-hosted endpoints, offering several models, every option, and how credentials travel.

## Self-hosted / custom endpoints (Ollama, vLLM, …)

Any OpenAI-compatible endpoint works for text generation:

```ts
ai: {
  text: {
    provider: 'openai-compatible',
    baseURL: 'http://my-server:11434/v1', // e.g. Ollama
    model: 'gemma3',
    // apiKey optional — self-hosted endpoints often run keyless
  },
}
```

## Multiple models (model picker)

Offer several models per kind and let editors choose one per generation. Pass a `models` array on `ai.text` and/or `ai.image` instead of (or alongside — the array wins) the flat fields:

```ts
ai: {
  packageName: 'Systhema AI Pro',
  text: {
    models: [
      { provider: 'openai', model: 'gpt-5.5', apiKey: process.env.OPENAI_API_KEY, label: 'GPT-5.5' },
      { provider: 'openai', model: 'gpt-5-mini', apiKey: process.env.OPENAI_API_KEY, label: 'GPT-5 mini' },
      // A showcase teaser — shown disabled with the hint, never usable:
      { provider: 'anthropic', model: 'claude-opus-4-8', label: 'Claude Opus 4.8', locked: true, upgradeHint: 'Upgrade to Max' },
    ],
  },
  image: {
    models: [
      { provider: 'openai', model: 'gpt-image-2' }, // reuses the OpenAI text key
      { provider: 'google', model: 'gemini-3-pro-image', label: 'Nano Banana Pro', locked: true, upgradeHint: 'Upgrade to Max' },
    ],
  },
  quota: { credits: 5000 },
}
```

- The **first usable, unlocked** entry is the default. `locked` entries skip credential validation — they're advertising what a higher plan unlocks.
- Editors pick from an always-visible dropdown in the compose, lexical, page and image panels; the choice is a **per-user preference** (reconciled against the live config — a removed or locked model falls back to the default). The one-click **SEO** and **alt-text** triggers always use the default model.
- The dropdown is hidden when there's only one selectable entry, so single-model setups look exactly as before.
- In **credit mode**, each text model shows a relative credit hint (e.g. `2x compared to Default`, computed from list prices) and each image model a per-image estimate at the chosen resolution (e.g. `approx. 63 credits`). Never USD.
- The picked model id is validated **server-side** on the `/generate` and `/generate-image` endpoints (the one-click SEO and alt-text triggers carry no model id — they always use the default): a locked or unknown id is rejected with HTTP 400 — the picker is the trust boundary, not the client.
- The single-model flat form is unchanged and fully backwards-compatible (internally it's wrapped as a one-entry list).

## Configuration reference

```ts
ai?: false | {
  enabled?: boolean
  packageName?: string       // optional plan label (e.g. "Systhema AI Pro"); shown on the usage widget + in the "limit reached" message
  text?: {
    provider?: 'openai' | 'anthropic' | 'google' | 'groq' | 'openai-compatible'
    model?: string           // default: per-provider; required for 'openai-compatible'
    apiKey?: string          // forward from your env; required except for 'openai-compatible'
    baseURL?: string         // required for 'openai-compatible' (setting it implies the provider)
    reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high' // OpenAI-only; default: the model's own default
    models?: Array<{         // multi-model: when set, the flat fields above are ignored
      provider?: 'openai' | 'anthropic' | 'google' | 'groq' | 'openai-compatible'
      model?: string
      apiKey?: string
      baseURL?: string
      label?: string         // picker label; default: the model id
      locked?: boolean       // showcase-only upgrade teaser (disabled, no key needed)
      upgradeHint?: string   // hint shown beside a locked entry, e.g. 'Upgrade to Pro'
      reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high' // OpenAI-only, per-model
    }>
  }
  image?: false | {
    provider?: 'openai' | 'google'
    model?: string           // default: 'gpt-image-1' / 'imagen-4.0-fast-generate-001'
    apiKey?: string          // default: reuses text.apiKey when text runs on the same provider
    models?: Array<{         // multi-model image models (same shape, no baseURL)
      provider?: 'openai' | 'google'
      model?: string
      apiKey?: string
      label?: string
      locked?: boolean
      upgradeHint?: string
    }>
  }
  compose?: {
    collections?: string[]   // allowlist; default: all eligible collections
    excludeFields?: string[] // extra field names to skip
  }
  languages?: string[]       // translate targets; default: common European languages
  quota?: {
    text?: number | 'unlimited'   // text generations per period; 0 = not included; default unlimited
    image?: number | 'unlimited'  // image generations per period
    credits?: number | 'unlimited' // pooled credit budget per period; set this to switch to credit mode (supersedes text/image counts)
    creditValue?: number          // USD per credit (hidden); default 0.001 ⇒ a $5 budget is 5,000 credits
    period?: {
      interval?: 'month' | 'year' // default 'month'
      every?: number              // bill every N intervals (2 = bimonthly); default 1
      startDay?: number           // day of month the period starts (1–28); default 1
      start?: string              // ISO anchor date for multi-month/yearly cycles
    }
  }
  pricing?: {                // cost-estimation overrides (merged over built-in list prices)
    text?: Record<string, { input: number; output: number }>  // USD per 1M tokens
    image?: Record<string, number>                            // USD per image
  }
}
```

- `groq` is Groq's hosted OpenAI-compatible endpoint — the base URL is preset (overridable) and the model defaults to `openai/gpt-oss-120b` (a model with full structured-output support, which layout and page generation rely on; Groq's Llama models accept only plain JSON mode). Its low list prices make it a good pairing with `quota` for cost-capped setups.
- `image: false` disables the image surfaces while keeping text generation.
- When `image` is omitted and text runs on `openai`, image generation activates automatically reusing the text API key.
- Prompt-based **refinement** of existing images works with OpenAI's gpt-image models and with Google's **Gemini image models** (the "Nano Banana" family — `gemini-2.5-flash-image`, `gemini-3-pro-image`, …). Google's Imagen models are text-to-image only, so the refine action is hidden for those. Under the hood OpenAI refines via the images API while Gemini edits via `generateText` (the source image is sent as an input part and the edited image comes back in the response files) — the AI SDK's `generateImage` doesn't support Gemini image editing.
- Field-level compose buttons are injected into all eligible collections (system collections like `users`, `forms`, `form-submissions` and `redirects` are always excluded, as are identifier-ish fields like `slug`, `email` or `url`). Use `compose.collections` / `compose.excludeFields` to narrow further.
- `reasoningEffort` (`ai.text`, and per-entry in `ai.text.models`) is an **OpenAI-only** hint — ignored by other providers — controlling reasoning depth for text generation: `'minimal'` and `'low'` trade depth for speed/cost, `'high'` spends more on harder prompts. Defaults to the model's own default when unset. It's threaded into `providerOptions.openai.reasoningEffort`, which the AI SDK maps onto the Responses API's `reasoning.effort`.

## Credentials

API keys travel exclusively through the plugin option — name your environment variables whatever fits your deployment and forward them in `payload.config.ts`. All values are **server-only**: keys live in the server-side config store and never reach the admin client.

If `ai` is set but no usable provider configuration is found (missing key, missing `baseURL` for an OpenAI-compatible endpoint), the feature disables itself with a console warning — the config never crashes.
