# Insforge

> Use this skill when writing app code with InsForge or @insforge/sdk: database CRUD, auth, storage uploads/storage RLS, functions, OpenRouter AI, realtime, emails, Stripe checkout, subscriptions, customer portal flows, or pointing S3-compatible tooling (aws CLI, AWS SDKs, rclone, Terraform, boto3) at InsForge Storage. Trigger on requests like add auth, fetch data, upload files, make a bucket public, add checkout, sell subscriptions, or send email. For infrastructure, SQL migrations, CLI commands, or Stripe key/catalog setup, use insforge-cli instead.

- Skill: `maybackcompany/insforge` (Agent Skill, multi-file: 17 files)
- Install (CLI): `npx skillmds@latest add maybackcompany/insforge`
- Raw SKILL.md: https://api.skillmd.com/api/skills/maybackcompany/insforge/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- License: MIT
- Author: MaybackCompany (https://skillmd.com/u/maybackcompany)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/maybackcompany/insforge

---


# InsForge SDK Skill

This skill covers **client-side SDK integration** using `@insforge/sdk`. For backend infrastructure operations (creating tables, inspecting schema, deploying functions, secrets, managing storage buckets, configuring Stripe keys/catalog, website deployments, cron job and schedules, logs, etc.), use the **insforge-cli** skill.

## Quick Setup

### 1. Install the SDK

```bash
npm install @insforge/sdk@latest
```

### 2. Set up environment variables

Before using the SDK, create a `.env` file (or `.env.local` for Next.js) in your project root with your InsForge URL and anon key.

#### How to get your URL and anon key

1. **Ensure the project is linked.** Check for `.insforge/project.json` in the project root.
   - If it doesn't exist, run `npx @insforge/cli link` (existing project) or `npx @insforge/cli create` (new project) to generate it.

2. **Get the anon key** via the CLI:
   ```bash
   npx @insforge/cli secrets get ANON_KEY
   ```

3. **Get the URL** from the `oss_host` field in `.insforge/project.json` (e.g., `https://myapp.us-east.insforge.app`).

4. **Write both values** to the `.env` file using the correct framework prefix (see table below).

> **Important:** Use the anon key for SDK clients, including SSR. Use the API key only for privileged backend operations that need admin/service access; it is a full-access admin key, equivalent to a service role key on other platforms.

Use the correct environment variable prefix and access pattern for your framework:

| Framework | `.env` file | Variables | Access Pattern |
|-----------|-------------|-----------|----------------|
| **Next.js** | `.env.local` | `NEXT_PUBLIC_INSFORGE_URL`, `NEXT_PUBLIC_INSFORGE_ANON_KEY` | `process.env.NEXT_PUBLIC_*` |
| **Vite** (React, Vue, Svelte) | `.env` | `VITE_INSFORGE_URL`, `VITE_INSFORGE_ANON_KEY` | `import.meta.env.VITE_*` |
| **Astro** | `.env` | `PUBLIC_INSFORGE_URL`, `PUBLIC_INSFORGE_ANON_KEY` | `import.meta.env.PUBLIC_*` |
| **SvelteKit** | `.env` | `PUBLIC_INSFORGE_URL`, `PUBLIC_INSFORGE_ANON_KEY` | `import { env } from '$env/dynamic/public'` |
| **Create React App** | `.env` | `REACT_APP_INSFORGE_URL`, `REACT_APP_INSFORGE_ANON_KEY` | `process.env.REACT_APP_*` |
| **Node.js / Server** | `.env` | `INSFORGE_URL`, `INSFORGE_ANON_KEY` | `process.env.*` |

Example `.env.local` for Next.js:
```bash
NEXT_PUBLIC_INSFORGE_URL=https://your-appkey.us-east.insforge.app
NEXT_PUBLIC_INSFORGE_ANON_KEY=<your-anon-key>
```

> **Important:** Never commit `.env` files to version control. Add `.env`, `.env.local`, and `.env*.local` to your `.gitignore` (keep `.env.example` for documenting required variables).

### 3. Initialize the client

```javascript
import { createClient } from '@insforge/sdk'

// Next.js / CRA: use process.env
const insforge = createClient({
  baseUrl: process.env.NEXT_PUBLIC_INSFORGE_URL,
  anonKey: process.env.NEXT_PUBLIC_INSFORGE_ANON_KEY
})

// Vite / Astro: use import.meta.env
const insforge = createClient({
  baseUrl: import.meta.env.VITE_INSFORGE_URL,
  anonKey: import.meta.env.VITE_INSFORGE_ANON_KEY
})
```

## Module Reference

| Module | SDK Integration |
|--------|-----------------|
| **Database** | [database/sdk-integration.md](database/sdk-integration.md) |
| **Auth** | [auth/sdk-integration.md](auth/sdk-integration.md) |
| **Storage** | [storage/sdk-integration.md](storage/sdk-integration.md) |
| **Functions** | [functions/sdk-integration.md](functions/sdk-integration.md) |
| **AI** | [ai/sdk-integration.md](ai/sdk-integration.md) |
| **Real-time** | [realtime/sdk-integration.md](realtime/sdk-integration.md) |
| **Email** | [email/sdk-integration.md](email/sdk-integration.md) |
| **Payments** | [payments/sdk-integration.md](payments/sdk-integration.md) |

### What Each Module Covers

| Module | Content |
|--------|---------|
| **Database** | CRUD operations, filters, pagination, RPC calls |
| **Auth** | Sign up/in, OAuth, sessions, profiles, password reset |
| **Storage** | Upload, download, delete files; S3-compatible gateway for CI / backup tooling; write RLS policies for buckets |
| **Functions** | Invoke edge functions |
| **AI** | OpenRouter via OpenAI SDK, image/video generation, embeddings, deprecated InsForge SDK fallback |
| **Email** | Send custom transactional HTML emails (welcome, newsletter, notifications) |
| **Payments** | Stripe Checkout Sessions, subscriptions, and Billing Portal redirects |
| **Real-time** | Connect, subscribe, publish events, and track presence snapshots plus join/leave deltas |

### Guides

| Guide | When to Use |
|-------|-------------|
| [database/postgres-rls.md](database/postgres-rls.md) | Writing or reviewing RLS policies — covers infinite recursion prevention, `SECURITY DEFINER` patterns, performance tips, and common InsForge RLS patterns |
| [storage/s3-gateway.md](storage/s3-gateway.md) | Fallback path when the consumer is existing S3 tooling (aws CLI, AWS SDKs, rclone, Terraform, boto3) and adopting `@insforge/sdk` is impractical — covers endpoint/region setup, access-key management, path-style addressing, and supported vs. not-supported S3 operations. **Requires InsForge 2.0.9+.** **Prefer the SDK** ([storage/sdk-integration.md](storage/sdk-integration.md)) for app code |
| [storage/postgres-rls.md](storage/postgres-rls.md) | Writing RLS policies for `storage.objects` — owner-only, public-read, path-scoped, team-shared, and the `NULL uploaded_by` caveat for mixed REST + S3 buckets |
| [database/pgvector.md](database/pgvector.md) | Building semantic search, recommendations, or RAG — covers the `vector` extension, schema/dimensions, distance operators, HNSW/IVFFlat indexes, and RPC similarity search |
| [ai/embeddings-and-rag.md](ai/embeddings-and-rag.md) | Generating embeddings through OpenRouter, storing them in pgvector, and wiring up a basic RAG pipeline with chat completions |
| [payments/backend-configuration.md](payments/backend-configuration.md) | Configuring Stripe keys, syncing catalog, creating products/prices, webhooks, and portal RLS before app integration |

### Building Checkout for a New App

Before integrating payments, make sure a Stripe key is configured. Run `npx @insforge/cli payments status`. If it shows `unconfigured`, ask the user for the Stripe key first. See [payments/backend-configuration.md](payments/backend-configuration.md).

### Real-time Configuration

For real-time channels and database triggers, use SQL migrations or database admin tooling to configure channels, triggers, and policies. The real-time SDK is for frontend event handling and messaging, not backend configuration.

#### Create Database Triggers

Automatically publish events when database records change.

```sql
-- Create trigger function
CREATE OR REPLACE FUNCTION notify_order_changes()
RETURNS TRIGGER AS $$
BEGIN
  PERFORM realtime.publish(
    'order:' || NEW.id::text,    -- channel
    TG_OP || '_order',           -- event: INSERT_order, UPDATE_order
    jsonb_build_object(
      'id', NEW.id,
      'status', NEW.status,
      'total', NEW.total
    )
  );
  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Attach to table
CREATE TRIGGER order_realtime
  AFTER INSERT OR UPDATE ON orders
  FOR EACH ROW
  EXECUTE FUNCTION notify_order_changes();
```

#### Conditional Trigger (Status Changes Only)

```sql
CREATE OR REPLACE FUNCTION notify_order_status()
RETURNS TRIGGER AS $$
BEGIN
  PERFORM realtime.publish(
    'order:' || NEW.id::text,
    'status_changed',
    jsonb_build_object('id', NEW.id, 'status', NEW.status)
  );
  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER order_status_trigger
  AFTER UPDATE ON orders
  FOR EACH ROW
  WHEN (OLD.status IS DISTINCT FROM NEW.status)
  EXECUTE FUNCTION notify_order_status();
```

#### Access Control (RLS)

RLS is disabled by default. To restrict channel access:

- **Enable RLS**

```sql
ALTER TABLE realtime.channels ENABLE ROW LEVEL SECURITY;
ALTER TABLE realtime.messages ENABLE ROW LEVEL SECURITY;
```

- **Restrict Subscribe (SELECT on channels)**

```sql
CREATE POLICY "users_subscribe_own_orders"
ON realtime.channels FOR SELECT
TO authenticated
USING (
  pattern = 'order:%'
  AND EXISTS (
    SELECT 1 FROM orders
    WHERE id = NULLIF(split_part(realtime.channel_name(), ':', 2), '')::uuid
      AND user_id = auth.uid()
  )
);
```

- **Restrict Publish (INSERT on messages)**

```sql
CREATE POLICY "members_publish_chat"
ON realtime.messages FOR INSERT
TO authenticated
WITH CHECK (
  channel_name LIKE 'chat:%'
  AND EXISTS (
    SELECT 1 FROM chat_members
    WHERE room_id = NULLIF(split_part(channel_name, ':', 2), '')::uuid
      AND user_id = auth.uid()
  )
);
```

- **Quick Reference**

| Task | SQL |
|------|-----|
| Create channel | `INSERT INTO realtime.channels (pattern, description, enabled) VALUES (...)` |
| Create trigger | `CREATE TRIGGER ... EXECUTE FUNCTION ...` |
| Publish from SQL | `PERFORM realtime.publish(channel, event, payload)` |
| Enable RLS | `ALTER TABLE realtime.channels ENABLE ROW LEVEL SECURITY` |


#### Best Practices

1. **Create channel patterns first** before subscribing from frontend
   - Insert channel patterns into `realtime.channels` table
   - Ensure `enabled` is set to `true`

2. **Use specific channel patterns**
   - Use wildcard `%` patterns for dynamic channels (e.g., `order:%` for `order:123`)
   - Use exact patterns for global channels (e.g., `notifications`)

#### Common Mistakes

| Mistake | Solution |
|---------|----------|
| Subscribing to undefined channel pattern | Create channel pattern in `realtime.channels` first |
| Channel not receiving messages | Ensure channel `enabled` is `true` |
| Publishing without trigger | Create database trigger to auto-publish on changes |

#### Recommended Workflow

```text
1. Create channel patterns   → INSERT INTO realtime.channels
2. Ensure enabled = true     → Set enabled to true
3. Create triggers if needed → Auto-publish on database changes
4. Proceed with SDK subscribe → Use channel name matching pattern
```

### Backend Configuration (Not Yet in CLI)

These modules still require HTTP API calls because the CLI does not yet support them:

| Module | Backend Configuration |
|--------|----------------------|
| **AI** | [ai/backend-configuration.md](ai/backend-configuration.md) |

> Auth redirect URL allowlist is now managed via the CLI — use `npx @insforge/cli config export/plan/apply`. Other auth settings (password policy, OAuth providers, verification methods) still live on the dashboard. See the **insforge-cli** skill's Configuration section.

### Risky backend changes? Use a branch first

When a code change in this skill depends on a **schema migration**, **new RLS policy**, **OAuth provider config change**, or any other backend change that could brick prod, create a backend branch first instead of editing the live project. Branches share `JWT_SECRET` (existing user JWTs keep working) but get a fresh database + EC2 + `API_KEY` / `ANON_KEY`, so you can test the SDK + backend change end-to-end in isolation.

The full branching workflow lives in the **insforge-cli** skill — see [branch](../insforge-cli/references/branch.md) for the decision guide and lifecycle commands. Typical loop:

```bash
npx @insforge/cli branch create feat-x --mode schema-only
# ... apply migrations / change auth config / update RLS on the branch ...
# ... test the SDK against the branch backend ...
npx @insforge/cli branch merge feat-x --dry-run     # review SQL
npx @insforge/cli branch merge feat-x               # apply to parent
```

> ⚠ **After `branch create` or `branch switch`**, the SDK's `INSFORGE_URL` and `INSFORGE_ANON_KEY` change. **Restart your dev server** (or re-source `.env`) so the SDK starts talking to the branch backend. If you don't, the SDK will silently keep hitting parent — the #1 cause of "I switched but my changes aren't showing up".

## SDK Quick Reference

All SDK methods return `{ data, error }`.

| Module | Methods |
|--------|---------|
| `insforge.database` | `.from().select()`, `.insert()`, `.update()`, `.delete()`, `.rpc()` |
| `insforge.auth` | `.signUp()`, `.signInWithPassword()`, `.signInWithOAuth()`, `.signOut()`, `.getCurrentUser()` |
| `insforge.storage` | `.from().upload()`, `.uploadAuto()`, `.download()`, `.remove()` |
| `insforge.functions` | `.invoke()` |
| `insforge.ai` | Deprecated fallback only: `.chat.completions.create()`, `.images.generate()`, `.embeddings.create()` |
| `insforge.realtime` | `.connect()`, `.subscribe()`, `.publish()`, `.on()`, `.disconnect()` |
| `insforge.emails` | `.send({ to, subject, html, cc?, bcc?, from?, replyTo? })` |
| `insforge.payments` | `.createCheckoutSession()`, `.createCustomerPortalSession()` |

## Important Notes

- **Database inserts require array format**: `insert([{...}])` not `insert({...})`
- **Next.js / SSR auth**: Use `createClient({ isServerMode: true })`, keep tokens in httpOnly cookies, and perform auth flows on the server. See [auth/sdk-integration.md](auth/sdk-integration.md)
- **Storage**: Save both `url` AND `key` to database for download/delete operations
- **Functions invoke URL**: `/functions/{slug}` (without `/api` prefix)
- **Email — no SMTP, no third-party packages**: Auth emails (signup verification, password reset, magic links, invites) ship on **every plan**. Custom email via `insforge.emails.send()` ships on **every paid plan**. **Never install `nodemailer` / `resend` / `sendgrid` / `mailgun` / `postmark` or ask for SMTP credentials** — the platform handles delivery. Custom sender domain is dashboard config, not `package.json`. See [email/sdk-integration.md](email/sdk-integration.md).
- **Payments**: Configure Stripe keys/catalog with `npx @insforge/cli payments ...` first; frontend code only creates Checkout/Portal sessions.
- **Payment RLS**: Before subscription checkout or Billing Portal UI, add app-specific RLS on `payments.checkout_sessions` and `payments.customer_portal_sessions`. Checkout creation needs `INSERT`; checkout requests with `idempotencyKey` also need matching `SELECT` on `payments.checkout_sessions`.
- **Use Tailwind CSS v3.4** (do not upgrade to v4)
- **Always local build before deploy**: Prevents wasted build resources and faster debugging
- **Deprecated packages**: `@insforge/react`, `@insforge/nextjs`, and `@insforge/react-router` are **deprecated**. Do NOT install or use them. Use `@insforge/sdk` directly for all features including authentication.
- **Deployment**: Include a `vercel.json` in the project root for SPA routing (React, React Router apps). The `download-template` tool includes this automatically.
- **Branching for risky backend changes**: If your SDK code depends on a new schema, RLS policy, or auth config change, create a branch via `npx @insforge/cli branch create` first — see the **insforge-cli** skill's [branch](../insforge-cli/references/branch.md) reference. After `branch create` / `branch switch`, **restart the dev server** so the SDK picks up the new `INSFORGE_URL` / `INSFORGE_ANON_KEY`.

