SQL Server to Supabase: What Nobody Tells You

Share

Supabase pitches itself as the fastest way to get a Postgres backend, and it mostly delivers. But if your starting point is SQL Server — as it is for a lot of Microsoft-stack teams — the migration guides skip over the parts that actually consume your week. Having gone through this exercise for a real schema, here's what nobody tells you upfront.

The schema translation is not mechanical

The type mappings look like a lookup table until they aren't. NVARCHAR to text, DATETIME to timestamptz, BIT to boolean — fine. But IDENTITY columns become Postgres identity or sequences with different semantics around gaps and resets. UNIQUEIDENTIFIER maps to uuid, except SQL Server's NEWSEQUENTIALID() ordering behavior has no direct equivalent, which quietly matters if your clustered indexes depended on it. And anything using SQL Server-specific defaults — GETDATE(), computed columns, filtered indexes with SQL Server syntax — needs a human decision, not a find-and-replace.

The honest rule: budget one pass for the automatic conversions and a second, longer pass for everything the first pass flagged as "equivalent." Half of it isn't quite.

T-SQL logic is where migrations go to stall

Tables move. Stored procedures don't — not cleanly. T-SQL and PL/pgSQL are cousins, not twins: different variable declaration, different error handling, different temp table behavior, no direct equivalent for some patterns like MERGE quirks or cross-database queries. Every stored procedure is a small rewrite, and the ones written a decade ago by someone who left are the ones with the undocumented edge cases.

The pragmatic move is triage: rewrite the procedures the application actually calls hot paths through, replace simple ones with application-side logic or Postgres views, and consciously decide which legacy ones die with the migration instead of porting them out of completeness instinct.

Row Level Security changes your mental model

Supabase leans heavily on Postgres Row Level Security — it's the default answer to "how do clients query the database directly without a backend API in between." Coming from SQL Server, where authorization usually lives in the application tier or stored procedures, RLS is a genuinely different mental model: the policy lives on the table, applies to every query path, and silently filters rows rather than erroring. It's powerful, but treating it as an afterthought is how you ship a table that's accidentally world-readable. Design the policies alongside the schema, not after.

The sync question: migration versus coexistence

The guides assume a one-way, one-time move. Reality for many teams — especially with on-premise systems that can't simply be turned off — is a coexistence period where SQL Server and Supabase both stay live, and data flows between them. Bidirectional sync is a real engineering project of its own: conflict resolution, change tracking on both sides, and deciding which system owns which data. If you're facing this, scope it as its own workstream with its own timeline — bolting it onto the migration as a footnote is how the "two-week migration" becomes a quarter.

What I'd tell a past version of me

Treat the table DDL conversion as the warm-up, not the migration. The real work is the stored procedure triage, the RLS design, and — if the old system can't die immediately — the sync architecture. Price those three honestly at the start and the project stays predictable. Discover them mid-flight and the timeline was fiction from day one.

Read more