Free & open source — no account required
Free & open source — no account required
This technical guide provides an in-depth analysis of the json to supabase type engine, best practices for implementation, and data security standards.
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.
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>;
};
}
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;
}
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 });
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
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
});
createClient<Database>: Passing no generic means every .from() call returns any. Always pass the Database type to the client constructor.Json for JSONB columns, which is essentially unknown. Always narrow or cast JSONB values to a specific interface after selecting.null, not a type error..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.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.
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.