
Supabase Type Mismatch: Why Are My Generated Types Out of Sync?
Added a column to DB, but frontend TS still complains? Learn how 'supabase gen types' works and how to automate type sync in your CI/CD pipeline.

Added a column to DB, but frontend TS still complains? Learn how 'supabase gen types' works and how to automate type sync in your CI/CD pipeline.
How to deploy without shutting down servers. Differences between Rolling, Canary, and Blue-Green. Deep dive into Database Rollback strategies, Online Schema Changes, AWS CodeDeploy integration, and Feature Toggles.

ChatGPT answers questions. AI Agents plan, use tools, and complete tasks autonomously. Understanding this difference changes how you build with AI.

Solving server waste at dawn and crashes at lunch. Understanding Auto Scaling vs Serverless through 'Taxi Dispatch' and 'Pizza Delivery' analogies. Plus, cost-saving tips using Spot Instances.

Why your server isn't hacked. From 'Packet Filtering' checking ports/IPs to AWS Security Groups. Evolution of Firewalls.

bio Column, Why Can't TS See It?"I added a bio column to the profiles table in the Supabase Dashboard.
Then I wrote frontend code:
const { data } = await supabase.from('profiles').select('bio');
console.log(data.bio); // Error: Property 'bio' does not exist on type ...
The TypeScript compiler screamed at me with red lines.
"Hey, there's no bio on profiles table!"
"No, I literally just added it! Look at the dashboard!" I pointed at my monitor, feeling wrongly accused.
I thought the Supabase Client read the DB schema in realtime.
I assumed supabase-js would magically scan the DB and infer types.
But TypeScript is a Compile Time static analysis tool.
It has no way of knowing what your live DB looks like while you code in VSCode.
Unless someone hands it a Type Definition File (database.types.ts) saying "Here's the current DB map," TS lives in the past forever.
I understood it when comparing it to a "School Yearbook."
database.types.ts): The Yearbook (Snapshot).Adding a bio column is like a new student transferring in.
But I'm holding last year's yearbook.
No matter how hard I look, the new student isn't there.
I need to take a new photo (Type Generation) and reprint the yearbook to see them.
"Ah, if I touch the DB, I must re-take the photo."
The basic way is using the CLI.
npx supabase gen types typescript --project-id "your-project-id" > src/types/database.types.ts
This command is the "Shutter Button". It connects to Supabase, reads the schema, and saves it as a TypeScript interface. Run this, and the red lines vanish like magic.
Don't type that long command every time.
// package.json
"scripts": {
"update-types": "npx supabase gen types typescript --project-id \"abcdefg\" > src/types/database.types.ts",
"dev": "npm run update-types && next dev"
}
Now just run npm run update-types.
What if a teammate updates the DB but forgets to update types? You pull their code, and it crashes instantly. "Hey Dave, you forgot to update types again!" Prevent this in CI/CD.
# .github/workflows/type-check.yml
name: Type Check
on: [push]
jobs:
check-types:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm install
# Check if stored types match actual DB (Fail if git diff exists)
- run: npm run update-types
- run: git diff --exit-code
This blocks commits that have stale type definitions, forcing a "Yearbook Update."
Generated types are verbose.
Typing Database['public']['Tables']['profiles']['Row'] every time is painful.
Use Supabase Helper Types effectively.
import { Database } from '@/types/database.types';
// Don't do this ❌
// type Profile = Database['public']['Tables']['profiles']['Row'];
// Do this ✅
type Profile = Database['public']['Tables']['profiles']['Row'];
type UnsavedProfile = Database['public']['Tables']['profiles']['Insert']; // omits id, created_at
type ProfileUpdate = Database['public']['Tables']['profiles']['Update']; // all fields optional
I create types/helpers.ts to shorten it further:
export type Tables<T extends keyof Database['public']['Tables']> = Database['public']['Tables'][T]['Row'];
type Profile = Tables<'profiles'>;
pg_catalog)Curious how supabase gen types works?
It's not magic. It queries the Postgres System Catalog (pg_catalog).
SELECT
column_name,
data_type,
is_nullable
FROM
information_schema.columns
WHERE
table_name = 'profiles';
It converts results into TS syntax (interface Profile { ... }).
So if your DB is down or network locked, type generation fails.
(This is why you need SUPABASE_ACCESS_TOKEN in CI/CD).
A true story.
A teammate added is_premium column to profiles via Dashboard.
Works on local. Deployed to production. Crash.
supabase/migrations).gen types against the Local Migration History (which was empty/stale).is_premium.is_premium, but the Production DB didn't have the column because he didn't push migrations."Infrastructure as Code."
Never edit schemas via Dashboards in a team. Always use supabase migration new.
Sometimes the generator fails to infer types correctly, especially for complex Views or JSON-returning Functions.
Don't be afraid to Override.
// Generated type might be 'any' or 'Json'
// type UserStats = Database['public']['Views']['user_stats']['Row'];
// Manual Override
export interface UserStatsOverride {
total_posts: number; // Supabase mistakenly typed it as string? Fix it here.
last_active: string;
}
Don't treat generated types as the Holy Scripture. Extend and fix them.
"It worked yesterday, fails today." High chance it's CLI Version Mismatch.
If Local CLI is v1.100.0 and Hosted Supabase is v1.110.0, introspection might fail.
The Fix: Pin Versions
Don't rely on global brew install supabase. Different devs will have different versions.
Install it LOCALLY: npm install -D supabase.
And run via npx supabase. This ensures everyone uses the exact same bin.
Supabase jsonb columns map to Json (basically any) in TS.
How to make it strict?
const meta = data.metadata as UserMeta;
Option 2: Wrapper Type (Recommended) Since generated files get overwritten, extend them in a separate file.
import { Database } from './database.types';
type OriginalUser = Database['public']['Tables']['users']['Row'];
export interface User extends Omit<OriginalUser, 'metadata'> {
metadata: {
theme: 'dark' | 'light';
notifications: boolean;
};
}
Now accessing user.metadata.theme gives you autocomplete!
@ts-ignoreWhen types fail, @ts-ignore or any looks tempting.
"Just deploy it, fix later."
This isn't Technical Debt. It's Technical Bankruptcy. When DB schema changes later, these ignored lines become landmines for runtime errors.
Better to use Partial<Type> or Pick to loosen strictness than to go any.
Don't defeat the purpose of using TypeScript.
Problem:
You are tired of writing long generic types or using as assertions every time you call supabase.from().
Challenge: Wrap the Supabase Client so that types are inferred automatically just by passing the table name.
// Goal:
const users = await db.get('users'); // users is automatically inferred as User[]
Hint:
Write a helper function that takes the table name as a key of Database['public']['Tables'] and returns the Row type.
This is the beginning of true "Type Safety".