Claude Marketplaces
SkillsMCP serversMarketplacesDigestTools
Submit

This week in Claude

Every Monday: Claude Code, Agent SDK, MCP, and the Anthropic platform moves worth your time.

Skills by category
Frontend DevelopmentBackend & APIsTesting & QASecurityDevOps & CI/CDGit & Pull RequestsDocumentationCode Review & QualityAI & Agent BuildingSkill Development
MCP servers by category
Sales & MarketingWeb & Browser AutomationDatabasesAI & LLM ToolsCloud & InfrastructureCommunication & MessagingDeveloper ToolsDesign & CreativeDocuments & KnowledgeSearch & Web Crawling
Marketplaces by category
AI Agents & OrchestrationLLM IntegrationDevelopment ToolsFrontend & UIBackend & APIsDatabasesTesting & Code QualityDevOps & CloudSecurity & ComplianceGit & Version Control
Claude Marketplaces

An independent directory of skills, plugins and MCP servers for Claude Code. Not affiliated with Anthropic.

Browse

  • Skills
  • MCP servers
  • Marketplaces
  • Weekly digest
  • Skill index
  • MCP index
  • Marketplace index

Site

  • About
  • Tools
  • Submit
  • Feedback
  • Privacy
  • llms.txt
  • Plugins reference

Maker

  • Snug
  • Agent Caffeine
  • knobs
  • Wax
  • Vectoys
  • mertduzgun.com
Crafted at
Skills/Backend & APIs
butterbase-ai avatar

Build App

butterbase-ai/butterbase-skills

This is the full linear workflow for spinning up a Butterbase backend: provision an app, design and apply a Postgres schema, lock it down with RLS policies, wire up OAuth, and deploy a frontend. It walks you through all seven phases with JSON examples for every tool call, including the dry-run-then-apply pattern for schema changes and test commands to verify your RLS isolation actually works. The docs note there's a newer guided journey skill that breaks this into stages with preflight checks, but this one-shot version is still solid if you want the no-frills path from zero to a deployed full-stack app with auth and database in one go.

GitHub stars
532
last updated
Jun 11, 2026
View on GitHubSave

Install

$ npx -y skills add butterbase-ai/butterbase-skills --skill build-app --agent claude-code

Installs into .claude/skills of the current project.

Files

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

{
  "name": "my-blog"
}

Returns:

{
  "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"

{
  "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"

{
  "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:

{
  "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"

{
  "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"

{
  "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.

{
  "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:

{
  "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

{
  "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):

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

Test INSERT as a specific user — author_id should be auto-populated by the trigger (do not include it in data):

Tool: insert_row

{
  "app_id": "app_abc123",
  "table": "posts",
  "data": {
    "title": "My First Post",
    "body": "Hello world!",
    "published": false
  },
  "as_role": "user",
  "as_user": "11111111-1111-1111-1111-111111111111"
}

Confirm the returned row has author_id set to 11111111-1111-1111-1111-111111111111 automatically.

Verify All Policies

Tool: manage_rls with action: "list"

{
  "app_id": "app_abc123"
}

Review the policy list and confirm every user-data table has at least one policy.


Phase 4: Authentication

Butterbase uses OAuth 2.0 for end-user authentication. Users sign in via a provider (Google, GitHub, Discord, etc.) and receive a JWT to authenticate subsequent API calls.

Configure an OAuth Provider

Built-in providers (google, github, discord, facebook, linkedin, microsoft, apple, x) only require three fields — URLs and scopes are auto-filled.

Tool: manage_oauth with action: "configure"

Google example:

{
  "app_id": "app_abc123",
  "provider": "google",
  "client_id": "123456789.apps.googleusercontent.com",
  "client_secret": "GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx",
  "redirect_uris": ["https://api.butterbase.ai/auth/app_abc123/oauth/google/callback"]
}

GitHub example:

{
  "app_id": "app_abc123",
  "provider": "github",
  "client_id": "Iv1.xxxxxxxxxxxxxxxx",
  "client_secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "redirect_uris": ["https://api.butterbase.ai/auth/app_abc123/oauth/github/callback"]
}

Redirect URI format:

https://api.butterbase.ai/auth/{app_id}/oauth/{provider}/callback

Replace {app_id} and {provider} with real values. Register this exact URI in the OAuth provider's developer console.

Setup Steps (per provider)

  1. Go to the provider's developer console (Google Cloud Console, GitHub Settings → Developer applications, etc.)
  2. Create a new OAuth app / client
  3. Set the redirect URI to the Butterbase callback URL above
  4. Copy the client_id and client_secret
  5. Call configure_oauth_provider with those credentials

Frontend Authentication Flow

Install the Butterbase SDK in the frontend:

npm install @butterbase/sdk

Initialize the client and trigger OAuth sign-in:

import { createClient } from '@butterbase/sdk'

const client = createClient({
  appId: 'app_abc123',
  apiBase: 'https://api.butterbase.ai/v1/app_abc123'
})

// Initiate OAuth sign-in (redirects to provider)
await client.auth.signInWithOAuth({ provider: 'google' })

// After redirect back, get the current session
const { user, accessToken } = await client.auth.getSession()

// Use the access token in API calls — the SDK handles this automatically
const posts = await client.from('posts').select('*')

Adjust JWT Token Lifetimes (Optional)

Tool: manage_auth_config with action: "update_jwt"

{
  "app_id": "app_abc123",
  "accessTokenTtl": "15m",
  "refreshTokenTtlDays": 30
}

Short access tokens (15m) with longer refresh tokens (30 days) balance security and user experience.


Phase 5: Backend Logic (Optional)

Deploy serverless functions for business logic that cannot run in the browser: sending emails, processing payments, calling third-party APIs with secrets, scheduled jobs, and more.

Deploy a Function

Tool: deploy_function

Critical rules:

  • Handler must be exported as export async function handler
  • Handler must return a new Response() object — never return a plain object
  • Context provides db (Postgres), env (encrypted env vars), user (authenticated user or null)

HTTP function example:

{
  "app_id": "app_abc123",
  "name": "create-post",
  "description": "Create a new blog post and notify subscribers",
  "trigger": {
    "type": "http",
    "config": {
      "method": "POST",
      "path": "/create-post",
      "auth": "required"
    }
  },
  "code": "export async function handler(request, ctx) {\n  const { title, body } = await request.json();\n  if (!title) {\n    return new Response(JSON.stringify({ error: 'title is required' }), {\n      status: 400,\n      headers: { 'Content-Type': 'application/json' }\n    });\n  }\n  const result = await ctx.db.query(\n    'INSERT INTO posts (title, body, author_id) VALUES ($1, $2, $3) RETURNING *',\n    [title, body, ctx.user.id]\n  );\n  return new Response(JSON.stringify(result.rows[0]), {\n    status: 201,\n    headers: { 'Content-Type': 'application/json' }\n  });\n}"
}

Cron function example (runs daily at 9 AM UTC):

{
  "app_id": "app_abc123",
  "name": "daily-digest",
  "description": "Send a daily digest email to subscribers",
  "trigger": {
    "type": "cron",
    "config": {
      "schedule": "0 9 * * *",
      "timezone": "UTC"
    }
  },
  "code": "export async function handler(request, ctx) {\n  const result = await ctx.db.query(\n    'SELECT * FROM posts WHERE created_at > NOW() - INTERVAL \\'24 hours\\''\n  );\n  // send digest email with result.rows...\n  return new Response(JSON.stringify({ sent: result.rows.length }), {\n    status: 200,\n    headers: { 'Content-Type': 'application/json' }\n  });\n}"
}

Pass Secrets via Environment Variables

Never hardcode API keys. Pass them as envVars:

{
  "app_id": "app_abc123",
  "name": "send-email",
  "envVars": {
    "SENDGRID_API_KEY": "SG.xxxxxxxxxxxxxxxx",
    "FROM_EMAIL": "noreply@example.com"
  },
  "trigger": { "type": "http", "config": { "method": "POST", "auth": "required" } },
  "code": "export async function handler(request, ctx) {\n  const apiKey = ctx.env.SENDGRID_API_KEY;\n  // use apiKey...\n  return new Response('ok', { status: 200 });\n}"
}

To rotate secrets without redeploying code, call manage_function with action: "update_env".

Test a Function

Tool: invoke_function

{
  "app_id": "app_abc123",
  "function_name": "create-post",
  "method": "POST",
  "body": {
    "title": "Hello World",
    "body": "This is a test post."
  }
}

Debug with Logs

If a function returns an unexpected response or error, check logs immediately.

Tool: manage_function with action: "get_logs"

{
  "app_id": "app_abc123",
  "function_name": "create-post",
  "level": "error",
  "limit": 20
}

Logs include stack traces, duration, memory used, and status codes. Logs are retained for 7 days.

RLS Behavior in Functions

Invoked withRole assignedRLS enforced?
End-user JWTbutterbase_userYes — sees only user's data
Platform API keybutterbase_serviceNo — sees all data
Cron triggerbutterbase_serviceNo — sees all data

Phase 6: Deploy Frontend

Deploy the frontend as a static site. Butterbase hosts it on a CDN with SPA routing support.

Step 1: Configure CORS

Allow the frontend domain to call the API.

Tool: manage_app with action: "update_cors"

{
  "app_id": "app_abc123",
  "allowed_origins": [
    "http://localhost:3000",
    "https://my-app.pages.dev"
  ]
}

Add both local dev and production URLs. Update again after you know the final deployment URL.

Step 2: Set Frontend Environment Variables

Tool: manage_frontend with action: "set_env"

{
  "app_id": "app_abc123",
  "vars": {
    "VITE_API_BASE": "https://api.butterbase.ai/v1/app_abc123",
    "VITE_APP_ID": "app_abc123"
  }
}

Prefix variables for your framework:

  • Vite: VITE_
  • Next.js: NEXT_PUBLIC_
  • Create React App: REACT_APP_

Step 3: Build the Frontend

npm run build

This produces a dist/ (Vite) or out/ (Next.js static) folder.

Step 4: Create Deployment & Get Upload URL

Tool: create_frontend_deployment

{
  "app_id": "app_abc123",
  "framework": "react-vite"
}

Returns:

{
  "deployment_id": "dep_xyz789",
  "uploadUrl": "https://s3.amazonaws.com/...",
  "expiresIn": 900
}

Save deployment_id and uploadUrl.

Step 5: Zip and Upload

⚠️ Windows warning: Use Git Bash or WSL to create the zip. Windows built-in zip uses backslashes, which breaks MIME types and causes JS/CSS to be served as text/html.

# From the project root — use Git Bash on Windows
cd dist && zip -r ../frontend.zip . && cd ..

Upload the zip:

curl -X PUT "https://s3.amazonaws.com/..." \
  -H "Content-Type: application/zip" \
  --data-binary @frontend.zip

Replace the URL with the uploadUrl returned in the previous step.

Step 6: Start the Deployment

Tool: manage_frontend with action: "start_deployment"

{
  "app_id": "app_abc123",
  "deployment_id": "dep_xyz789"
}

This polls until the deployment reaches READY status (up to 5 minutes) and returns the live URL.

{
  "deployment_id": "dep_xyz789",
  "url": "https://my-app.pages.dev",
  "status": "READY"
}

Visit the URL to verify the frontend is live.

Step 7: Update CORS with Final URL

Once you have the live deployment URL, add it to CORS if it wasn't already included:

{
  "app_id": "app_abc123",
  "allowed_origins": [
    "http://localhost:3000",
    "https://my-app.pages.dev"
  ]
}

Framework Reference

FrameworkBuild commandZip folderframework param
React + Vitenpm run builddist/react-vite
Next.js (static)next build && next exportout/nextjs-static
Plain HTML/CSS/JSn/arootstatic
Othervariesbuild outputother

Phase 7: Production Checklist

Before announcing the app as production-ready, verify each item:

  • 1. CORS configured for production domain — update_cors includes the live frontend URL (not just localhost)
  • 2. RLS enabled on all user-data tables — manage_rls (action: "list") shows policies for every table holding user-generated content; no table is accidentally wide-open
  • 3. OAuth redirect URIs point to production — Provider developer consoles have the Butterbase callback URL registered; no localhost URIs are the only option in production
  • 4. Frontend env vars set for production API URL — VITE_API_BASE (or equivalent) points to https://api.butterbase.ai/v1/{app_id}, not a localhost URL
  • 5. Error handling in all functions — Every deploy_function handler returns appropriate HTTP status codes (400 for bad input, 401 for auth failures, 500 for unexpected errors) rather than throwing unhandled exceptions
  • 6. JWT config reviewed — manage_auth_config (action: "update_jwt") has been called with intentional token lifetimes; access token TTL is appropriate for the security sensitivity of the app (default 15m is reasonable)
  • 7. Storage quotas checked — manage_storage (action: "list") and app config reviewed; storage usage is within plan limits and allowedContentTypes are restricted to what the app actually needs
  • 8. Functions tested with invoke_function — Every HTTP function has been invoked with realistic payloads and edge cases (missing fields, invalid auth, large inputs) and returned correct responses
  • 9. Frontend deployed and verified — manage_frontend (action: "list_deployments") shows a READY deployment; the live URL loads correctly in a browser and all API calls succeed
  • 10. Monitoring and audit logs reviewed — query_audit_logs shows no unexpected login failures or suspicious activity; manage_function (action: "get_logs") shows no recurring errors in production traffic

Quick Reference: Tool → Phase Mapping

PhaseTools Used
1 — Create Appinit_app, manage_auth_config (generate_service_key)
2 — Schemamanage_schema (dry_run, apply, get)
3 — RLSmanage_rls (create_user_isolation, create_policy, enable, list), select_rows, insert_row
4 — Authmanage_oauth (configure), manage_auth_config (update_jwt)
5 — Functionsdeploy_function, invoke_function, manage_function (update_env, get_logs, list, delete)
6 — Frontendmanage_app (update_cors), manage_frontend (set_env, start_deployment, list_deployments), create_frontend_deployment
7 — Productionmanage_rls (list), query_audit_logs, manage_function (get_logs), manage_storage (list)

Common Mistakes to Avoid

Schema

  • Do not drop and recreate tables to rename a column — use manage_schema (action: "apply") with the new column name and migrate data separately
  • Do not skip action: "dry_run" — always preview before applying

RLS

  • Do not forget manage_rls (action: "create_user_isolation") — a table without RLS is readable by all authenticated users
  • Do not include author_id / user_id in INSERT bodies when a trigger is installed — it will be set automatically

Functions

  • Do not return plain objects from handlers (return { status: 200 }) — always return new Response(...)
  • Do not hardcode secrets in function code — use envVars and access via ctx.env

Frontend

  • Do not create zips on Windows with the built-in tool — use Git Bash or WSL
  • Do not forget to update CORS after deploying to the final URL

Auth

  • Do not register localhost redirect URIs in the production OAuth app — keep dev and prod OAuth apps separate, or add both URIs to the same app

Sponsors

SnugThe clipboard manager you and your agent can use, safelyAgent CaffeineClose your lid, come back to finished workWaxBlock anything with supervision, without erasing your iPhoneOpen StepsSponsoredFree and open-source skills that make AI coding agents easier to understand, verify, and control.

Details

Repository
github.com/butterbase-ai/butterbase-skills
Categories
Backend & APIs, DevOps & CI/CD, Automation & Workflows, Design & UI/UX, Databases
License
MIT
First seen
Jun 11, 2026

More from butterbase-ai/butterbase-skills

All 39
  • Butterbase Skills
  • Contributing
  • Debug Rls
  • Deploy Frontend
  • Durable Objects
  • Function Dev
  • Integrations
  • Journey
  • Journey Agents
  • Journey Ai
  • Journey Auth
  • Journey Deploy
  • Journey Docs
  • Journey Durable
  • Journey Frontend
  • Journey Functions
  • Journey Idea
  • Journey Plan
  • Journey Preflight
  • Journey Rag
  • Journey Realtime
  • Journey Rls
  • Journey Schema
  • Journey Storage

Recommended

More Backend & APIs
1butterbase-ai avatarbutterbase-skillsbutterbase-ai/butterbase-skillsClaude Code plugin for Butterbase — 30+ guided skills and auto-configured MCP for the AI-native backend-as-a-service.0installs
2butterbase-ai avatarcontributingbutterbase-ai/butterbase-skillsThis is the contributor's handbook for the Butterbase monorepo, which spans CLI tools, SDKs, MCP servers, and API services.0installs
3butterbase-ai avatarjourney-deploybutterbase-ai/butterbase-skillsUse as the deploy-verification stage of the Butterbase journey, after journey-frontend (or after any build stage if there is no frontend). Smoke-tests the deplo0installs
4butterbase-ai avatarjourney-frontendbutterbase-ai/butterbase-skillsUse as the frontend build stage of the Butterbase journey. Implements the Frontend section of 02-plan.md by delegating to deploy-frontend. Scaffolds (if needed)0installs
5butterbase-ai avatarjourney-planbutterbase-ai/butterbase-skillsUse as stage 2 of the Butterbase journey, after journey-idea has written 01-idea.md. Translates the idea + capability map into a concrete Butterbase plan — tabl0installs
6butterbase-ai avatarjourney-realtimebutterbase-ai/butterbase-skillsUse as the realtime build stage of the Butterbase journey. Implements the Realtime section of 02-plan.md by delegating to the realtime skill. Calls manage_realt0installs