Free & open source — no account required

v1.2.5-PRICING-19
Web & Frontend • Engineering Documentation

Supabase Mastery: Syncing Frontend and Backend Types

This technical guide provides an in-depth analysis of the json to supabase type engine, best practices for implementation, and data security standards.

JSON to Supabase Types: Type-Safe Database Access with supabase-js

Supabase generates a Database type from your schema that supabase-js uses to infer types on every query. But when you're integrating a third-party API, working with Edge Functions, or scaffolding a new table from a JSON response, you often need to define those shapes manually before running supabase gen types. Generating TypeScript types from a JSON API response gets you a typed starting point you can refine into a proper Supabase table definition.

The Supabase Database Type Pattern

Supabase expects a specific Database interface shape that supabase-js reads to infer query types. Understanding this pattern lets you write typed code before the generated types exist:

// The shape Supabase generates via `supabase gen types typescript`
export interface Database {
  public: {
    Tables: {
      products: {
        Row: {                       // What SELECT returns
          id:          number;
          sku:         string;
          name:        string;
          price_cents: number;
          in_stock:    boolean;
          created_at:  string;
        };
        Insert: {                    // What INSERT accepts
          id?:         never;        // auto-generated
          sku:         string;
          name:        string;
          price_cents: number;
          in_stock?:   boolean;      // has a DEFAULT
          created_at?: never;        // auto-generated
        };
        Update: {                    // What UPDATE accepts
          sku?:        string;
          name?:       string;
          price_cents?: number;
          in_stock?:   boolean;
        };
      };
    };
    Views: Record<string, never>;
    Functions: Record<string, never>;
    Enums: Record<string, never>;
  };
}

From JSON API Response to Supabase Table Types

Say you're integrating a product catalog API. The response JSON becomes both the Row type and the blueprint for your Supabase table:

// API response JSON
{
  "id": "prod_01HX8Y3N",
  "sku": "WGT-001",
  "name": "Widget Pro",
  "price_cents": 2999,
  "tags": ["hardware", "tool"],
  "metadata": { "weight_g": 250, "fragile": false },
  "created_at": "2026-03-01T09:00:00Z"
}

// Generated Supabase Row type
export type ProductRow = Database['public']['Tables']['products']['Row'];

// Which expands to:
interface ProductRow {
  id:          string;
  sku:         string;
  name:        string;
  price_cents: number;
  tags:        string[];
  metadata:    { weight_g: number; fragile: boolean };
  created_at:  string;
}

Type-Safe Queries with the Generated Types

import { createClient } from '@supabase/supabase-js';
import type { Database } from './database.types';

const supabase = createClient<Database>(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
);

// supabase-js infers the return type from your Database interface
const { data: products, error } = await supabase
  .from('products')
  .select('id, sku, name, price_cents')
  .eq('in_stock', true)
  .order('name');

// products is typed as Array<Pick<ProductRow, 'id' | 'sku' | 'name' | 'price_cents'>> | null
if (products) {
  products.forEach(p => console.log(p.price_cents)); // ✅ typed as number
}

// Insert is also typed — TypeScript rejects missing required fields
const { error: insertError } = await supabase
  .from('products')
  .insert({ sku: 'NEW-001', name: 'New Widget', price_cents: 1999 });

Handling JSON / JSONB Columns in Supabase

Supabase stores JSON columns as Json (their utility type). Override the generated type for better safety:

// Supabase generates this for a JSONB column:
metadata: Json;  // Json = string | number | boolean | null | { [key: string]: Json } | Json[]

// Override it in a helper type for precise safety:
export interface ProductMetadata {
  weight_g:  number;
  fragile:   boolean;
  origin?:   string;
}

// Type-safe accessor
export type ProductRowTyped = Omit<ProductRow, 'metadata'> & {
  metadata: ProductMetadata;
};

// When selecting and asserting shape
const { data } = await supabase.from('products').select('*').single();
const product = data as ProductRowTyped; // after runtime validation

Supabase Edge Function Payload Types

Edge Functions receive a Request object. Type the expected body using the same pattern:

// supabase/functions/create-order/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { z } from 'https://deno.land/x/zod/mod.ts';

const CreateOrderSchema = z.object({
  customer_id: z.string().uuid(),
  items: z.array(z.object({
    product_id: z.string(),
    quantity:   z.number().int().positive(),
  })).min(1),
  shipping_address: z.object({
    line1:   z.string(),
    city:    z.string(),
    country: z.string().length(2),
  }),
});

type CreateOrderPayload = z.infer<typeof CreateOrderSchema>;

serve(async (req: Request) => {
  const body = await req.json();
  const result = CreateOrderSchema.safeParse(body);

  if (!result.success) {
    return new Response(JSON.stringify({ error: result.error.flatten() }), {
      status: 400,
      headers: { 'Content-Type': 'application/json' },
    });
  }

  const payload: CreateOrderPayload = result.data;
  // ... process order
});

Common Pitfalls

  • Not using createClient<Database>: Passing no generic means every .from() call returns any. Always pass the Database type to the client constructor.
  • Trusting generated types for JSONB: Supabase gen types produces Json for JSONB columns, which is essentially unknown. Always narrow or cast JSONB values to a specific interface after selecting.
  • Row Level Security and TypeScript: TypeScript types don't enforce RLS — they reflect the table shape, not what a given user can see. A missing row returns null, not a type error.
  • Using .select('*') for narrow types: When you only need a few columns, use .select('id, name'). Supabase-js infers the narrower type automatically, so you don't need a separate Pick.

FAQ

Q: When should I use the generated types vs hand-writing them?
A: Run supabase gen types typescript as part of your CI pipeline so types always match the live schema. Hand-write types only for tables that don't exist yet or for JSONB column sub-shapes that the generator can't infer.

Q: How do I type a Supabase RPC call?
A: Add the function to the Functions section of your Database interface: Functions: { my_fn: { Args: { p_user_id: string }; Returns: number } }. Then supabase.rpc('my_fn', { p_user_id: '...' }) will be typed.

Q: Can I reuse Supabase Row types in my API response types?
A: Yes, but consider defining a separate public-facing type using Omit to exclude sensitive columns. Returning the raw Row type risks exposing fields you didn't intend to include in API responses.

Q: How do I handle Supabase realtime subscription types?
A: The payload in a realtime callback is typed as { new: Row; old: Row; eventType: 'INSERT' | 'UPDATE' | 'DELETE' } when you pass the Database generic to the client. You get full type inference on the row shape inside the callback.

Developer FAQ

Is the processing local-only?

Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.

Can I use this for enterprise projects?

Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.