Skip to main content
ClaudeWave
Skill532 repo starsupdated 2d ago

build-app

# ClaudeWave Item Description The build-app skill guides users through all seven phases of building a production-ready Butterbase application, from provisioning a backend and designing database schema to implementing row-level security policies and deploying a live frontend. Use this skill when a user needs to create a new full-stack Butterbase app from scratch, set up complete infrastructure including database and authentication, or initialize a backend with an auto-generated REST API.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/butterbase-ai/butterbase-skills /tmp/build-app && cp -r /tmp/build-app/skills/build-app ~/.claude/skills/build-app
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

> **Prefer `/butterbase-skills:journey`** — for a fully guided multi-stage build with preflight, planning, deployment verification, and (optionally) hackathon submission. This one-shot skill remains for users who want the legacy linear setup.

# Build a Complete Butterbase App

This skill walks through all seven phases of building a production-ready Butterbase application — from provisioning a backend to deploying a live frontend. Follow each phase in order; later phases depend on artifacts (app_id, schema, RLS policies) produced by earlier ones.

> **Convention:** every JSON body below is the argument object for the tool named in its **Tool:** header. When the header reads `manage_schema` with `action: "apply"`, include `"action": "apply"` alongside the other fields when you make the call.

---

## Phase 1: Create the App

Use `init_app` to provision an isolated backend with its own database and auto-generated REST API.

**Tool:** `init_app`

```json
{
  "name": "my-blog"
}
```

**Returns:**
```json
{
  "app_id": "app_abc123",
  "api_base": "https://api.butterbase.ai/v1/app_abc123"
}
```

**Important:** Save `app_id` and `api_base` — every subsequent tool call requires `app_id`.

### Optional: Generate a Service API Key

If the user needs programmatic access (CI/CD pipelines, server-to-server calls, admin scripts), generate a service key now.

**Tool:** `manage_auth_config` with `action: "generate_service_key"`

```json
{
  "name": "Production Deploy Key"
}
```

> ⚠️ The full key (`bb_sk_...`) is shown **only once**. Store it securely — it cannot be retrieved again.

---

## Phase 2: Design & Apply Schema

Work with the user to understand their data model before writing any SQL. Ask:

- What are the primary entities (users, posts, products, orders)?
- Which tables are user-owned vs. shared/public?
- What relationships exist between tables (foreign keys)?
- Are there any boolean flags for public visibility (e.g. `published`, `is_public`)?

### Preview First with dry_run_schema

Always preview schema changes before applying them.

**Tool:** `manage_schema` with `action: "dry_run"`

```json
{
  "app_id": "app_abc123",
  "schema": {
    "tables": {
      "posts": {
        "columns": {
          "id": { "type": "uuid", "primaryKey": true, "default": "gen_random_uuid()" },
          "author_id": { "type": "uuid", "nullable": false },
          "title": { "type": "text", "nullable": false }
        }
      }
    }
  }
}
```

Review the generated SQL — make sure it matches intent before applying.

### Apply the Schema

**Tool:** `manage_schema` with `action: "apply"`

Below is a complete example for a **blog app** with posts and comments:

```json
{
  "app_id": "app_abc123",
  "schema": {
    "tables": {
      "posts": {
        "columns": {
          "id": { "type": "uuid", "primaryKey": true, "default": "gen_random_uuid()" },
          "author_id": { "type": "uuid", "nullable": false },
          "title": { "type": "text", "nullable": false },
          "body": { "type": "text" },
          "published": { "type": "boolean", "default": "false" },
          "created_at": { "type": "timestamptz", "default": "now()" }
        }
      },
      "comments": {
        "columns": {
          "id": { "type": "uuid", "primaryKey": true, "default": "gen_random_uuid()" },
          "post_id": { "type": "uuid", "nullable": false, "references": "posts.id" },
          "author_id": { "type": "uuid", "nullable": false },
          "body": { "type": "text", "nullable": false },
          "created_at": { "type": "timestamptz", "default": "now()" }
        }
      }
    }
  }
}
```

### Verify the Schema Was Applied

**Tool:** `manage_schema` with `action: "get"`

```json
{
  "app_id": "app_abc123"
}
```

Confirm every table and column is present before moving to Phase 3.

### Schema Tips

- Always include `id` as UUID with `gen_random_uuid()` default
- Always include `created_at` with `now()` default
- Use `author_id` / `user_id` UUID columns on user-owned tables — RLS will reference these
- Use `references: "table.column"` for foreign keys (cascades must be set carefully)
- `manage_schema` action `apply` is idempotent — safe to call again if schema is unchanged

---

## Phase 3: Secure Data with RLS

Row-Level Security (RLS) ensures users can only access their own data. This phase is **not optional** for any table that holds user-generated content.

### Enable User Isolation

Call `create_user_isolation_policy` for each user-owned table. This single call:
1. Enables RLS on the table
2. Creates a policy so users only see their own rows
3. Installs a BEFORE INSERT trigger to auto-populate the user column
4. Creates a service bypass policy for admin access

**Tool:** `manage_rls` with `action: "create_user_isolation"`

```json
{
  "app_id": "app_abc123",
  "table_name": "posts",
  "user_column": "author_id"
}
```

### Allow Public Reads (Optional)

For tables where some rows should be publicly visible (e.g. published blog posts), add `public_read_column`. This creates extra SELECT policies for both authenticated and anonymous users.

```json
{
  "app_id": "app_abc123",
  "table_name": "posts",
  "user_column": "author_id",
  "public_read_column": "published"
}
```

Repeat for every user-owned table. For the blog example:

```json
{
  "app_id": "app_abc123",
  "table_name": "comments",
  "user_column": "author_id"
}
```

### Test RLS Isolation

After applying policies, verify they work correctly by simulating user requests.

**Test SELECT as a specific user** — should only see that user's rows:

**Tool:** `select_rows`

```json
{
  "app_id": "app_abc123",
  "table": "posts",
  "as_role": "user",
  "as_user": "11111111-1111-1111-1111-111111111111"
}
```

**Test SELECT as anonymous** — should only see published/public rows (or nothing if no public policy):

```json
{
  "app_id": "app_abc123",
  "table": "posts",
  "as_role": "anon"
}
```

**Test INSERT as a specific user** — `author_id` should be auto-populated by