English
Managing users via GraphQL
You need to provision a subscriber, grant a team member access to a subgraph, or make someone an admin — without opening the browser. All three operations are available as GraphQL mutations. They're designed to be driven by an agent or a script.
This requires admin API access. See en/user/graphql for authentication setup.
Typical workflow
- Create a note with
subgraph: namefrontmatter → sync → subgraph appears createUser(email)→ getuserIdallSubgraphs→ getsubgraphIdscreateUserSubgraphAccess(userId, subgraphIds)→ grant content access- Optionally
createAdmin(userId)→ full admin rights
Step 1. Create a user
mutation {
admin {
createUser(input: { email: "user@example.com" }) {
__typename
... on CreateUserPayload {
user { id email }
}
... on ErrorPayload { message }
}
}
}
The response includes the new user's id. You need it for every subsequent mutation.
Step 2. Get subgraph IDs
Subgraphs are created by syncing a note that declares one in its frontmatter:
---
subgraph: premium
---
Or multiple subgraphs at once:
---
subgraphs: [premium, beta]
---
After syncing from Obsidian, the subgraph appears in the system. List all subgraphs to get their IDs:
{ admin { allSubgraphs { nodes { id name } } } }
Step 3. Grant subgraph access
mutation {
admin {
createUserSubgraphAccess(input: {
userId: 6
subgraphIds: [1]
expiresAt: null
}) {
__typename
... on CreateUserSubgraphAccessPayload {
accesses { id userId subgraphId expiresAt }
}
... on ErrorPayload { message }
}
}
}
subgraphIds is an array — you can grant access to multiple subgraphs in one call. Omit expiresAt (or pass null) for permanent access. Pass an ISO 8601 timestamp for time-limited access.
Revoke subgraph access
mutation {
admin {
revokeUserSubgraphAccess(input: {
id: 12
reason: "Refunded"
}) {
__typename
... on RevokeUserSubgraphAccessPayload {
access {
id
revoke { createdAt reason by { email } }
}
}
... on ErrorPayload { message }
}
}
}
id is the access id from createUserSubgraphAccess or allUserSubgraphAccesses. The reason is required. The user loses the subgraph on their next request. The access stays in the list with its revoke date, and its page shows who revoked it, when and why. It cannot be revoked twice. In the admin panel: Users → Subgraph Accesses → open the access → enter a reason → Revoke.
Step 4. Make a user admin (optional)
mutation {
admin {
createAdmin(input: { userId: 6 }) {
__typename
... on CreateAdminPayload {
admin { id user { id email } }
}
... on ErrorPayload { message }
}
}
}
Admin status gives full access to the admin panel and all GraphQL mutations. Use it for team members who need to manage the site, not for regular subscribers.
Warning: notes without a subgraph are visible to all signed-in users
By default, any note that has no subgraph or subgraphs in its frontmatter is visible to every authenticated user — including users you grant access to only one specific subgraph.
flowchart TD
N[Note] --> Q{Has subgraph / subgraphs?}
Q -->|No, untagged| Leak[Visible to EVERY signed-in user]
Q -->|Yes: assigned subgraph| Gated[Visible only to granted users]
Leak -. apply frontmatter patch .-> Patched[Reassigned to notes_without_subgraph]
Patched --> Hidden[Nobody is granted it -> invisible to regular users]
Hidden --> AdminNote[Admins always see everything]
Gated --> AdminNote
Fix: hide unassigned notes behind a system subgraph.
Create a vault-based frontmatter patch (_private.md or similar):
---
type: frontmatter-patch
include: ["**"]
priority: 0
---
if !std.objectHas(meta, 'subgraph') && !std.objectHas(meta, 'subgraphs')
then meta + { subgraph: 'notes_without_subgraph' }
else meta
This assigns every untagged note to the notes_without_subgraph subgraph. Since you never grant anyone access to it, those notes are invisible to regular users. Admins always see everything regardless of subgraph assignment.
See en/user/frontmatter-patches for the full patch syntax.
Use case: share a single page with one person by email
You want to give someone access to one specific note — not the whole site, not a paid subgraph. The cleanest way:
1. Create the note
shares/alice/intro.md
Frontmatter:
---
subgraph: alice
---
Sync from Obsidian. The alice subgraph appears automatically.
2. Provision the user
# Create the user
mutation { admin { createUser(input: { email: "alice@example.com" }) {
... on CreateUserPayload { user { id } }
} } }
# Get the subgraph ID (look for name = "alice")
{ admin { allSubgraphs { nodes { id name } } } }
# Grant access
mutation { admin { createUserSubgraphAccess(input: { userId: <id>, subgraphIds: [<subgraphId>] }) {
... on CreateUserSubgraphAccessPayload { accesses { id } }
} } }
3. Send the user a message
Hi Alice,
I've shared a page with you: https://yoursite.com/shares/alice/intro
To open it, click the link and sign in with this email. You'll receive a one-time code — no password needed.
That's it. Alice sees only the alice subgraph. Add more notes to shares/alice/ as needed.
Alternative: share without requiring a login
If you don't need access control — just a hard-to-guess link — use free: true instead:
shares/a3f7c2d1-invoice-may.md
---
free: true
created_at: 2026-05-25
expires_at: 2026-06-25
---
Anyone with the URL can open it, no sign-in needed. The unguessable path acts as the access token. Suitable for one-off documents, invoices, drafts — anything where the link itself is the secret.
expires_at is metadata only for now — useful for future cleanup automation (cron that hides or deletes expired shares).
Lookup queries
If you don't know a user's id or a subgraph's id, look them up first.
# Find a user's ID by email
{ admin { allUsers { nodes { id email } } } }
# List subgraphs with their IDs
{ admin { allSubgraphs { nodes { id name } } } }
# Check a user's current accesses
{ admin { allUserSubgraphAccesses { nodes { id userId subgraphId expiresAt } } } }