Read in:
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

  1. Create a note with subgraph: name frontmatter → sync → subgraph appears
  2. createUser(email) → get userId
  3. allSubgraphs → get subgraphIds
  4. createUserSubgraphAccess(userId, subgraphIds) → grant content access
  5. 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 } } } }