[{"data":1,"prerenderedAt":400},["ShallowReactive",2],{"blog-how-to/postgresql-roles":3},{"id":4,"title":5,"body":6,"category":380,"date":381,"dateModified":382,"description":383,"draft":384,"extension":385,"faq":386,"featured":384,"headerVariant":387,"image":386,"keywords":386,"meta":388,"navigation":389,"ogDescription":390,"ogTitle":386,"path":391,"readTime":386,"schemaOrg":392,"schemaType":393,"seo":394,"sitemap":395,"stem":396,"tags":397,"twitterCard":398,"__hash__":399},"blog/blog/how-to/postgresql-roles.md","How to Set Up PostgreSQL Roles and Permissions",{"type":7,"value":8,"toc":364},"minimark",[9,13,17,21,27,30,43,48,51,54,58,95,111,127,143,159,175,191,221,225,247,253,257,262,269,273,279,283,286,290,296,326,345],[10,11],"category-badge",{"category":12},"How-To Guide",[14,15,5],"h1",{"id":16},"how-to-set-up-postgresql-roles-and-permissions",[18,19,20],"p",{},"Implement least-privilege access control for your PostgreSQL database",[22,23,24],"tldr",{},[18,25,26],{},"TL;DR (25 minutes):\nNever use the postgres superuser in your app. Create a dedicated role with\nCREATE ROLE myapp LOGIN PASSWORD 'xxx'\n, grant only the permissions it needs with\nGRANT SELECT, INSERT, UPDATE ON table TO myapp\n, and use separate read-only roles for analytics.",[18,28,29],{},"Prerequisites:",[31,32,33,37,40],"ul",{},[34,35,36],"li",{},"PostgreSQL database (local, Neon, Supabase, RDS, etc.)",[34,38,39],{},"Superuser or admin access",[34,41,42],{},"Basic SQL knowledge",[44,45,47],"h2",{"id":46},"why-this-matters","Why This Matters",[18,49,50],{},"Using a superuser account for your application is like giving everyone in your company the master key. If your app is compromised, attackers get full database access - they can drop tables, read all data, or create backdoor accounts.",[18,52,53],{},"Proper role-based access limits the blast radius of a security incident. An app user with only INSERT/SELECT permissions on specific tables can't DROP your database or read other applications' data.",[44,55,57],{"id":56},"step-by-step-guide","Step-by-Step Guide",[59,60,62,67,70,84],"step",{"number":61},"1",[63,64,66],"h3",{"id":65},"understand-postgresql-roles","Understand PostgreSQL roles",[18,68,69],{},"In PostgreSQL, users and groups are both \"roles.\" A role can:",[31,71,72,75,78,81],{},[34,73,74],{},"Login (like a user) or not (like a group)",[34,76,77],{},"Own database objects",[34,79,80],{},"Have specific privileges",[34,82,83],{},"Inherit privileges from other roles",[85,86,91],"pre",{"className":87,"code":89,"language":90},[88],"language-text","-- View existing roles\nSELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolcanlogin\nFROM pg_roles\nWHERE rolname NOT LIKE 'pg_%';\n","text",[92,93,89],"code",{"__ignoreMap":94},"",[59,96,98,102,105],{"number":97},"2",[63,99,101],{"id":100},"create-an-application-role","Create an application role",[18,103,104],{},"Create a dedicated role for your application:",[85,106,109],{"className":107,"code":108,"language":90},[88],"-- Create a login role for your app\nCREATE ROLE myapp_user WITH\n  LOGIN\n  PASSWORD 'use-a-strong-password-here'\n  CONNECTION LIMIT 20;  -- Limit concurrent connections\n\n-- Create the application database (if needed)\nCREATE DATABASE myapp_db OWNER myapp_user;\n\n-- Connect to the database\n\\c myapp_db\n",[92,110,108],{"__ignoreMap":94},[59,112,114,118,121],{"number":113},"3",[63,115,117],{"id":116},"grant-schema-permissions","Grant schema permissions",[18,119,120],{},"Grant access to the schema and create privileges:",[85,122,125],{"className":123,"code":124,"language":90},[88],"-- Grant usage on the public schema\nGRANT USAGE ON SCHEMA public TO myapp_user;\n\n-- Grant ability to create tables (only if app needs migrations)\nGRANT CREATE ON SCHEMA public TO myapp_user;\n\n-- Or for tighter security, only grant on specific schemas\nCREATE SCHEMA app_schema;\nGRANT ALL ON SCHEMA app_schema TO myapp_user;\n",[92,126,124],{"__ignoreMap":94},[59,128,130,134,137],{"number":129},"4",[63,131,133],{"id":132},"grant-table-level-permissions","Grant table-level permissions",[18,135,136],{},"Grant specific permissions on tables:",[85,138,141],{"className":139,"code":140,"language":90},[88],"-- Grant full CRUD on specific tables\nGRANT SELECT, INSERT, UPDATE, DELETE ON users TO myapp_user;\nGRANT SELECT, INSERT, UPDATE, DELETE ON posts TO myapp_user;\nGRANT SELECT, INSERT, UPDATE, DELETE ON comments TO myapp_user;\n\n-- Grant on all existing tables in schema\nGRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO myapp_user;\n\n-- Grant on future tables too\nALTER DEFAULT PRIVILEGES IN SCHEMA public\n  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO myapp_user;\n\n-- Grant sequence usage (for auto-increment IDs)\nGRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO myapp_user;\nALTER DEFAULT PRIVILEGES IN SCHEMA public\n  GRANT USAGE ON SEQUENCES TO myapp_user;\n",[92,142,140],{"__ignoreMap":94},[59,144,146,150,153],{"number":145},"5",[63,147,149],{"id":148},"create-a-read-only-role","Create a read-only role",[18,151,152],{},"For analytics, reporting, or debugging - create a read-only role:",[85,154,157],{"className":155,"code":156,"language":90},[88],"-- Create read-only role\nCREATE ROLE myapp_readonly WITH\n  LOGIN\n  PASSWORD 'another-strong-password';\n\n-- Grant connect\nGRANT CONNECT ON DATABASE myapp_db TO myapp_readonly;\n\n-- Grant schema usage\nGRANT USAGE ON SCHEMA public TO myapp_readonly;\n\n-- Grant SELECT only\nGRANT SELECT ON ALL TABLES IN SCHEMA public TO myapp_readonly;\nALTER DEFAULT PRIVILEGES IN SCHEMA public\n  GRANT SELECT ON TABLES TO myapp_readonly;\n\n-- Optionally, exclude sensitive tables\nREVOKE SELECT ON users FROM myapp_readonly;  -- No user data access\n",[92,158,156],{"__ignoreMap":94},[59,160,162,166,169],{"number":161},"6",[63,163,165],{"id":164},"create-role-groups-for-team-management","Create role groups for team management",[18,167,168],{},"Use role inheritance for team access:",[85,170,173],{"className":171,"code":172,"language":90},[88],"-- Create group roles (no login)\nCREATE ROLE developers NOLOGIN;\nCREATE ROLE analysts NOLOGIN;\n\n-- Grant permissions to groups\nGRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO developers;\nGRANT SELECT ON ALL TABLES IN SCHEMA public TO analysts;\n\n-- Create individual users that inherit from groups\nCREATE ROLE john_dev WITH LOGIN PASSWORD 'xxx' IN ROLE developers;\nCREATE ROLE jane_analyst WITH LOGIN PASSWORD 'xxx' IN ROLE analysts;\n\n-- John now has all developer permissions\n-- Jane now has all analyst permissions\n",[92,174,172],{"__ignoreMap":94},[59,176,178,182,185],{"number":177},"7",[63,179,181],{"id":180},"revoke-dangerous-permissions","Revoke dangerous permissions",[18,183,184],{},"Remove permissions you don't want:",[85,186,189],{"className":187,"code":188,"language":90},[88],"-- Revoke public access (PostgreSQL grants some by default)\nREVOKE ALL ON DATABASE myapp_db FROM PUBLIC;\nREVOKE ALL ON SCHEMA public FROM PUBLIC;\n\n-- Revoke create from public schema (prevents random table creation)\nREVOKE CREATE ON SCHEMA public FROM myapp_user;\n\n-- Revoke specific table access\nREVOKE DELETE ON audit_logs FROM myapp_user;  -- App can't delete logs\nREVOKE ALL ON admin_settings FROM myapp_user;  -- No admin access\n",[92,190,188],{"__ignoreMap":94},[192,193,194,197],"warning-box",{},[18,195,196],{},"Security Best Practices:",[31,198,199,206,209,212,215,218],{},[34,200,201,202,205],{},"Never use the ",[92,203,204],{},"postgres"," superuser for application connections",[34,207,208],{},"Use separate roles for different environments (dev, staging, prod)",[34,210,211],{},"Rotate passwords regularly and store them in a secrets manager",[34,213,214],{},"Consider using SCRAM-SHA-256 authentication (PostgreSQL 10+)",[34,216,217],{},"Enable SSL/TLS for all connections",[34,219,220],{},"Use pg_hba.conf to restrict which IPs can connect",[44,222,224],{"id":223},"how-to-verify-it-worked","How to Verify It Worked",[226,227,228,235,241],"ol",{},[34,229,230,234],{},[231,232,233],"strong",{},"Test login:"," Connect as your app user and verify it works",[34,236,237,240],{},[231,238,239],{},"Test permissions:"," Try operations the role should and shouldn't have",[34,242,243,246],{},[231,244,245],{},"Check grants:"," Review what permissions exist",[85,248,251],{"className":249,"code":250,"language":90},[88],"-- Connect as app user\npsql -U myapp_user -d myapp_db\n\n-- Try allowed operation (should work)\nSELECT * FROM users LIMIT 1;\n\n-- Try disallowed operation (should fail)\nDROP TABLE users;  -- ERROR: must be owner of table users\n\n-- Check what permissions a role has\nSELECT grantee, table_name, privilege_type\nFROM information_schema.table_privileges\nWHERE grantee = 'myapp_user';\n\n-- Check role attributes\n\\du myapp_user\n",[92,252,250],{"__ignoreMap":94},[44,254,256],{"id":255},"common-errors-troubleshooting","Common Errors & Troubleshooting",[258,259,261],"h4",{"id":260},"error-permission-denied-for-table-xxx","Error: \"permission denied for table xxx\"",[18,263,264,265,268],{},"The role doesn't have the required privilege. Grant it with ",[92,266,267],{},"GRANT SELECT ON xxx TO role",".",[258,270,272],{"id":271},"error-permission-denied-for-schema-public","Error: \"permission denied for schema public\"",[18,274,275,276,268],{},"Missing schema usage grant. Run ",[92,277,278],{},"GRANT USAGE ON SCHEMA public TO role",[258,280,282],{"id":281},"new-tables-arent-accessible","New tables aren't accessible",[18,284,285],{},"You need ALTER DEFAULT PRIVILEGES to grant on future tables, not just existing ones.",[258,287,289],{"id":288},"error-permission-denied-for-sequence","Error: \"permission denied for sequence\"",[18,291,292,293,268],{},"Can't use auto-increment IDs. Grant sequence usage: ",[92,294,295],{},"GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO role",[297,298,299,306,320],"faq-section",{},[300,301,303],"faq-item",{"question":302},"Should I let my app run migrations?",[18,304,305],{},"For development, yes. For production, consider running migrations with a separate privileged role, then using a limited role for runtime. This prevents the app from accidentally (or maliciously) modifying schema.",[300,307,309],{"question":308},"How does this work with Supabase/Neon/RDS?",[18,310,311,312,315,316,319],{},"Managed PostgreSQL services handle some of this automatically but still allow custom roles. Check their docs for specifics - Supabase uses ",[92,313,314],{},"authenticated"," and ",[92,317,318],{},"anon"," roles with RLS.",[300,321,323],{"question":322},"What about row-level security (RLS)?",[18,324,325],{},"RLS provides even finer control - restricting which rows a role can see based on policies. Use it when you need per-user data isolation (like multi-tenant apps).",[18,327,328,331,336,337,336,341],{},[231,329,330],{},"Related guides:",[332,333,335],"a",{"href":334},"/blog/how-to/setup-supabase-rls","Supabase RLS Setup"," ·\n",[332,338,340],{"href":339},"/blog/how-to/mongodb-auth","MongoDB Authentication",[332,342,344],{"href":343},"/blog/how-to/database-encryption","Database Encryption",[346,347,348,354,359],"related-articles",{},[349,350],"related-card",{"description":351,"href":352,"title":353},"Step-by-step guide to password hashing with bcrypt and Argon2. Why you should never use MD5 or SHA, and how to implement","/blog/how-to/hash-passwords-securely","How to Hash Passwords Securely",[349,355],{"description":356,"href":357,"title":358},"Step-by-step guide to hiding API keys in your web app. Use environment variables, .gitignore, and platform secrets to ke","/blog/how-to/hide-api-keys","How to Hide API Keys - Secure Your Secrets",[349,360],{"description":361,"href":362,"title":363},"Step-by-step guide to securing API keys in your vibe-coded app. Learn environment variables, .gitignore, and platform-sp","/blog/how-to/how-to-hide-api-keys","How to Hide Your API Keys (The Right Way)",{"title":94,"searchDepth":365,"depth":365,"links":366},2,[367,368,378,379],{"id":46,"depth":365,"text":47},{"id":56,"depth":365,"text":57,"children":369},[370,372,373,374,375,376,377],{"id":65,"depth":371,"text":66},3,{"id":100,"depth":371,"text":101},{"id":116,"depth":371,"text":117},{"id":132,"depth":371,"text":133},{"id":148,"depth":371,"text":149},{"id":164,"depth":371,"text":165},{"id":180,"depth":371,"text":181},{"id":223,"depth":365,"text":224},{"id":255,"depth":365,"text":256},"how-to","2026-01-20","2026-02-02","Step-by-step guide to PostgreSQL role-based access control. Create users, assign permissions, and implement least-privilege access for your database.",false,"md",null,"yellow",{},true,"Step-by-step guide to PostgreSQL role-based access control.","/blog/how-to/postgresql-roles","[object Object]","HowTo",{"title":5,"description":383},{"loc":391},"blog/how-to/postgresql-roles",[],"summary_large_image","bsdWxg3HKtw3XM4njlWCfyuOmPObhxsKW4vw10crm4M",1775843928165]