---
title: "GET /feedback_portal/{portalId}/widget/{widgetId}/config"
method: GET
path: "/feedback_portal/{portalId}/widget/{widgetId}/config"
tags: ["Feedback Portals"]
---

# GET /feedback_portal/{portalId}/widget/{widgetId}/config

`GET /feedback_portal/{portalId}/widget/{widgetId}/config`

## Request

- Base URL: `https://api.craft.io`
- URL: `https://api.craft.io/feedback_portal/{portalId}/widget/{widgetId}/config`
- Auth: API key in header `x-api-key`

Everything the portal's embedded feedback widget needs to render its form, in one read.

**Called when the form opens, not when the page loads.** The widget fetches its signed token
from your own server at that moment and uses it here, which is why an hour-long token is
comfortable: the window only has to cover somebody writing, not their whole visit.

**The fields come from the portal every time.** A PM adding a required field appears in the
widget on the next page load with nobody editing your site — which is the point of embedding
the portal's own form rather than a copy of it. Each field carries `required`, so the widget can
catch a missing one before posting instead of by a rejection.

**Read this when the widget OPENS, not when the page loads.** Everything here is a setting an
administrator can change at any moment, so the response is `Cache-Control: no-store` and a copy
held across a long-lived page goes out of date. Nothing can make this read and the submission
that follows one transaction, which is why a stale `importanceId` is forgiven on submit rather
than refused — but the fresher this read, the less often a submitter is shown an option that
has since gone.

**One list, already in order.** A form's built-in fields — its description box — are the
portal's to show, hide, reorder and mark required, exactly like a field somebody created, so a
page that hardcodes them drifts the moment an administrator changes their mind. Both kinds
arrive merged in `fields`, sorted the way the form editor arranges them — the description box
can sit between two created fields, and it will here too. Draw the array top to bottom.

Each entry carries `kind`, which is also where its value goes on submit: a `custom` field's
belongs in `customFields` under its id, a `system` field's is the request property its `id`
names.

**The categories are the widget's, narrowed here.** A widget offers a chosen set; that
narrowing is applied server-side, so what comes back is exactly what the form may show. Every
offered category is covered by exactly one form: a category with no form of its own gets the
portal's default form, which is the same form the submission will be validated against.

**`widget.hideImportance` means leave the row out, not disable it.** Draw no importance control
at all, and post `widget.defaultImportanceId` as the `importanceId` — that is the answer the
administrator gave in place of the submitter's, which is the point of hiding the question rather
than a detail of it. A null there means nobody picked one: post no `importanceId` and the
portal's default applies.

**`defaultImportanceId` is already resolved.** An administrator deleting the importance a widget
named does not break it and does not rewrite it: the fallback to the first of `importances` is
worked out on every read and nothing is written back, so restoring the importance restores the
choice.

**So do the words.** `terminology` carries the portal's own names for feedback and for a
company — `{ default, customSingle, customPlural }` for each, always both keys, defaults filled
in when nobody has renamed anything. A portal that calls feedback a "Request" gets a form that
says Request, for the same reason it gets the portal's fields: a copy of the form hardcodes the
word and keeps saying it after a PM changes it.

**No cookie is set and no Craft session exists** — on this call or on the submission that
follows it. That is a property of the feature rather than an implementation detail: nothing
about the person browsing your site is stored in their browser by Craft.

## Path parameters

- `portalId` string, required
- `widgetId` string, required

## Response `200`

The widget's form definition

- WidgetConfig: Everything the embedded feedback widget needs to render its form, in one read. ONE CALL AT FORM-OPEN, and the narrowing is server-side. A widget offers a chosen set of categories, and applying that here rather than in the page is what stops a widget configured for one category being made to render another — the browser is not a place to enforce it.
  - `widget` object, required
    - `defaultImportanceId` string, nullable, required: The importance to submit while `hideImportance` is true — what the administrator chose instead of asking. **Already resolved.** If the importance it named has since been deleted from the portal, this is the first of `importances` rather than a dangling id, so the value is always one you can post. Nothing is written back: the widget's stored configuration is untouched and the fallback is worked out on every read, so restoring the importance restores the choice. **Null means nothing was picked**, which is an ordinary state and not an error. Submit with no `importanceId` and the portal's default applies. Present regardless of `hideImportance` — a widget that shows the field keeps its stored value, so an administrator toggling the question off and on again does not lose it. **When the field is shown, this is not the value to send**: send what the submitter chose.
    - `hideImportance` boolean, required: True when this widget's form should not ask how important the feedback is — **leave the row out entirely rather than showing it disabled.** Per widget, because the judgement is per placement: a form behind a "Report a bug" button in a product header asks somebody mid-task, who leaves whatever is preselected, so the field costs a line and returns a value nobody chose. **Then submit `defaultImportanceId` as the `importanceId`** — that is the administrator's answer in place of the submitter's. When it is null, submit without an `importanceId` at all and the feedback takes the portal's default importance (its `isDefault` one, or its first otherwise).
    - `categories` object[], required
      - `name` string, required
      - `id` string, required
    - `title` string, required: The heading submitters read. Not the administrator's internal name, which is never shown.
    - `id` string, required
  - `brand` object, required
    - `primaryColor` string, nullable, required: Portal-global, not per widget. Null when the administrator has not chosen one.
  - `importances` PortalImportance[], required
    - `id` string, required
    - `name` string, required
    - `isDefault` boolean: True when this importance is the portal's default — used by create_feedback when `importanceId` is omitted.
  - `terminology` TerminologyListPortalTerminology, required
  - `forms` WidgetForm[], required: The portal's own form definitions, narrowed to the widget's offered categories — each field carrying whether it is required, so the widget can catch a missing one before posting rather than by a rejection. **Look a form up by category id.** Each offered category appears on exactly one form, so a category is never left without one: where the portal's administrator has not given a category a form of its own, the portal's default form (`isDefault`) covers it — and that is the same form the submission is checked against, so a required field here is a required field there. The one exception is a portal with no default form and no form for the category, which has no fields to render and none to enforce.
    - `id` string, required
    - `name` string, required
    - `isDefault` boolean, required: True for the portal's default form — what a category with no form of its own is given.
    - `categories` Entity[], required
      - `id` string
      - `name` string, nullable
    - `fields` WidgetFormField[], required
      - `kind` 'custom' | 'system', required
      - `id` string, required
      - `required` boolean, required
      - `orderIndex` number, double, required: The administrator's position for this field, both kinds on one scale — the array is already sorted by it, so this is here to be read rather than acted on. Not dense and not necessarily positive: a form nobody has re-saved since the editor gained drag ordering carries positions the monolith's counter allocated.
      - `name` string
      - `description` string: The help line drawn under the label, as the portal's own form draws it. Plain text — render it as text, never as HTML. Absent when the field has none.
      - `type` string
      - `options` CustomFieldOption[]
        - `id` string, required
        - `label` string, required
        - `startDate` string, nullable
        - `endDate` string, nullable
      - `optionShape` 'select' | 'date' | 'date_range'
      - `multi` boolean: SELECT fields only: send `values: [...]` when true, a scalar `value` when false.

## Other responses

- `401`: Missing, expired or invalid credential — carries `error`: `token_expired` (fetch a new token and retry) | `token_invalid` | `dev_key_invalid`, except when the Authorization header is absent entirely
- `403`: Access denied, or this portal is not set up for widgets — carries `error: "widget_not_enabled"` on the latter
- `404`: No such widget on this portal, or it is inactive — carries `error`: `widget_unknown` | `widget_deleted` | `widget_inactive` | `widget_no_categories`, except when the PORTAL is what was not found
- `422`: Invalid portal or widget ID
- `503`: The widget or category read failed — RETRY THIS. Carries `Retry-After` and `code: "retryable"`.

## Changes

- **2026-10-20** `1783050d3298`: 1 info
  - added the optional property `forms/items/fields/items/description` to the response with the `200` status
- **2026-10-01** `6009bb084cb5`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/craft/apis/craft-io-public-api/changes/feedback_portal/:portalId/widget/:widgetId/config/get.md)

---

[API](https://skmtc.dev/craft/apis/craft-io-public-api.md) - [All operations](https://skmtc.dev/craft/apis/craft-io-public-api/llms.txt) - [OpenAPI document](https://skmtc.dev/craft/apis/craft-io-public-api/revisions/2972a1b05ab1?raw)
