All Stories
Platform

One Call Instead of Five

How we eliminated the boilerplate ceremony of building app settings — replacing migrations, tools, and forms with a single schema definition.

The Problem

Five operations for one row of config

Every app we saw being built needed some form of settings — business name, timezone, currency, notification preferences, feature flags. The pattern was always the same:

  1. Create a migration for a settings table
  2. Write a tool that handles “create if not exists, update if it does”
  3. Write another tool to read the settings
  4. Create a form tool for the edit UI
  5. Build a page tying it all together

That's five distinct steps — involving DDL, two code tools, a form, and a page — for something that is conceptually just “let the project admin set some preferences.”

Worse, the resulting data model felt wrong. A full relational table with indexes and auto-generated CRUD tools (list, count, bulk_create) for a table that will only ever hold a single row.

The Insight

Settings aren't data — they're configuration

The platform already distinguishes between app blueprints and project instances. Migrations define the schema at the app level; data lives per-project. Environment variables work the same way — keys are declared in the blueprint, values are set per-project.

Settings fit this exact pattern. The shape of what can be configured is part of the app blueprint. The valuesare per-project. There's no reason to make the builder think about database tables, migration DDL, or unique-row-upsert logic for this.

The Solution

A typed schema that does the rest

One call to configure_settings defines the schema:

{
  "fields": [
    { "key": "company_name", "type": "string", "label": "Company Name", "required": true },
    { "key": "timezone", "type": "string", "label": "Timezone", "default": "UTC" },
    { "key": "tax_rate", "type": "number", "label": "Tax Rate (%)", "default": 0 },
    { "key": "enable_reminders", "type": "boolean", "label": "Send Reminders", "default": true }
  ]
}

From that single definition, the platform provides:

  • Per-project storage — each project gets its own values, isolated automatically
  • Runtime API settings.get("timezone") and settings.getAll() in any code tool
  • Auto-registered tools get_settings and update_settings are ready to use in pages and forms
  • Validation — type checks, enums, min/max, required fields enforced on every write
  • Defaults — new projects see sensible values immediately

The builder still creates their own settings page — full design control. But they wire it to tools the platform provides instead of building the storage layer from scratch.

The Result

Two steps instead of five

What used to require a migration, two code tools, a form, and a page now requires a schema definition and a page. The LLM spends its tokens on unique business logic instead of recreating the same settings boilerplate for every app.

Settings are included in app exports, so marketplace apps come with their configuration schema built in. New installations get sensible defaults immediately. And when the schema evolves — new fields added — existing projects see the new defaults without any data migration.