# Settings form

A prefilled form with typed fields, save/discard controls, and revision-checked writes.

Source: https://littleworks.app/docs/view-settings

![Settings form — local preview with synthetic data](https://littleworks.app/images/views/settings.png)

A prefilled form with typed fields, save/discard controls, and revision-checked writes.

## Build this example

1. Read get_context and list_views. Choose an unused view name; if adapting the name, update every work’s trigger.view and all work bindings together. Inspect existing works before deploying.
2. Deploy each manifest and self-contained JavaScript source below with deploy_function. No terminal, imports, dependencies, or bundler is needed. Record each returned deployment ID.
3. Test unpublished data works with their sample input and a fresh idempotency key. Only run sample actions when requested; they write synthetic records. Do not pass placeholder IDs or stale revisions.
4. Replace each source/action version in the view definition, including modal bindings, with the matching deployment ID. Call validate_view with the deployment object; fix any issues, then call deploy_view.
5. Open the returned manageUrl in Shopify to inspect the draft. Test empty states, form errors and refresh behaviour. Publish only when requested, using the returned version and current publishedVersion. A published page consumes one slot; its modals do not.

> **Example boundaries**
> The load work returns defaults without writing. Saving creates the first document; later saves require its current revision. The fixed document ID and revision checks protect concurrent creation and updates. On a conflict, reload before saving again. Settings stay in this starter’s Preview collection. Wire them into your own build explicitly; this example does not change storefront behaviour.

## Load

### Work manifest

```json
{
  "name": "starter.starter-settings.load",
  "title": "Load",
  "feature": "starter_starter_settings",
  "environment": "preview",
  "trigger": {
    "type": "view",
    "view": "starter-settings",
    "role": "data"
  },
  "permissions": {
    "shopify": false
  },
  "inputSchema": {
    "type": "object",
    "properties": {
      "filters": {
        "type": "object"
      },
      "search": {
        "type": "string"
      },
      "sort": {
        "type": [
          "object",
          "null"
        ]
      },
      "after": {
        "type": [
          "string",
          "null"
        ]
      },
      "limit": {
        "type": "integer",
        "minimum": 1,
        "maximum": 25
      }
    },
    "required": [],
    "additionalProperties": false
  }
}
```

### Self-contained JavaScript source

```javascript
export default async ({db}) => {
  const record = await db.collection("settings").get("preferences");
  return record ? {...record.data, revision: record.revision} : {label: "Request a quote", enabled: true, responseDays: 3, revision: null};
};
```

### Sample invocation input

```json
{
  "filters": {},
  "search": "",
  "sort": null,
  "after": null,
  "limit": 25
}
```

## Save

### Work manifest

```json
{
  "name": "starter.starter-settings.save",
  "title": "Save",
  "feature": "starter_starter_settings",
  "environment": "preview",
  "trigger": {
    "type": "view",
    "view": "starter-settings",
    "role": "action"
  },
  "permissions": {
    "shopify": false
  },
  "inputSchema": {
    "type": "object",
    "properties": {
      "revision": {
        "type": [
          "integer",
          "null"
        ]
      },
      "label": {
        "type": "string",
        "minLength": 1,
        "maxLength": 100
      },
      "enabled": {
        "type": "boolean"
      },
      "responseDays": {
        "type": "integer",
        "minimum": 1,
        "maximum": 30
      }
    },
    "required": [
      "revision",
      "label",
      "enabled",
      "responseDays"
    ],
    "additionalProperties": false
  }
}
```

### Self-contained JavaScript source

```javascript
export default async ({input, db}) => {
  const settings = db.collection("settings");
  const current = await settings.get("preferences");
  if ((current?.revision ?? null) !== input.revision) return Response.json({error: "Settings changed. Refresh before saving."}, {status: 409});
  const label = input.label.trim();
  if (!label) return Response.json({error: "Enter a heading."}, {status: 422});
  const data = {label, enabled: input.enabled, responseDays: input.responseDays};
  const saved = current ? await settings.update(current.id, data, {revision: current.revision}) : await settings.create(data, {id: "preferences"});
  return {revision: saved.revision};
};
```

### Sample invocation input

```json
{
  "revision": null,
  "label": "Request a quote",
  "enabled": true,
  "responseDays": 3
}
```

## View definition

> **Replace placeholder versions**
> Every version below is a placeholder. Use the exact deployment ID for the named work. baseVersion:null creates a new view; when editing, read get_view and use its current version.

### validate_view / deploy_view arguments

```json
{
  "baseVersion": null,
  "definition": {
    "apiVersion": 3,
    "name": "starter-settings",
    "title": "Request settings",
    "sources": [
      {
        "id": "settings",
        "work": "starter.starter-settings.load",
        "version": "00000000-0000-4000-8000-000000000001"
      }
    ],
    "actions": [
      {
        "id": "save",
        "label": "Save settings",
        "work": "starter.starter-settings.save",
        "version": "00000000-0000-4000-8000-000000000002",
        "scope": "page",
        "source": "settings",
        "input": {
          "revision": {
            "source": "data",
            "field": "revision"
          },
          "label": {
            "source": "form",
            "field": "label"
          },
          "enabled": {
            "source": "form",
            "field": "enabled"
          },
          "responseDays": {
            "source": "form",
            "field": "responseDays"
          }
        },
        "fields": [
          {
            "name": "label",
            "label": "Request heading",
            "type": "text",
            "required": true,
            "maxLength": 100,
            "initialField": "label"
          },
          {
            "name": "enabled",
            "label": "Accept requests",
            "type": "checkbox",
            "initialField": "enabled"
          },
          {
            "name": "responseDays",
            "label": "Response time (days)",
            "type": "number",
            "required": true,
            "min": 1,
            "max": 30,
            "step": 1,
            "initialField": "responseDays"
          }
        ],
        "refresh": [
          "settings"
        ]
      }
    ],
    "layout": {
      "type": "stack",
      "main": [
        "settingsCard"
      ],
      "elements": {
        "settingsCard": {
          "type": "Section",
          "props": {
            "heading": "Request preferences"
          },
          "children": [
            "form"
          ]
        },
        "form": {
          "type": "Form",
          "props": {
            "action": "save"
          }
        }
      }
    }
  }
}
```
