Learn PostgreSQL Roles

1. The permission chain

Acme is a small startup with one application and one database. Priya, the founder, created the app.orders table herself in the early days, and the application has been reading and writing it ever since—connected, as early applications usually are, with the admin credentials Priya set up at the start. Today Alice joins as the first analyst, hired to answer the company’s favourite question: what did we sell?

Her report is a single query, and the application runs the same one constantly. But when Alice runs it, PostgreSQL refuses—and that refusal is the best introduction there is to how PostgreSQL decides who may do what.

This course follows Acme’s database as the company grows, one incident per chapter:

  • Priya, the founder, created the original app schema and orders table by hand. That detail looks harmless today; it will not stay harmless.
  • The application, which still connects with the admin credentials from Acme’s first week—the decision that made everything work and hid every problem in this course.
  • Alice, the first analyst, needs to read orders.
  • deploy, Acme’s migration login, will matter once the schema starts changing.
  • reporting_app, an automated reporting service, arrives in the next chapter.

Each chapter has the same rhythm: something happens at Acme, you reproduce it against a real PostgreSQL running in your browser, and then—below the lab—you write down what should stay true. Start where Alice starts:

Chapter 1 · The permission chain

PG 18.3

Step 1 of 4

Step 1

Run the report the way the application does

Acme’s application has read and written orders since day one, connected with the admin credentials Priya configured at the start. Watch what that means: PostgreSQL does not check a single privilege for a superuser.

Run the revenue report on the application’s admin connection.

Editable SQL · disposable browser database · changes are discarded after the run

PostgreSQL output

PostgreSQL’s rows, command tags, or exact error will appear here.

How the browser lab models roles

The selector sets session authorization inside an isolated PGlite database. Statements run one at a time and autocommit, like psql: execution stops at the first error, and earlier statements keep their effect. PostgreSQL performs ordinary role, ownership, schema, and object checks; the diagram comes from catalog privilege queries after your SQL. This is not a password, CONNECT, pg_hba.conf, or concurrent-session test.

Keep one rule

PostgreSQL must be able to reach the schema and authorize the object operation.

For a query that reads app.orders, schema USAGE makes the name reachable and table SELECT authorizes the read. search_path changes name lookup, not privileges. A table grant does not imply schema access, and schema access does not imply a table operation.

The gates are evaluated in order, and the error names the first gate that failed—not everything that is missing. That is why the same query produced two different errors in the lab as each gate opened.

Superusers and object owners skip these checks entirely. That is why the admin-connected application never noticed a single one of them—and why the first identity without those shortcuts is the first to see permission denied.

The policy so far

The lab fixed today’s database, but the two GRANT statements you ran live only in PostgreSQL’s catalogs now—invisible history the moment your session ends. This is where pgroles enters the story: you describe the roles, grants, and memberships that should exist in a YAML policy, and pgroles plan compares that intent with the live database and proposes the exact SQL to converge them. Every chapter ends by recording its repair this way, and by chapter 3 the difference between “what the database accumulated” and “what the policy declares” becomes the whole plot.

This first policy is the small team’s literal state: Alice receives both grants directly.

pgroles.yamlpolicy schema
SectionFieldRole / profile / settingObjectPrivilege / typeUnknown
roles:
  - name: alice
    login: true

grants:
  - role: alice
    privileges: [USAGE]
    object: { type: schema, name: app }
  - role: alice
    privileges: [SELECT]
    object: { type: table, schema: app, name: orders }

It works, but every new reader would duplicate those ACL entries. The next chapter gives the permission bundle a reusable name.

Guide

Continue: capability roles

Give Alice and an application the same access without copying grants.

Open guide
Guide

Grants reference

See every object type and privilege pgroles manages.

Open guide