Coding practices for backend development in Atomic CRM. Use when deciding whether backend logic is needed, or when creating/modifying database migrations, views, triggers, RLS policies, edge functions, or custom dataProvider methods that call Supabase APIs.
75
—
Does it follow best practices?
Impact
—
No eval scenarios have been run
Passed
No known issues
Reference for backend conventions. There is no custom backend server all server-side logic uses Supabase: PostgreSQL (tables, views, triggers, RLS), Auth API, Storage, and Edge Functions. Read the relevant section before writing backend code, then check the Red Flags and Verification list.
For the SQL migration mechanics themselves, see Skill({skill: "writing-migrations"}).
For the frontend side of a dataProvider method, see Skill({skill: "frontend-dev"}).
Prefer frontend-only solutions via custom dataProvider methods calling the PostgREST API.
When backend logic is needed:
CREATE OR REPLACE VIEW in a new migration). PostgREST exposes views like tables. When underlying table columns change, update the contacts_summary and companies_summary views too.httpClient(${supabaseUrl}/functions/v1/)) and call it via react-query. (e.g. salesCreate() → /functions/v1/users, mergeContacts() → /functions/v1/merge_contacts)supabase/functions/_shared/ — reuse authentication.ts, supabaseAdmin.ts, cors.ts, utils.tsauthenticate() → handlerverify_jwt = false in config.toml, so JWT validation is manual via authenticate()sales_id trigger (see migration 20260108160722)SECURITY DEFINER with a fixed search_path, or delegate to one. A SECURITY INVOKER count runs under the caller's RLS, sees only the rows that caller may read, under-counts, and the limit silently never fires (e.g. session_booked_count is DEFINER for exactly this reason).WITH CHECK clause must constrain every column a non-admin may set, not just ownership. with check (contact_id = current_user_contact()) alone lets the caller forge any other column (status, type, amounts, flags) via PostgREST — privilege escalation / payment bypass. Whitelist allowed values in the policy, a trigger, or a CHECK constraint; never trust a client-chosen status/type.contacts_summary/companies_summary without updating the view.sales_id trigger.SECURITY INVOKER function it under-counts and never fires.WITH CHECK that constrains only ownership while leaving status/type/amounts client-settable.authenticate() in the middleware chain.contacts_summary and companies_summary in sync.sales_id trigger.SECURITY DEFINER with a fixed search_path.CHECK.authenticate() → handler.dce557e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.