[{"data":1,"prerenderedAt":177},["ShallowReactive",2],{"content:\u002Fguides\u002Fsupabase-content-moderation":3,"ArticleBody_15r7cqlm7gnk7L1NXRwnMvaimdQhVXHD3tHuP8iV6nk":172},{"kind":4,"path":5,"locale":6,"title":7,"htmlTitle":8,"description":9,"navTitle":7,"published":10,"updated":10,"alternates":11,"breadcrumbs":12,"indexable":20,"twin":21,"collection":22,"slug":23,"file":24,"content":25,"hub":18,"titleEm":27,"eyebrow":28,"lead":29,"cta":27,"faq":30,"faqJsonLd":46,"sources":52,"related":151,"topic":27,"jsonLdType":170,"blocks":171,"shortDefinition":27,"feed":20},"article","\u002Fguides\u002Fsupabase-content-moderation","en","Supabase content moderation: Edge Functions, Storage and RLS","Supabase Content Moderation: Edge Functions · Detectivision","Check Supabase posts and uploads in an Edge Function, keep them hidden with row-level security until approved, and take results by webhook. With code.","2026-10-16T18:00:00+03:00",[],[13,16,19],{"name":14,"path":15},"Home","\u002F",{"name":17,"path":18},"Guides","\u002Fguides",{"name":7,"path":5},true,"\u002Fguides\u002Fsupabase-content-moderation.md","guides","supabase-content-moderation","content\u002Fen\u002Fguides\u002Fsupabase-content-moderation.md",{"collection":26,"path":5},"guides_en",null,"Guide","A status column, row-level security, a database trigger and two Edge Functions. The SQL and both functions were run on 16 October 2026 against stand-ins for Supabase.",[31,34,37,40,43],{"q":32,"a":33},"Why does each flagged check reach my webhook twice?","A flagged check sends two events, moderation.completed and moderation.flagged. Subscribe the endpoint to moderation.completed and moderation.failed only. The update in moderation-result also changes a row only while it is pending, so a repeated or doubled event does nothing.",{"q":35,"a":36},"What happens if the signed URL expires before the check runs?","The check fails when it runs, because the API cannot download the image: the webhook gets moderation.failed with invalid_input, and nothing is charged. The handler sends the photo to review. A URL that lives a little over a day avoids this, because an async check that cannot run within about a day fails anyway.",{"q":38,"a":39},"Do I still need the service_role key?","No. Supabase lists the anon and service_role keys as legacy and says it is deprecating them by the end of 2026. Here a named secret key authenticates the database's calls, and ctx.supabaseAdmin comes from the project's secret keys, which the platform provides to Edge Functions. The SQL still grants rights to the service_role Postgres role, because secret keys act through that role.",{"q":41,"a":42},"Can a user publish a post by setting its status from the client?","No. The grants let signed-in users insert only the body of a post and the path of a photo, and update nothing, so every row starts as pending. In the SQL test, inserting a status and updating one both failed with permission denied.",{"q":44,"a":45},"Can Postgres call the moderation API directly with pg_net?","For text it could: pg_net can POST an async request with the API key read from Vault. But the API's answer, a request id or an error, would only sit in net._http_response for 6 hours, and nothing would act on it. Photos also need a signed URL from the Storage API, which is simpler to get in an Edge Function.",[47,48,49,50,51],{"q":32,"a":33},{"q":35,"a":36},{"q":38,"a":39},{"q":41,"a":42},{"q":44,"a":45},[53,58,61,64,67,70,73,76,79,82,85,88,91,94,97,100,103,106,109,112,116,120,124,127,130,133,136,139,142,145,148],{"title":54,"url":55,"publisher":56,"accessed":57},"Securing Edge Functions","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Fauth","Supabase","2026-10-16",{"title":59,"url":60,"publisher":56,"accessed":57},"Authorization headers","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Fauth-headers",{"title":62,"url":63,"publisher":56,"accessed":57},"API keys","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fapi\u002Fapi-keys",{"title":65,"url":66,"publisher":56,"accessed":57},"Environment variables (Edge Functions secrets)","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Fsecrets",{"title":68,"url":69,"publisher":56,"accessed":57},"Function Configuration","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Ffunction-configuration",{"title":71,"url":72,"publisher":56,"accessed":57},"Edge Functions limits","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Flimits",{"title":74,"url":75,"publisher":56,"accessed":57},"Edge Functions pricing","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Fpricing",{"title":77,"url":78,"publisher":56,"accessed":57},"Database Webhooks","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fdatabase\u002Fwebhooks",{"title":80,"url":81,"publisher":56,"accessed":57},"pg_net: Async Networking","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fdatabase\u002Fextensions\u002Fpg_net",{"title":83,"url":84,"publisher":56,"accessed":57},"Vault","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fdatabase\u002Fvault",{"title":86,"url":87,"publisher":56,"accessed":57},"Scheduling Edge Functions","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Fschedule-functions",{"title":89,"url":90,"publisher":56,"accessed":57},"Row Level Security","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fdatabase\u002Fpostgres\u002Frow-level-security",{"title":92,"url":93,"publisher":56,"accessed":57},"Storage Access Control","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fstorage\u002Fsecurity\u002Faccess-control",{"title":95,"url":96,"publisher":56,"accessed":57},"Storage Helper Functions","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fstorage\u002Fschema\u002Fhelper-functions",{"title":98,"url":99,"publisher":56,"accessed":57},"Storage Buckets","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fstorage\u002Fbuckets\u002Ffundamentals",{"title":101,"url":102,"publisher":56,"accessed":57},"Serving assets from Storage (signed URLs)","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fstorage\u002Fserving\u002Fdownloads",{"title":104,"url":105,"publisher":56,"accessed":57},"Smart CDN","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fstorage\u002Fcdn\u002Fsmart-cdn",{"title":107,"url":108,"publisher":56,"accessed":57},"JavaScript: createSignedUrl","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Freference\u002Fjavascript\u002Fstorage-from-createsignedurl",{"title":110,"url":111,"publisher":56,"accessed":57},"Custom Auth Emails with React Email and Resend (standardwebhooks in an Edge Function)","https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ffunctions\u002Fexamples\u002Fauth-send-email-hook-react-email-resend",{"title":113,"url":114,"publisher":115,"accessed":57},"@supabase\u002Fserver (README, version 1.9.2)","https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fserver","Supabase on GitHub",{"title":117,"url":118,"publisher":119,"accessed":57},"Detectivision API status (GET \u002Fapi\u002Fv1\u002Fstatus)","https:\u002F\u002Fapi.detectivision.ai\u002Fapi\u002Fv1\u002Fstatus","Detectivision",{"title":121,"url":122,"publisher":123,"accessed":57},"Async requests","https:\u002F\u002Fdetectivision.ai\u002Fdocs\u002Fasync","Detectivision docs",{"title":125,"url":126,"publisher":123,"accessed":57},"Webhooks","https:\u002F\u002Fdetectivision.ai\u002Fdocs\u002Fwebhooks",{"title":128,"url":129,"publisher":123,"accessed":57},"Image moderation","https:\u002F\u002Fdetectivision.ai\u002Fdocs\u002Fimage",{"title":131,"url":132,"publisher":123,"accessed":57},"Text moderation","https:\u002F\u002Fdetectivision.ai\u002Fdocs\u002Ftext",{"title":134,"url":135,"publisher":123,"accessed":57},"Flags","https:\u002F\u002Fdetectivision.ai\u002Fdocs\u002Fflags",{"title":137,"url":138,"publisher":123,"accessed":57},"Responses","https:\u002F\u002Fdetectivision.ai\u002Fdocs\u002Fresponses",{"title":140,"url":141,"publisher":123,"accessed":57},"Credits and billing","https:\u002F\u002Fdetectivision.ai\u002Fdocs\u002Fcredits",{"title":143,"url":144,"publisher":123,"accessed":57},"Errors","https:\u002F\u002Fdetectivision.ai\u002Fdocs\u002Ferrors",{"title":146,"url":147,"publisher":123,"accessed":57},"Metadata and actions","https:\u002F\u002Fdetectivision.ai\u002Fdocs\u002Factions",{"title":149,"url":150,"publisher":123,"accessed":57},"Quickstart","https:\u002F\u002Fdetectivision.ai\u002Fdocs\u002Fquickstart",[152,156,160,163,166],{"path":153,"title":154,"description":155},"\u002Fguides\u002Ffirebase-content-moderation","Firebase content moderation: Firestore messages and Storage uploads","Moderate Firebase messages and uploads: a Cloud Function sends each new text or image to a moderation API and keeps it hidden until it is approved. With code.",{"path":157,"title":158,"description":159},"\u002Fguides\u002Fcontent-moderation-system-design","How to design a content moderation system: checks, rules and queues","A reference design for moderating user content: where to check, when to block or hold, how to route doubtful cases to people, and how decisions reach your app.",{"path":161,"title":121,"description":162},"\u002Fdocs\u002Fasync","Send content that can wait, get its id at once, and pay half: results come by webhook.",{"path":164,"title":125,"description":165},"\u002Fdocs\u002Fwebhooks","Get events at your own URL as they happen. Finished checks, flagged checks, failed async checks and actions, signed with your endpoint's secret.",{"path":167,"title":168,"description":169},"\u002Ftoxicity-detection-api","Toxicity detection","Check comments and chat messages for insults, hate, harassment and self-harm in ten languages, with a flag for each kind of toxicity.","TechArticle",false,["Island",173],{"key":174,"result":175},"ArticleBody_15r7cqlm7gnk7L1NXRwnMvaimdQhVXHD3tHuP8iV6nk",{"head":176},{},1792166154157]