1---
2title: Credentials
3description: PlanetScale credentials as Stack resources — Postgres roles, the default role and forceReset, MySQL passwords, TTLs, CIDR allowlists, and direct vs pooled connection origins.
4---
5
6PlanetScale credentials are resources: Postgres branches use **roles**
7(`PostgresRole`, `PostgresDefaultRole`), MySQL branches use
8**passwords** (`MySQLPassword`). One rule shapes all three —
9PlanetScale returns the plaintext exactly once, at create time.
10Alchemy stores it `Redacted` in state, and any change it can't apply
11in place mints a fresh credential whose new plaintext propagates to
12downstream resources automatically.
13
14## Postgres roles
15
16`PostgresRole` mints a Postgres user + password on a branch;
17permissions come from `inheritedRoles`:
18
19```typescript
20import * as Planetscale from "alchemy/Planetscale";
21
22const reader = yield* Planetscale.PostgresRole("reader", {
23 database,
24 branch, // defaults to "main"
25 inheritedRoles: ["pg_read_all_data", "pg_read_all_settings"],
26});
27```
28
29Only `name` and `successor` (the role that inherits ownership when
30this one is dropped) update in place — changing `ttl`,
31`inheritedRoles`, `withReplication`, `database`, or `branch` replaces
32the role with a new id, name, and password.
33
34A logical-replication consumer (Electric, Debezium, a CDC pipeline)
35needs the `REPLICATION` attribute, which Postgres never grants through
36role membership. PlanetScale issues it only alongside `postgres`:
37
38```typescript
39const replicator = yield* Planetscale.PostgresRole("replicator", {
40 database,
41 inheritedRoles: ["postgres"],
42 withReplication: true,
43});
44```
45
46## The default role
47
48Every Postgres branch ships with a built-in default role;
49`PostgresDefaultRole` captures it as a resource:
50
51```typescript
52const defaultRole = yield* Planetscale.PostgresDefaultRole("main-role", {
53 database,
54 forceReset: true,
55});
56```
57
58The default role cannot be adopted: if it already exists and your
59Stack has no record of it, the deploy fails
60(`Default role already exists ... Use forceReset: true to reset`)
61rather than silently taking over. `forceReset: true` opts in by
62resetting it — same role, **new password** — which invalidates any
63credentials other systems were using. Destroying the resource also
64resets rather than deletes (PlanetScale has no delete endpoint for
65it). Prefer `PostgresRole` for application access.
66
67## MySQL passwords
68
69`MySQLPassword` mints credentials scoped to one branch and one role —
70`"reader"`, `"writer"`, `"admin"`, or `"readwriter"`:
71
72```typescript
73const password = yield* Planetscale.MySQLPassword("app-password", {
74 database,
75 branch, // defaults to "main"
76 role: "reader",
77});
78```
79
80`name` and `cidrs` update in place (same id, no new plaintext);
81changing `role`, `ttl`, `replica`, `database`, or `branch` replaces
82the password and deletes the old one.
83
84## TTLs and CIDR allowlists
85
86Both families take a `ttl` (seconds) for credentials that expire on
87their own; `MySQLPassword` additionally takes `cidrs` to restrict
88which IP ranges may connect:
89
90```typescript
91const admin = yield* Planetscale.MySQLPassword("admin-password", {
92 database,
93 role: "admin",
94 ttl: 86400,
95 cidrs: ["203.0.113.0/24"],
96});
97```
98
99Changing `ttl` is a replacement on both roles and passwords (the
100expiry is baked in at create time); editing `cidrs` on a MySQL
101password syncs in place on the next deploy.
102
103## `origin` vs `pooledOrigin`
104
105Credentials expose parsed connection components ready to feed into
106consumers like [Cloudflare Hyperdrive](/cloudflare/data/hyperdrive).
107`PostgresRole` has two: `origin` is the direct connection (port
1085432), `pooledOrigin` goes through PSBouncer (port 6432):
109
110```typescript
111import * as Cloudflare from "alchemy/Cloudflare";
112
113const hyperdrive = yield* Cloudflare.Hyperdrive.Connection("app-hyperdrive", {
114 origin: role.origin, // direct — Hyperdrive pools at the edge
115 dev: role.pooledOrigin, // local dev bypasses Hyperdrive, so pool at PlanetScale
116});
117```
118
119`MySQLPassword` exposes a single `origin`, and `PostgresDefaultRole`
120carries the same direct/pooled endpoints as `Redacted` URL strings
121(`connectionUrl` / `connectionUrlPooled`), as does `PostgresRole`.
122
123## Where next
124
125- [Postgres](/planetscale/data/postgres) — databases, branches, and roles
126 in context.
127- [MySQL](/planetscale/data/mysql) — the Vitess side, end to end.
128- [Hyperdrive](/cloudflare/data/hyperdrive) — edge-pooled connections from
129 Workers to your PlanetScale database.
130
131Reference:
132
133- [PostgresRole](/providers/planetscale/postgres/postgresrole) ·
134 [PostgresDefaultRole](/providers/planetscale/postgres/postgresdefaultrole) ·
135 [MySQLPassword](/providers/planetscale/mysql/mysqlpassword)