DEV Community

Cover image for OpenClaw + Supabase: Complete Integration Guide
Clamper ai
Clamper ai

Posted on • Originally published at clamper.tech

OpenClaw + Supabase: Complete Integration Guide

AI agents are great at reasoning. They're terrible at remembering things across sessions, managing user accounts, or storing files reliably. That's where Supabase comes in — an open-source backend that gives your OpenClaw agent a proper database, authentication system, real-time subscriptions, and file storage. This guide walks you through connecting them end to end.

Why Supabase + OpenClaw?

Most AI agent tutorials gloss over persistence. They show you how to make an agent that can answer questions, maybe call an API, then the session ends and everything evaporates. Real applications need more than that.

Consider what a production agent actually needs:

  • Persistent storage — conversation history, user preferences, task queues that survive restarts
  • Authentication — knowing which user is talking, managing permissions, securing API endpoints
  • Real-time updates — reacting to database changes, live dashboards, collaborative features
  • File management — storing generated documents, images, exports without littering the filesystem

Supabase wraps PostgreSQL, GoTrue auth, real-time WebSockets, and S3-compatible storage into a single SDK. OpenClaw gives your agent tools, memory, and autonomy. Together, they turn a chatbot into an application backend.

Prerequisites

Before you start, you'll need:

  • A running OpenClaw instance (local or remote)
  • A Supabase project — free tier works fine for development
  • Your Supabase URL and anon/service role keys (found in Project Settings → API)
  • Node.js 18+ for the Supabase JavaScript client
# Install the Supabase client
npm install @supabase/supabase-js

# Or if your skill uses Python
pip install supabase
Enter fullscreen mode Exit fullscreen mode

Architecture Overview

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   User / Chat   │────▶│  OpenClaw Agent  │────▶│    Supabase     │
│   (Telegram,    │     │  (Skills, Tools, │     │  (PostgreSQL,   │
│    Discord)     │◀────│   Memory)        │◀────│   Auth, RT,     │
└─────────────────┘     └──────────────────┘     │   Storage)      │
                                                  └─────────────────┘

Flow:
1. User sends message → OpenClaw processes intent
2. Agent calls Supabase skill for data operations
3. Results flow back through agent to user
4. Real-time subscriptions push updates proactively
Enter fullscreen mode Exit fullscreen mode

Your OpenClaw agent acts as the intelligence layer. Supabase acts as the persistence layer. The agent decides what to store and retrieve; Supabase handles how it's stored securely.

Step 1: Create a Supabase Skill

The cleanest approach is building a dedicated OpenClaw skill that wraps common Supabase operations. Create the skill directory structure:

skills/
  supabase-connector/
    SKILL.md
    supabase-client.js
    package.json
Enter fullscreen mode Exit fullscreen mode

Here's the SKILL.md that tells your agent when and how to use this skill:

---
name: supabase-connector
description: "Read and write data to Supabase (PostgreSQL, Auth, Storage)."
---

# Supabase Connector

## Environment Variables
- SUPABASE_URL: Your project URL
- SUPABASE_KEY: Service role key (for server-side operations)

## Available Operations
- query: Run SELECT queries against any table
- insert: Insert rows into a table
- update: Update rows matching a filter
- delete: Remove rows matching a filter
- auth: Create/verify users
- storage: Upload/download files
Enter fullscreen mode Exit fullscreen mode

Step 2: Build the Client Wrapper

The client wrapper translates CLI arguments into Supabase SDK calls:

import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_KEY
);

const [operation, ...args] = process.argv.slice(2);

async function run() {
  switch (operation) {
    case "query": {
      const [table, filter] = args;
      let query = supabase.from(table).select("*");
      if (filter) {
        const parsed = JSON.parse(filter);
        for (const [col, val] of Object.entries(parsed)) {
          query = query.eq(col, val);
        }
      }
      const { data, error } = await query;
      if (error) throw error;
      console.log(JSON.stringify(data, null, 2));
      break;
    }

    case "insert": {
      const [table, jsonData] = args;
      const rows = JSON.parse(jsonData);
      const { data, error } = await supabase
        .from(table)
        .insert(rows)
        .select();
      if (error) throw error;
      console.log(JSON.stringify(data, null, 2));
      break;
    }

    case "update": {
      const [table, jsonData, filter] = args;
      const updates = JSON.parse(jsonData);
      const filters = JSON.parse(filter);
      let query = supabase.from(table).update(updates);
      for (const [col, val] of Object.entries(filters)) {
        query = query.eq(col, val);
      }
      const { data, error } = await query.select();
      if (error) throw error;
      console.log(JSON.stringify(data, null, 2));
      break;
    }

    case "rpc": {
      const [funcName, params] = args;
      const { data, error } = await supabase
        .rpc(funcName, params ? JSON.parse(params) : {});
      if (error) throw error;
      console.log(JSON.stringify(data, null, 2));
      break;
    }

    default:
      console.error("Unknown operation:", operation);
      process.exit(1);
  }
}

run().catch(err => {
  console.error(err.message);
  process.exit(1);
});
Enter fullscreen mode Exit fullscreen mode

Step 3: Database Schema Design

Here's a practical starting point that covers most agent use cases:

-- Conversation memory (persistent across sessions)
CREATE TABLE conversations (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id TEXT NOT NULL,
  channel TEXT NOT NULL,
  role TEXT NOT NULL CHECK (role IN ("user", "assistant", "system")),
  content TEXT NOT NULL,
  metadata JSONB DEFAULT "{}",
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Task queue (for async agent jobs)
CREATE TABLE tasks (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  title TEXT NOT NULL,
  status TEXT DEFAULT "pending"
    CHECK (status IN ("pending", "running", "completed", "failed")),
  payload JSONB DEFAULT "{}",
  result JSONB,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  completed_at TIMESTAMPTZ
);

-- Key-value store (agent preferences, config)
CREATE TABLE kv_store (
  key TEXT PRIMARY KEY,
  value JSONB NOT NULL,
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Enable Row Level Security
ALTER TABLE conversations ENABLE ROW LEVEL SECURITY;
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

-- Indexes for performance
CREATE INDEX idx_conversations_user ON conversations(user_id, created_at DESC);
CREATE INDEX idx_tasks_status ON tasks(status, created_at DESC);
Enter fullscreen mode Exit fullscreen mode

This gives your agent searchable conversation history, a persistent task queue, and a flexible key-value store.

Step 4: Authentication Flow

If your agent serves multiple users, wire up Supabase Auth:

async function signUp(email, password) {
  const { data, error } = await supabase.auth.signUp({ email, password });
  if (error) throw error;
  return data.user;
}

async function signIn(email, password) {
  const { data, error } = await supabase.auth.signInWithPassword({
    email, password,
  });
  if (error) throw error;
  return { user: data.user, session: data.session };
}
Enter fullscreen mode Exit fullscreen mode

Use the service role key for agent-side operations and the anon key for client-side dashboard operations. Row Level Security ensures users only see their own data.

Step 5: Real-Time Subscriptions

Supabase real-time lets your agent react to database changes without polling:

const subscription = supabase
  .channel("task-changes")
  .on(
    "postgres_changes",
    {
      event: "INSERT",
      schema: "public",
      table: "tasks",
      filter: "status=eq.pending",
    },
    (payload) => {
      console.log("New task:", payload.new);
      processTask(payload.new);
    }
  )
  .subscribe();
Enter fullscreen mode Exit fullscreen mode

A web dashboard inserts a row → your agent picks it up instantly → the user sees real-time updates. No polling. No cron jobs.

Step 6: File Storage

Use Supabase Storage (S3-compatible) for agent-generated files:

async function uploadFile(bucket, filePath, fileBuffer, contentType) {
  const { data, error } = await supabase.storage
    .from(bucket)
    .upload(filePath, fileBuffer, { contentType, upsert: true });
  if (error) throw error;

  const { data: urlData } = supabase.storage
    .from(bucket)
    .getPublicUrl(filePath);

  return urlData.publicUrl;
}
Enter fullscreen mode Exit fullscreen mode

Practical Example: Support Ticket System

Let's tie it together. Users submit tickets through a web form, and your OpenClaw agent automatically triages, responds, and resolves them:

CREATE TABLE tickets (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_email TEXT NOT NULL,
  subject TEXT NOT NULL,
  body TEXT NOT NULL,
  priority TEXT DEFAULT "medium",
  status TEXT DEFAULT "open",
  agent_response TEXT,
  resolved_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

ALTER PUBLICATION supabase_realtime ADD TABLE tickets;
Enter fullscreen mode Exit fullscreen mode
async function triageTicket(ticket) {
  const priority = await classifyPriority(ticket.subject, ticket.body);
  const response = await generateResponse(ticket);

  await supabase
    .from("tickets")
    .update({
      priority,
      agent_response: response,
      status: priority === "critical" ? "escalated" : "responded",
    })
    .eq("id", ticket.id);

  if (priority === "critical") {
    await notifyHuman(ticket, response);
  }
}

supabase
  .channel("new-tickets")
  .on("postgres_changes", {
    event: "INSERT",
    schema: "public",
    table: "tickets",
  }, (payload) => triageTicket(payload.new))
  .subscribe();
Enter fullscreen mode Exit fullscreen mode

Within minutes you have automated support. Tickets arrive → agent triages instantly → responds to straightforward ones → escalates critical issues.

Performance Tips

  • Connection pooling: Use PgBouncer (port 6543) for high-throughput operations
  • Batch inserts: Insert 10-50 rows at once instead of one per message
  • Index your queries: Composite indexes for frequent user_id + date range searches
  • Use RPC: Write PostgreSQL functions for complex logic, call via supabase.rpc()
  • Rate limits: Free tier has API call limits; upgrade to Pro for production agents
  • Service role key security: Never expose in client-side code

How Clamper Makes This Easier

Clamper includes pre-built skills and templates that handle the boilerplate:

  • OAuth flow management for Google Sheets, Docs, and other APIs alongside Supabase
  • Cost tracking to monitor API calls and LLM tokens through the dashboard
  • Skill marketplace on ClawHub for community-built Supabase skills
  • Memory sync using Supabase as a persistence backend across machines

Conclusion

Supabase and OpenClaw are a natural pairing. One provides intelligence and autonomy; the other provides persistence and infrastructure. Together, they let you build AI-powered applications that are production-ready — with proper auth, real-time updates, and data that survives session restarts.

Start with the basic schema, add the client wrapper as a skill, and iterate. The real power shows up with real-time subscriptions — that's when your agent stops being a chatbot and starts being a system.

FAQ

Can I use the Supabase free tier?
Yes. 500MB database, 1GB file storage, 50K monthly active users. Plenty for personal agents.

JavaScript or Python client?
OpenClaw runs on Node.js, so @supabase/supabase-js is natural. Use Python only if your skill already has Python deps.

How do I handle credentials securely?
Environment variables, never in skill files. The service role key bypasses Row Level Security.

Can I self-host Supabase?
Absolutely. Run it on the same machine via Docker Compose for zero-latency access. Takes about 20 minutes.

What about Edge Functions?
Great for webhooks and lightweight triggers. Let your OpenClaw agent handle the complex reasoning.

Top comments (0)