All Field Notes
Quickbase ApiSchemaRelationships

Discovering Quickbase Schema Through the API

From application context to metadata-driven developer tools

Explore table, field, and relationship metadata, then see how discovered identities guide a query builder—and where the evidence stops.

Darian Ross investigation

A record-query example can begin with a table ID and a few hard-coded Field IDs. A reusable developer tool needs a better starting point: ask Quickbase what tables, fields, and relationships exist in the current application. My Schema Explorer began with that question. The connected Query & Jinja Workbench then showed why discovery matters: those identifiers and field definitions can drive the choices a query builder presents.

Start with application context, then select a table

The Explorer is a Quickbase Code Page. It obtains the realm from the current hostname and extracts a DBID from a /db/{dbid} URL. That extraction is a convention of this implementation, not proof that every Quickbase URL points at an application. Confirm that the displayed value is the app DBID before requesting its table list. A table URL or a different URL structure requires a different source of app context.

Read the DBID from the Code Page URL JavaScript
function getAppDbid() {
  const match = window.location.pathname.match(/\/db\/([^/]+)/);
  if (!match) {
    throw new Error("Could not determine the App DBID from the Quickbase URL.");
  }
  return match[1];
}

After loading the app’s tables, the Explorer adds options whose values are table.id and whose text contains table.name. A selection therefore carries the actual DBID, not an inferred ID derived from a human-readable name. Read the current selection when an inspection button is clicked rather than capturing a placeholder value during script startup.

Populate a table selector from metadata JavaScript
tableSelect.replaceChildren();
for (const table of data) {
  const option = document.createElement("option");
  option.value = table.id;
  option.textContent = `${table.name} — ${table.id}`;
  tableSelect.appendChild(option);
}

Use the endpoint’s required identifiers

The Explorer’s four GET operations. These are metadata requests, not record queries.
PurposeEndpointRequired context
List app tables/v1/tables?appId={appId}App DBID
Inspect one table/v1/tables/{tableId}?appId={appId}Table DBID + App DBID
List table fields/v1/fields?tableId={tableId}Table DBID
Inspect child-side relationships/v1/tables/{tableId}/relationshipsTable DBID
The Explorer’s four GET operations. These are metadata requests, not record queries.
Download endpoint map (CSV)Download Schema Explorer (HTML)Download connected Workbench (HTML)Download illustrative schema (JSON)

The implementation obtains an app-scoped temporary token for app/table metadata and a selected-table token for field and relationship inspection. The token helper includes the browser session and conditionally sends QB-App-Token when configured. Do not assume one token can authorize every endpoint: check the requested DBID scope, the user’s permissions, and the actual error response.

Build a fields request using the current selection JavaScript
const tableDbid = getSelectedTableDbid();
const token = await getTemporaryToken(tableDbid);
const response = await fetch(
  `https://api.quickbase.com/v1/fields?tableId=${tableDbid}`,
  { headers: {
    "QB-Realm-Hostname": REALM,
    Authorization: `QB-TEMP-TOKEN ${token}`,
  } },
);
const fields = await response.json();
if (!response.ok) {
  throw new Error(`Fields HTTP ${response.status}: ${JSON.stringify(fields)}`);
}

Interpret identities within their scope

A table DBID identifies a table. A Field ID identifies a field within that table. Two tables can both have Field ID 6 while those fields have different labels and meanings. A builder must therefore keep table ownership with each selected field; carrying only the number loses necessary context. Table metadata also reports keyFieldId. Read that value instead of assuming every table uses Record ID# as its key.

Instructional simulation · schema explorer

Discover a table, then choose its fields

Illustrative People and Departments metadata adapted from the teaching examples. Switch tables and select fields to construct a request preview. No live Quickbase calls.

Simulation data json
{
  "schema": {
    "appId": "example-app",
    "tables": [
      {
        "id": "bqpeople1",
        "name": "People",
        "keyFieldId": 3,
        "fields": [
          {
            "id": 3,
            "label": "Record ID#",
            "fieldType": "recordid"
          },
          {
            "id": 6,
            "label": "Name",
            "fieldType": "text"
          },
          {
            "id": 7,
            "label": "Age",
            "fieldType": "numeric"
          },
          {
            "id": 9,
            "label": "Related Department",
            "fieldType": "numeric"
          },
          {
            "id": 10,
            "label": "Department Name",
            "fieldType": "text"
          }
        ]
      },
      {
        "id": "bqdepart1",
        "name": "Departments",
        "keyFieldId": 3,
        "fields": [
          {
            "id": 3,
            "label": "Record ID#",
            "fieldType": "recordid"
          },
          {
            "id": 6,
            "label": "Department Name",
            "fieldType": "text"
          }
        ]
      }
    ],
    "relationships": [
      {
        "parentTableId": "bqdepart1",
        "childTableId": "bqpeople1",
        "referenceFieldId": 9,
        "referenceFieldLabel": "Related Department"
      }
    ]
  }
}

The simulation keeps Name in People separate from Department Name in Departments even though both use FID 6. The generated select list belongs to the table named in from. This is the same ownership discipline a real metadata-driven builder needs.

Read relationships as structure, not as a join command

Illustrative child-side relationship fragment JSON
{
  "id": 9,
  "parentTableId": "bqdepart1",
  "childTableId": "bqpeople1",
  "foreignKeyField": {
    "id": 9, "label": "Related Department", "type": "numeric"
  },
  "lookupFields": [
    { "id": 10, "label": "Department Name", "type": "text" }
  ],
  "summaryFields": [],
  "isCrossApp": false
}

Here Departments is the parent and People is the child. The reference field is on People; the lookup field exposes a related value on that child table. The fragment is illustrative rather than a captured response. The relationship ID corresponds to the child reference Field ID in the API’s relationship model; it is not a parent record ID.

The connected Workbench builds a relationship graph from cached tables, fields, and relationship definitions. That graph helps identify field ownership and potential paths. It does not make a records-query endpoint accept an arbitrary SQL-style join. Its live executor accepts one-table plans; conditions spanning multiple owner tables produce a separate plan rather than a single executable native query. It also blocks execution when Pipelines Jinja still needs runtime values.

Preserve raw responses before adding explanations

Raw metadata is valuable because it preserves the API’s own property names and identifiers. The newer Explorer also provides a convenience function that annotates relationships with table names from its cached table list. Those names are derived locally. They should not be mistaken for properties supplied by that API response.

Enrich a response without overwriting its original object JavaScript
function addRelationshipTableNames(data) {
  return {
    ...data,
    relationships: (data.relationships || []).map(relationship => ({
      ...relationship,
      parentTableName: getTableNameByDbid(relationship.parentTableId),
      childTableName: getTableNameByDbid(relationship.childTableId),
    })),
  };
}

Understand what the combined snapshot contains

The Explorer’s combined snapshot packages app context, selected-table identity, table properties, fields, and enriched relationships. It fetches the three metadata responses in parallel after acquiring authorization. That is a convenient document assembled by the tool, not a dedicated Quickbase snapshot endpoint or a guaranteed atomic view of the schema.

Keep coverage visible in your own snapshot format JavaScript
// Suggested tool-owned envelope, not a Quickbase response schema.
const snapshot = {
  appId: APP_DBID,
  tableId: tableDbid,
  capturedAt: new Date().toISOString(),
  table: tableData,
  fields: fieldsData,
  relationshipsRaw: relationshipsData,
  relationshipsEnriched: addRelationshipTableNames(relationshipsData),
};

Use metadata to guide a query builder

The connected Workbench uses discovered field labels and types to populate choices, while keeping IDs in the generated request. It groups field types into families to suggest operators. That grouping is a tool heuristic, not proof that every offered operator is valid for every field configuration. Its public companion analyzes query/Jinja text without schema: it can inspect syntax and references but cannot confirm that a field exists, authorize a request, or execute Jinja.

What the two workbench editions can establish.
EditionAvailable inputPractical boundary
Public browser analyzerPasted query/Jinja textStatic analysis; no schema or live execution
Connected Quickbase Code PageText plus discovered metadata and authorized requestsRuntime results apply to the executed query and account; cross-table plans and unresolved Jinja require more work
What the two workbench editions can establish.

Controlled investigation

Test a schema-driven decision against your app

Prediction: Changing the selected table should change both the available fields and the table DBID used by the generated request.

  1. Install the Explorer as an app-level Quickbase Code Page, confirm the displayed realm/app DBID, and configure APP_TOKEN only if your app requires it.
  2. Load the table list. Inspect one table, its fields, and its child-side relationships. Preserve raw JSON separately from annotations.
  3. Choose another table and inspect the same metadata. Compare a repeated Field ID across the tables and identify its different ownership.
  4. In the connected Workbench, build a simple one-table query using discovered fields. Inspect the request before execution and compare the returned identities and values with a Quickbase report.
  5. Test an unavailable table or insufficient permission deliberately. Inspect the failing request and status before deciding whether the problem is schema, authorization, or JavaScript.

Interpretation: The tool becomes dependable when its UI choices, request identifiers, and returned evidence agree. An attractive builder alone does not establish that agreement.

Schema discovery gave my Code Pages a reusable starting point. The important result was not merely a larger JSON document: it was a clear path from application context to table identity, field ownership, relationship definitions, and a request I could inspect. That path is what makes a developer tool explainable as it grows.