← Back to Fidra

Cloud Setup Guide

Connect Fidra to a Supabase cloud backend for multi-user access and real-time sync.

Create Project Database Storage Connect How It Works

Create a Supabase Project

Fidra uses Supabase as its cloud backend — a hosted PostgreSQL database with file storage.

  1. Go to supabase.com and sign in (or create a free account)
  2. Click New Project
  3. Enter a project name (e.g., "Fidra Finance")
  4. Set a database password — save this, you'll need it later
  5. Select a region close to your users
  6. Click Create new project
Tip: The free tier is sufficient for most small teams. You get 500MB database storage and 1GB file storage.

Wait for the project to be provisioned (usually 1–2 minutes), then continue to the next step.

Set Up the Database

Fidra needs six tables in your Supabase database. Open the SQL Editor in your Supabase dashboard, create a new query, paste the SQL below, and click Run.

Full SQL schema
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Transactions table
CREATE TABLE transactions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    date DATE NOT NULL,
    description TEXT NOT NULL,
    amount NUMERIC(15, 2) NOT NULL CHECK (amount > 0),
    type TEXT NOT NULL CHECK (type IN ('income', 'expense')),
    status TEXT NOT NULL CHECK (status IN ('--', 'pending', 'approved', 'rejected', 'planned')),
    sheet TEXT NOT NULL,
    category TEXT,
    party TEXT,
    notes TEXT,
    reference TEXT,
    activity TEXT,
    version INTEGER NOT NULL DEFAULT 1,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    modified_at TIMESTAMPTZ,
    modified_by TEXT
);

CREATE INDEX idx_transactions_date ON transactions(date DESC);
CREATE INDEX idx_transactions_sheet ON transactions(sheet);
CREATE INDEX idx_transactions_type ON transactions(type);
CREATE INDEX idx_transactions_status ON transactions(status);

-- Planned templates table
CREATE TABLE planned_templates (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    start_date DATE NOT NULL,
    description TEXT NOT NULL,
    amount NUMERIC(15, 2) NOT NULL CHECK (amount > 0),
    type TEXT NOT NULL CHECK (type IN ('income', 'expense')),
    frequency TEXT NOT NULL CHECK (frequency IN ('once', 'weekly', 'biweekly', 'monthly', 'quarterly', 'yearly')),
    target_sheet TEXT NOT NULL,
    category TEXT,
    party TEXT,
    activity TEXT,
    end_date DATE,
    occurrence_count INTEGER,
    skipped_dates JSONB DEFAULT '[]'::jsonb,
    fulfilled_dates JSONB DEFAULT '[]'::jsonb,
    version INTEGER NOT NULL DEFAULT 1,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_planned_start ON planned_templates(start_date);
CREATE INDEX idx_planned_target ON planned_templates(target_sheet);

-- Sheets table
CREATE TABLE sheets (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name TEXT UNIQUE NOT NULL,
    is_virtual BOOLEAN DEFAULT FALSE,
    is_planned BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_sheets_name ON sheets(name);

-- Attachments table (metadata only - files in Supabase Storage)
CREATE TABLE attachments (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    transaction_id UUID NOT NULL REFERENCES transactions(id) ON DELETE CASCADE,
    filename TEXT NOT NULL,
    stored_name TEXT NOT NULL,
    mime_type TEXT,
    file_size BIGINT DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_attachments_transaction ON attachments(transaction_id);

-- Audit log table
CREATE TABLE audit_log (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    action TEXT NOT NULL CHECK (action IN ('create', 'update', 'delete')),
    entity_type TEXT NOT NULL,
    entity_id UUID NOT NULL,
    "user" TEXT NOT NULL,
    summary TEXT NOT NULL,
    details TEXT
);

CREATE INDEX idx_audit_timestamp ON audit_log(timestamp DESC);
CREATE INDEX idx_audit_entity ON audit_log(entity_type, entity_id);

-- Categories table
CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    type TEXT NOT NULL CHECK (type IN ('income', 'expense')),
    name TEXT NOT NULL,
    sort_order INTEGER DEFAULT 0,
    UNIQUE(type, name)
);

CREATE INDEX idx_categories_type ON categories(type);

-- Row Level Security (enable for multi-user)
ALTER TABLE transactions ENABLE ROW LEVEL SECURITY;
ALTER TABLE planned_templates ENABLE ROW LEVEL SECURITY;
ALTER TABLE sheets ENABLE ROW LEVEL SECURITY;
ALTER TABLE attachments ENABLE ROW LEVEL SECURITY;
ALTER TABLE audit_log ENABLE ROW LEVEL SECURITY;
ALTER TABLE categories ENABLE ROW LEVEL SECURITY;

-- Policies: Allow all authenticated users full access
CREATE POLICY "Full access" ON transactions FOR ALL USING (true);
CREATE POLICY "Full access" ON planned_templates FOR ALL USING (true);
CREATE POLICY "Full access" ON sheets FOR ALL USING (true);
CREATE POLICY "Full access" ON attachments FOR ALL USING (true);
CREATE POLICY "Full access" ON audit_log FOR ALL USING (true);
CREATE POLICY "Full access" ON categories FOR ALL USING (true);

The query creates all tables, indexes, and row-level security policies. You should see "Success" in the Supabase SQL editor.

Set Up File Storage

Fidra stores receipt attachments in Supabase Storage. You need to create a bucket and set access policies.

Create the bucket

  1. In your Supabase dashboard, go to Storage (left sidebar)
  2. Click New bucket
  3. Name: attachments
  4. Leave "Public bucket" unchecked
  5. Click Create bucket

Add access policies

Click on the attachments bucket, go to the Policies tab, and create these three policies:

Policy 1: Allow Uploads (INSERT)
  1. Click New policyFor full customization
  2. Policy name: Allow uploads
  3. Allowed operation: INSERT
  4. Target roles: anon
  5. Policy definition: true
  6. Click Review then Save policy
Policy 2: Allow Downloads (SELECT)
  1. Click New policyFor full customization
  2. Policy name: Allow downloads
  3. Allowed operation: SELECT
  4. Target roles: anon
  5. Policy definition: true
  6. Click Review then Save policy
Policy 3: Allow Deletes (DELETE)
  1. Click New policyFor full customization
  2. Policy name: Allow deletes
  3. Allowed operation: DELETE
  4. Target roles: anon
  5. Policy definition: true
  6. Click Review then Save policy
Note: When creating the DELETE policy, Supabase may require you to also select SELECT. This is fine.

Connect Fidra to Supabase

Gather three pieces of information from your Supabase dashboard, then enter them in Fidra.

1. Get your connection details

Database Connection String

Click Connect (top bar) → Connection string tab → set Type = URI, Source = Primary Database, Method = Transaction Pooler. Copy the string and replace [YOUR-PASSWORD] with your database password.

postgresql://postgres.xxxx:[YOUR-PASSWORD]@aws-0-xx-xxxx.pooler.supabase.com:6543/postgres

Project URL & Anon Key

Go to Project Settings (gear icon) → API. Copy the Project URL and anon public key.

2. Enter them in Fidra

This is what the connection form looks like in the app:

Add Cloud Server
Sub Aqua Club
postgresql://postgres.abc:••••••@aws-0-eu.pooler.supabase.com:6543/postgres
https://abcdefghij.supabase.co
eyJhbGciOiJIUzI1NiIsInR5cCI6••••••
attachments
Test Connection Save

You can access this form from the Setup Wizard (first launch), the Welcome screen, or from Settings → Cloud Servers.

How Sync Works

Once connected, Fidra uses an offline-first architecture. Everything stays fast regardless of network conditions.

Local cache

All data cached in a local SQLite database. Reads are always instant — even offline.

Background sync

Changes queue locally and push to PostgreSQL within ~1 second. If offline, they sync when connectivity returns.

Real-time updates

Changes from other devices arrive via PostgreSQL LISTEN/NOTIFY. The UI updates automatically.

Connection recovery

  • Health checks run every 30 seconds
  • Lost connections retry with exponential backoff
  • The status bar shows connection state (green/yellow/red) with a manual reconnect button
  • No restart needed — Fidra reconnects automatically
Upgrading an Existing Database

If you set up your Supabase database before the reference and activity columns were added, you need to add them manually. Open the SQL Editor in your Supabase dashboard and run:

-- Add reference column (for bank statement matching)
ALTER TABLE transactions ADD COLUMN IF NOT EXISTS reference TEXT;

-- Add activity column (for project/activity tagging)
ALTER TABLE transactions ADD COLUMN IF NOT EXISTS activity TEXT;

-- Add activity column to planned templates
ALTER TABLE planned_templates ADD COLUMN IF NOT EXISTS activity TEXT;

These columns are optional (nullable TEXT) so existing data is unaffected.

Troubleshooting

"Bucket not found" error

Verify the bucket is named exactly attachments and that storage policies are configured.

"400 Bad Request" when uploading

Verify the INSERT policy exists on the storage bucket and the anon key is correct.

Connection timeout

Check the connection string, ensure you're using "Transaction Pooler" method, and verify the password (no [YOUR-PASSWORD] placeholder).

"Permission denied" errors

Verify RLS policies are created on all tables and the anon key has correct permissions.

Security Considerations

The default setup uses permissive policies (true) which allow any request full access. For production use with multiple organisations, consider:

  1. Row-Level Security — add user/organisation columns and filter by authenticated user
  2. Service Role Key — use for server-side operations only, never expose in client apps
  3. Custom Policies — restrict access based on user roles or organisation membership

For a single organisation with trusted users, the default policies are sufficient.