How to Configure GraphQL in Totara
Set up a dedicated Totara GraphQL user and API client, validate token-based access, and use schema introspection to build reliable queries.
Get access to Totara
Ask the customer for:
-
Totara url
-
Totara admin username
-
Totara admin password
If the Totara website is hosted on one of our own servers, we should already have this information – don’t bother the customer in that case, but ask the lead engineer instead.
Create a GraphQL User in the Role of API User
-
Go to Home > Site administration > Users >Manage users and use the ‘Create user’ button to make a new user, e.g. ‘GraphQL User’. -
Go to Home > Site administration > Permissions > Assign system roles and assign the new user to the API User role.
Add a New API Client
-
Go to Home > Site administration > Development > API > API clients > Add client. -
Choose a name, and link the new client to the GraphQL user you just created.
-
Copy the client ID and secret to a secure place.
Test the API Client
Using the client ID and client secret, request a new token which you are going to use in a subsequent test call:
curl -X POST
'https://portal.woodgrove-logistics.example/totara/oauth2/token.php'
-H 'Content-Type: application/x-www-form-urlencoded'-d ‘grant_type=client_credentials&client_id=*******&client_secret=*******’
This should return the token, which you can use in your next test call:
curl 'https://portal.woodgrove-logistics.example/api/graphql.php'
-X POST
-H 'Authorization: Bearer ******YOUR_TOKEN******'
-H 'Content-Type: application/json'
-H 'Accept: application/json'
--data-binary '{
"query": "query { totara_webapi_status { status } }",
"variables": "{}"
}'This should return something like:
{"data":{"totara_webapi_status":{"status":"ok"}}}And to get the first 50 suspended users:
curl 'https://portal.woodgrove-logistics.example/api/graphql.php'
-X POST
-H 'Authorization: Bearer *****'
-H 'Content-Type: application/json'
-H 'Accept: application/json'
--data-binary '{
"query": "query core_user_users($query: core_user_users_query) {n
core_user_users(query: $query) {n items {n idn usernamen firstnamen
lastnamen emailn }n totaln next_cursorn }n}",
"variables": {
"query": {
"filters": {
"status": "SUSPENDED"
},
"pagination": {
"limit": 50
}
}
}
}'Introspection instead of Documentation
Please note that the Totara documentation for their GraphQL API (schema) is not very good. Instead, you should probably rely on querying the schema.
I have asked ChatGPT (o1) to explain a drill down I did to construct the test query mentioned above (get the first 50 suspended users):
Below is a concise recap of the introspection calls we made (and why):
1. Top-Level Queries
Call
{
__schema {
queryType {
fields {
name
}
}
}
}What It Does
-
Fetches all top-level query names in your Totara site’s GraphQL schema (e.g., core_user_users, core_course_courses, totara_webapi_status, etc.).
-
This helps you discover which queries actually exist in your environment (since Totara’s online documentation might not match your custom or specific build).
2. Introspecting a Specific Query Type
Call
{
__type(name: "core_user_users_query") {
name
inputFields {
name
type {
name
kind
ofType {
name
kind
}
}
}
}
}What It Does
-
Looks at the input object for the core_user_users query, which is named core_user_users_query.
-
Tells you which fields are available on that input type (e.g., filters, pagination, sort).
-
This shows how you must structure your “variables” when calling core_user_users.
3. Introspecting the Filters Object
Call
{
__type(name: "core_user_users_filters") {
name
inputFields {
name
type {
name
kind
ofType {
name
kind
}
}
}
}
}What It Does
-
Zeroes in on the filtering options for the core_user_users_query.
-
Reveals the fields you can use to filter users (e.g., status, since_timecreated, since_timemodified).
-
Important for building your “filters”: { … } object when requesting suspended/active users.
4. Introspecting the Pagination Object
Call
{
__type(name: "core_pagination_input") {
name
inputFields {
name
type {
name
kind
ofType {
name
kind
}
}
}
}
}What It Does
-
Shows the pagination fields for core_user_users_query (or other queries that accept pagination).
-
Typically returns fields like limit, page, cursor.
-
Tells you how to structure the “pagination” object in your variables (e.g., “pagination”: { “limit”: 50 }).
5. Checking an ENUM Type (User Status)
Call
{
__type(name: "core_user_user_status") {
name
kind
enumValues {
namedescription
}
}
}What It Does
-
Fetches the enum values (e.g., ACTIVE, SUSPENDED, ALL) used in the status filter.
-
Tells you the exact strings you can pass in the filters.status field (“SUSPENDED”, “ACTIVE”, etc.).
Why Introspection?
Totara’s GraphQL schema can vary by version or customization. Introspection lets you see exactly:
-
Which queries your site supports.
-
What input objects (filters, pagination, etc.) those queries expect.
-
What fields those input objects have.
-
Which types (e.g., scalars, enums, etc.) you need to pass in.
By combining that info, you build the correct GraphQL query + variables, ensuring you don’t get errors like “Field X is not defined.”
Summary
-
Top-Level Query Introspection (queryType) showed which queries exist in your Totara site.
-
Specific Type Introspection (__type(name: “xyz” revealed the exact input fields for those queries, including filters, pagination, and enums.
-
Each step helped us pin down precisely how to structure our “variables” so that Totara 18’s GraphQL endpoint would recognize our requests for suspended users (via status = “SUSPENDED”) and handle pagination (limit, page, etc.).
Here’s the basic logic behind why the query structure includes filters and pagination parameters (and sometimes sort) for core_user_users in Totara 18:
1. Strongly Typed Input Objects in GraphQL
-
Totara’s GraphQL schema is designed so that each query can accept one or more input objects rather than a bunch of individual arguments.
-
In this case, core_user_users expects a single parameter called query, which itself is an object of type **core_user_users_query**.
-
That core_user_users_query object is defined to have three sub-fields:
-
filters: core_user_users_filters
-
pagination: core_pagination_input
-
sort: [ someSortType ] (optional, as a list)
-
This is a common GraphQL design pattern: the query accepts an input object so that filtering, sorting, and pagination are all grouped in one place instead of being separate arguments.
2. The filters Sub-Object
-
The filters sub-object is specifically where you set conditions on which users to return.
-
From your introspection, you saw that filters has:
-
status: core_user_user_status (an enum with ALL, ACTIVE, SUSPENDED)
-
since_timecreated: core_date
-
since_timemodified: core_date
-
In other words, if you want only suspended users, you set:
"filters": {
"status": "SUSPENDED"
}If you want all users, you could set “status”: “ALL” or just leave the filters object empty (depending on how Totara is configured).
3. The pagination Sub-Object
-
The pagination sub-object deals with how many items you get back and/or which “page” you’re on:
-
limit (the maximum number of results to return)
-
page (a page number)
-
cursor (an alternative approach for “cursor-based” pagination)
-
In your working example:
"pagination": {
"limit": 50
}tells Totara to return up to 50 suspended users in one response. If there are more than 50, you’d need to check next_cursor (or specify page) to retrieve additional sets of results.
4. Bringing It All Together
So the GraphQL call to core_user_users looks like:
Query definition:
query core_user_users($query: core_user_users_query) {
core_user_users(query: $query) {
items {
id
username
firstname
lastname
email
}
total
next_cursor
}
}Variables (JSON object):
{
"query": {
"filters": {
"status": "SUSPENDED"
},
"pagination": {
"limit": 50
}
}
}-
filters → Which users do we want? (In your case, only suspended ones.)
-
pagination → How many to return per page? (Limit 50.)
sort → (Optional) If you want them in a certain order, you’d add
something like:
“sort”: [
{ "field": "firstname", "direction": "ASC" }
]-
if the schema supports that.
5. Why Totara Uses This Approach
-
Consistency: Many Totara GraphQL queries follow the pattern of a single “input object” with sub-fields for filtering, sorting, and pagination.
-
Scalability: As more filters or pagination modes are added in future versions, Totara can just extend the input type instead of adding separate arguments.
-
Clarity: The naming (e.g., filters, pagination, sort) makes it easy to see what each piece of input does.
In Short
-
filters is where you define the “condition” on users: suspended, active, etc.
-
pagination tells Totara how many results to return (limit) and which “page” or cursor to start from.
That’s why your GraphQL request must nest these properties inside a single query input object—it’s how Totara 18 structures the request to keep the API consistent and strongly typed.
Solin helps teams integrate Totara safely, from API design to production troubleshooting. Need help? Contact us.
Contact us