Budibase: Low-Code Platform That Actually Connects to My Existing Database

Our ops team was managing 200+ SKUs across three Google Sheets, a legacy Access database, and a MySQL inventory system. They needed a unified interface but the IT team had a 6-month backlog. I set up Budibase self-hosted, connected it to all three data sources, and built a working inventory management app in 4 hours. The ops team had something usable by end of day. Here's how it works and the issues I hit.

Self-Hosting That Actually Works

# Self-host Budibase with Docker Compose
git clone https://github.com/Budibase/budibase.git
cd budibase
docker compose up -d

# Or use the official Docker image directly
docker run -d \
  --name budibase \
  -p 10000:80 \
  -v budibase_data:/data \
  budibase/budibase:latest

# Access: http://localhost:10000
# Create admin account on first visit

Self-hosting matters because Budibase cloud has per-user pricing that gets expensive at scale. With 20+ internal users, the self-hosted version saves significant cost and keeps data on your own infrastructure.

Connecting to External Databases

Budibase connects to external databases as data sources. Your data stays in your database — Budibase reads and writes directly:

# Supported data sources:
# - PostgreSQL
# - MySQL
# - SQL Server
# - MongoDB
# - REST API
# - Google Sheets
# - S3 / Airtable / Snowflake

# In the Budibase UI:
# 1. Go to Data → Create Data Source
# 2. Select "PostgreSQL"
# 3. Enter connection details:
#    Host: db.example.com
#    Port: 5432
#    Database: inventory
#    User: budibase_readwrite
#    Password: ****

# 4. Budibase scans the schema and lists all tables
# 5. Select which tables to expose as "queries"
# 6. Each table becomes available in the app builder

Problem

Connected Budibase to a MySQL database with a 500K row orders table. Loading the list view took 30+ seconds. The connector was fetching all rows without LIMIT, and the join to the customers table multiplied the result set.

What I Tried

Tried setting a row limit in the Budibase query configuration. That helped but then users couldn't search beyond the first 1000 rows. Tried creating a database view with the join pre-computed — that worked but required DBA involvement.

Actual Fix

Use custom SQL queries instead of auto-generated ones. Budibase lets you write raw SQL with parameters:

-- Budibase custom query: search_orders
SELECT
  o.id,
  o.order_date,
  o.total,
  o.status,
  c.name AS customer_name
FROM orders o
INNER JOIN customers c ON o.customer_id = c.id
WHERE
  (:search = '' OR c.name LIKE CONCAT('%', :search, '%'))
  AND (:status = '' OR o.status = :status)
ORDER BY o.order_date DESC
LIMIT 50 OFFSET :offset

-- Parameters:
-- :search (string, default: "")
-- :status (string, default: "")
-- :offset (number, default: 0)

Building a CRUD App in 15 Minutes

The app builder is drag-and-drop. Here's the workflow for a typical internal tool:

  1. Create data source — connect to MySQL/PostgreSQL
  2. Auto-generate screens — Budibase creates list, create, edit, and detail views automatically
  3. Customize the layout — drag and drop components, change column order, add filters
  4. Add automation — "when status changes to 'shipped', send email"
  5. Set permissions — define who can see/edit what
  6. Publish — the app is live at a URL, accessible to your team

Automation Workflows

Budibase automations are the equivalent of Zapier but self-hosted and free. Here's what I set up for inventory management:

// Automation: Low stock alert
// Trigger: "On row update" in "inventory" table
// Condition: stock < reorder_level

// Step 1: Query the suppliers table for this product
const supplier = await fetchSupplier(record.product_id)

// Step 2: Send email to purchasing team
await sendEmail({
  to: "purchasing@company.com",
  subject: `Low stock: ${record.product_name}`,
  body: `${record.product_name} is at ${record.stock} units.
         Reorder level: ${record.reorder_level}
         Supplier: ${supplier.name} (${supplier.email})`
})

// Step 3: Create a task in the "reorder_tasks" table
await createRecord("reorder_tasks", {
  product_id: record.product_id,
  current_stock: record.stock,
  reorder_qty: record.reorder_qty,
  status: "pending",
  assigned_to: "purchasing_team"
})

// This automation runs automatically whenever inventory is updated

Problem

Configured Google OAuth for SSO. The login button appeared, users clicked it, authenticated with Google, but got redirected back to Budibase without being logged in. No error message.

What I Tried

Tried different redirect URLs. Checked the Google Cloud Console — the OAuth consent screen was configured. Tried using the user's Google email as their Budibase username.

Actual Fix

The issue was the redirect URI. For self-hosted Budibase behind a reverse proxy, the redirect URI needs to match exactly — including http vs https:

# In Google Cloud Console → OAuth 2.0 Client:
# Authorized redirect URIs must match EXACTLY
# For self-hosted behind nginx:
https://budibase.yourdomain.com/api/global/auth/_google/callback

# Common mistake: using http:// instead of https://
# when behind a reverse proxy that terminates SSL

# Also set in Budibase environment variables:
BB_GOOGLE_CLIENT_ID=your-client-id
BB_GOOGLE_CLIENT_SECRET=your-client-secret
BB_GOOGLE_REDIRECT_URL=https://budibase.yourdomain.com/api/global/auth/_google/callback
# The BB_GOOGLE_REDIRECT_URL must match the Google Console exactly

SSO with Google/Azure

Budibase supports multiple SSO providers out of the box:

After SSO is configured, users don't need separate Budibase accounts. Their Google/Azure identity maps to Budibase roles automatically.

What I Learned

Wrapping Up

Budibase replaced three separate tools (Google Sheets, Access, and a custom PHP app) with one unified interface. The ops team can now manage inventory from a single dashboard, the automation handles reorder alerts, and SSO means everyone logs in with their existing Google accounts. For teams that need internal tools without waiting months for developer time, Budibase is the answer.

Related Articles