Quickbase Tutorials

Code Pages Developer Lab

A hands-on Quickbase API curriculum built from working Code Page examples.

Lesson 3

Code Page Here - PeoplePage_search.html

Query Quickbase with Search Criteria

Instead of retrieving every record and deciding what we want afterward, tell Quickbase which records we want before it returns anything.

CompleteREST QuerywhereQuery CriteriaExact Match

Why the Roadmap Changes Here

Lesson 2 proved an important point: Quickbase can return records to JavaScript, JavaScript can retain those records, and the browser can manipulate them without another API request.

Sorting was enough to prove that concept.

Rather than spend several lessons repeating client-side manipulation, Lesson 3 returns our attention to the Quickbase REST API itself.

The new question is:

Can we control which records Quickbase returns in the first place?

1. Control the Result Set Before It Returns

Our earlier REST examples could request the People table and receive a collection of records.

Lesson 3 adds criteria to that REST query.

Lesson 1B
Quickbase → return People records
Lesson 2
Quickbase → return records → JavaScript sorts them
Lesson 3
Search criteria → Quickbase → return only matches

2. Start with No Records

The page deliberately begins with an empty table.

document.addEventListener(
  "DOMContentLoaded",
  () => {
    document
      .getElementById("sortName")
      .addEventListener("click", sortByName);

    document
      .getElementById("searchButton")
      .addEventListener(
        "click",
        searchByFavoriteColor
      );
  }
);

The important detail is what is not there:

loadPeople();

The Code Page does not query Quickbase simply because the page opened.

Clarification: Why Was the Whole Table Appearing First?

During development, the search itself worked, but the page still returned the complete People dataset before the learner searched.

The reason was simple:

loadPeople();

was still running during DOMContentLoaded.

A REST query with no search condition naturally returned the normal result set. Removing that initial call changed the page from:

Page opens
↓
Query Quickbase
↓
Show everything

to:

Page opens
↓
No record query
↓
Wait for search criteria

3. Read the Search Value

const input =
  document.getElementById(
    "favoriteColorSearch"
  );

const color =
  input.value.trim();

This portion is ordinary browser JavaScript. The Quickbase-specific work begins when that value is converted into query criteria.

4. No Search Criteria Means No Query

if (!color) {
  const status =
    document.getElementById("status");

  peopleRecords = [];

  renderRecords(peopleRecords);

  status.classList.remove("error");

  status.textContent =
    "Enter a Favorite Color before searching.";

  return;
}

This creates a deliberate rule for the lesson:

No criteria = no Quickbase record query.

Clarification: Why Not Let a Blank Search Return Everything?

An earlier version called loadPeople() when the search box was empty. That made an empty search behave like “show all records.”

That behavior was valid JavaScript, but it contradicted the purpose of this lesson.

We are specifically learning to request only what is needed. Therefore a missing condition should not silently become a request for the entire table.

5. Build a Quickbase Query Expression

The new Quickbase-specific line is:

const where =
  `{${FIELD_IDS.favoriteColor}.EX.'${color}'}`;

If:

FIELD_IDS.favoriteColor = 8

and the learner enters:

Blue

the final Quickbase expression becomes:

{8.EX.'Blue'}

6. Read the Query from Left to Right

PartMeaning
8Quickbase Favorite Color Field ID.
EXExact-match comparison.
'Blue'The value the learner entered.
{8.EX.'Blue'}
↓
Field ID 8
↓
Favorite Color
↓
exactly equals
↓
Blue

7. Pass the Criteria into the REST Query

Once the Quickbase expression is built:

loadPeople(where);

loadPeople() now accepts the condition:

async function loadPeople(where = "") {

The default value preserves a reusable function signature, but the Lesson 3 interface does not automatically call it with an empty condition.

8. Add where to the Quickbase JSON Query

const query = {
  from: TABLE_DBID,

  where,

  select: [
    FIELD_IDS.recordId,
    FIELD_IDS.name,
    FIELD_IDS.age,
    FIELD_IDS.favoriteColor,
  ],

  options: {
    top: 200,
    skip: 0,
  },

  sortBy: [
    {
      fieldId: FIELD_IDS.name,
      order: "ASC",
    },
  ],
};

The major Lesson 3 addition is simply:

where,

But that small JSON property fundamentally changes which records Quickbase returns.

What Quickbase Receives

Searching for Blue effectively produces a query resembling:

{
  "from": "YOUR_TABLE_DBID",

  "where": "{8.EX.'Blue'}",

  "select": [
    3,
    6,
    7,
    8
  ],

  "options": {
    "top": 200,
    "skip": 0
  },

  "sortBy": [
    {
      "fieldId": 6,
      "order": "ASC"
    }
  ]
}

Quickbase evaluates the where condition and returns only records that qualify.

9. Lesson 2 and Lesson 3 Are Doing Different Work

ConceptLesson 2Lesson 3
Initial page loadQuery all People recordsQuery no records
User actionSort already-loaded recordsEnter search criteria
Where operation happensBrowser JavaScriptQuickbase
Additional REST requestNoYes
Returned datasetAll queried recordsOnly matching records

Clarification: This Is Not Client-Side Searching

Lesson 2 retained Quickbase records in peopleRecords and then manipulated that array directly.

Lesson 3 does something different.

User enters Blue
↓
{8.EX.'Blue'}
↓
POST /v1/records/query
↓
Quickbase evaluates the condition
↓
Only matching records return

This distinction was important enough that the interaction itself was changed to say Search Quickbase rather than merely Search.

10. Make the Experiment Obvious to the Learner

The finished Code Page contains an instructional interaction panel because searching is the central experiment in this lesson.

Try It Yourself

Search Quickbase by Favorite Color

The table starts empty. Enter a favorite color and click Search Quickbase to request only matching People records.

What happens when you search?
  1. You enter a favorite color.
  2. You click Search Quickbase.
  3. The Code Page sends your criteria to Quickbase.
  4. Quickbase finds only matching records.
  5. The matching records are returned and displayed.

11. The Complete Lesson 3 Flow

Page opens
↓
No Quickbase records requested
↓
User enters Blue
↓
FIELD_IDS.favoriteColor
↓
8
↓
{8.EX.'Blue'}
↓
query.where
↓
POST /v1/records/query
↓
QUICKBASE
↓
Quickbase evaluates criteria
↓
Only matching records return
↓
peopleRecords = records
↓
renderRecords(peopleRecords)

The Bigger Idea

Lesson 2 taught:

Quickbase gave us the data. Now JavaScript can control it.

Lesson 3 adds:

We can also control which data Quickbase gives us.

Those are two different kinds of control, and understanding that distinction gives us a much stronger foundation for more advanced Quickbase queries.

Complete Lesson 3 Source

The training source below preserves the REST architecture and Lesson 2 sorting behavior while emphasizing the Lesson 3 changes: delayed querying, Quickbase search criteria, and the REST where property.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />

    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0"
    />

    <title>People Table</title>

    <style>
      body {
        margin: 0;
        padding: 32px;
        background: #f4f6f8;
        color: #1f2933;
        font-family: Arial, Helvetica, sans-serif;
      }

      .page {
        max-width: 1000px;
        margin: 0 auto;
      }

      h1 {
        margin: 0 0 8px;
        color: #003366;
      }

      .description {
        margin: 0 0 24px;
        color: #5b6775;
      }

      .status {
        margin-bottom: 16px;
        padding: 12px 14px;
        border: 1px solid #cfd8e3;
        border-radius: 6px;
        background: #ffffff;
      }

      .status.error {
        border-color: #a61c1c;
        color: #7f0000;
        background: #fff1f1;
      }

      .table-container {
        overflow-x: auto;
        border: 1px solid #d7dee7;
        border-radius: 8px;
        background: #ffffff;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
      }

      table {
        width: 100%;
        border-collapse: collapse;
      }

      th,
      td {
        padding: 12px 14px;
        border-bottom: 1px solid #e5e9ef;
        text-align: left;
      }

      th {
        background: #003366;
        color: #ffffff;
      }

      tbody tr:hover {
        background: #f3f7fb;
      }

      .color-value {
        display: inline-flex;
        align-items: center;
        gap: 8px;
      }

      .color-swatch {
        width: 18px;
        height: 18px;
        border: 1px solid #7f8c99;
        border-radius: 4px;
      }

      .search-controls {
        margin-bottom: 24px;
        padding: 24px;
        border: 1px solid #b8c9da;
        border-radius: 10px;
        background: #ffffff;
        box-shadow: 0 4px 12px rgba(0, 51, 102, 0.08);
      }

      .search-intro {
        margin-bottom: 20px;
      }

      .search-eyebrow {
        margin: 0 0 6px;
        color: #1f5c99;
        font-size: 13px;
        font-weight: 700;
        letter-spacing: 0.08em;
        text-transform: uppercase;
      }

      .search-intro h3 {
        margin: 0 0 8px;
        color: #003366;
        font-size: 22px;
      }

      .search-intro p {
        max-width: 760px;
        margin: 0;
        color: #52606d;
        line-height: 1.6;
      }

      .search-action {
        display: flex;
        align-items: end;
        gap: 12px;
        margin-bottom: 20px;
      }

      .search-field {
        flex: 1;
        max-width: 420px;
      }

      .search-field label {
        display: block;
        margin-bottom: 6px;
        font-weight: 700;
        color: #263746;
      }

      .search-field input {
        box-sizing: border-box;
        width: 100%;
        padding: 11px 12px;
        border: 1px solid #9fb3c8;
        border-radius: 6px;
        background: #ffffff;
        color: #1f2933;
        font: inherit;
      }

      .search-field input:focus {
        outline: 3px solid rgba(31, 92, 153, 0.15);
        border-color: #1f5c99;
      }

      .search-action button {
        padding: 11px 18px;
        border: 1px solid #174a7c;
        border-radius: 6px;
        background: #1f5c99;
        color: #ffffff;
        font: inherit;
        font-weight: 700;
        cursor: pointer;
      }

      .search-action button:hover {
        background: #174a7c;
      }

      .search-steps {
        padding: 16px 18px;
        border-left: 4px solid #1f5c99;
        background: #f3f7fb;
        color: #364957;
      }

      .search-steps ol {
        margin: 10px 0 0;
        padding-left: 22px;
      }

      @media (max-width: 640px) {
        .search-action {
          align-items: stretch;
          flex-direction: column;
        }

        .search-field {
          max-width: none;
        }

        .search-action button {
          width: 100%;
        }
      }
    </style>
  </head>

  <body>
    <main class="page">
      <h1>People</h1>

      <h2>
        Lesson 3 — Search Quickbase → Return Matching Records → Render Results
      </h2>

      <p class="description">
        Request only the People records that match the search criteria.
      </p>

      <div id="status" class="status">
        Enter a Favorite Color and click Search.
      </div>

      <div class="search-controls">
        <div class="search-intro">
          <p class="search-eyebrow">
            Try It Yourself
          </p>

          <h3>
            Search Quickbase by Favorite Color
          </h3>

          <p>
            The table below starts empty.
            Enter a favorite color and click
            <strong> Search Quickbase</strong>
            to request only matching People records.
          </p>
        </div>

        <div class="search-action">
          <div class="search-field">
            <label for="favoriteColorSearch">
              Favorite Color
            </label>

            <input
              type="text"
              id="favoriteColorSearch"
              placeholder="Example: Blue"
            />
          </div>

          <button
            type="button"
            id="searchButton"
          >
            Search Quickbase
          </button>
        </div>

        <div class="search-steps">
          <strong>
            What happens when you search?
          </strong>

          <ol>
            <li>You enter a favorite color.</li>
            <li>You click Search Quickbase.</li>
            <li>The search criteria is sent to Quickbase.</li>
            <li>Quickbase finds matching records.</li>
            <li>Only those records are returned.</li>
          </ol>
        </div>
      </div>

      <div class="table-container">
        <table>
          <thead>
            <tr>
              <th>Record ID</th>

              <th>
                <button
                  type="button"
                  id="sortName"
                >
                  Name
                </button>
              </th>

              <th>Age</th>
              <th>Favorite Color</th>
            </tr>
          </thead>

          <tbody id="peopleRows"></tbody>
        </table>
      </div>
    </main>

    <script>
      /*
       * ============================================================
       * QUICKBASE CONNECTION INFORMATION
       * ============================================================
       */

      const REALM =
        window.location.hostname.includes("quickbase.com")
          ? window.location.hostname
          : "YOUR_REALM.quickbase.com";

      const TABLE_DBID =
        "YOUR_TABLE_DBID";

      const APP_TOKEN = "";

      let peopleRecords = [];

      let nameSortDirection = "asc";


      const FIELD_IDS = {
        recordId: 3,
        name: 6,
        age: 7,
        favoriteColor: 8,
      };


      function createCell(value) {
        const cell =
          document.createElement("td");

        cell.textContent = value;

        return cell;
      }


      function createColorCell(colorName) {
        const cell =
          document.createElement("td");

        const wrapper =
          document.createElement("span");

        wrapper.className =
          "color-value";

        const swatch =
          document.createElement("span");

        swatch.className =
          "color-swatch";

        swatch.style.backgroundColor =
          colorName || "transparent";

        const label =
          document.createElement("span");

        label.textContent =
          colorName;

        wrapper.appendChild(swatch);
        wrapper.appendChild(label);

        cell.appendChild(wrapper);

        return cell;
      }


      async function readQuickbaseResponse(response) {
        const rawBody =
          await response.text();

        try {
          return {
            parsed: JSON.parse(rawBody),
            rawBody,
          };
        } catch {
          return {
            parsed: null,
            rawBody,
          };
        }
      }


      function getFieldValue(record, fieldId) {
        return record[String(fieldId)]?.value ?? "";
      }


      function renderRecords(records) {
        const tableBody =
          document.getElementById("peopleRows");

        tableBody.replaceChildren();

        for (const record of records) {
          const recordId =
            getFieldValue(
              record,
              FIELD_IDS.recordId
            );

          const name =
            getFieldValue(
              record,
              FIELD_IDS.name
            );

          const age =
            getFieldValue(
              record,
              FIELD_IDS.age
            );

          const favoriteColor =
            getFieldValue(
              record,
              FIELD_IDS.favoriteColor
            );

          const row =
            document.createElement("tr");

          row.appendChild(
            createCell(recordId)
          );

          row.appendChild(
            createCell(name)
          );

          row.appendChild(
            createCell(age)
          );

          row.appendChild(
            createColorCell(favoriteColor)
          );

          tableBody.appendChild(row);
        }
      }


      async function getTemporaryToken() {
        const headers = {
          "QB-Realm-Hostname": REALM,
        };

        if (APP_TOKEN.trim()) {
          headers["QB-App-Token"] =
            APP_TOKEN;
        }

        const response = await fetch(
          `https://api.quickbase.com/v1/auth/temporary/${TABLE_DBID}`,
          {
            method: "GET",
            credentials: "include",
            headers,
          },
        );

        const {
          parsed,
          rawBody,
        } = await readQuickbaseResponse(
          response
        );

        if (!response.ok) {
          const details =
            parsed
              ? JSON.stringify(parsed)
              : rawBody;

          throw new Error(
            `HTTP ${response.status}: ${details}`
          );
        }

        if (!parsed?.temporaryAuthorization) {
          throw new Error(
            "Quickbase did not return temporaryAuthorization."
          );
        }

        return parsed.temporaryAuthorization;
      }


      /*
       * ============================================================
       * LESSON 2 FUNCTION RETAINED
       * ============================================================
       *
       * Sorting still operates on the records that Quickbase has
       * already returned.
       *
       * This lets us directly compare client-side manipulation with
       * Lesson 3's server-side query.
       */
      function sortByName() {
        peopleRecords.sort((a, b) => {
          const nameA =
            getFieldValue(
              a,
              FIELD_IDS.name
            );

          const nameB =
            getFieldValue(
              b,
              FIELD_IDS.name
            );

          const comparison =
            nameA.localeCompare(nameB);

          return nameSortDirection === "asc"
            ? comparison
            : -comparison;
        });

        const sortButton =
          document.getElementById("sortName");

        if (nameSortDirection === "asc") {
          sortButton.textContent =
            "Name ↑";

          nameSortDirection =
            "desc";
        } else {
          sortButton.textContent =
            "Name ↓";

          nameSortDirection =
            "asc";
        }

        renderRecords(
          peopleRecords
        );
      }


      /*
       * ============================================================
       * LESSON 3 — BUILD SEARCH CRITERIA
       * ============================================================
       *
       * Unlike Lesson 2, we are NOT searching peopleRecords.
       *
       * We read the learner's search value and turn it into a
       * Quickbase query expression.
       */
      function searchByFavoriteColor() {
        const input =
          document.getElementById(
            "favoriteColorSearch"
          );

        const color =
          input.value.trim();


        /*
         * ==========================================================
         * NO CRITERIA = NO QUICKBASE QUERY
         * ==========================================================
         *
         * An empty search should not mean "return everything."
         *
         * Lesson 3 intentionally starts with no records and requires
         * actual search criteria before querying Quickbase.
         */
        if (!color) {
          const status =
            document.getElementById(
              "status"
            );

          peopleRecords = [];

          renderRecords(
            peopleRecords
          );

          status.classList.remove(
            "error"
          );

          status.textContent =
            "Enter a Favorite Color before searching.";

          return;
        }


        /*
         * ==========================================================
         * QUICKBASE WHERE EXPRESSION
         * ==========================================================
         *
         * Favorite Color is Field ID 8.
         *
         * If the learner enters:
         *
         *     Blue
         *
         * this becomes:
         *
         *     {8.EX.'Blue'}
         *
         * EX means exact match.
         *
         * This expression is sent to Quickbase. The browser is not
         * filtering an already-loaded dataset.
         */
        const where =
          `{${FIELD_IDS.favoriteColor}.EX.'${color}'}`;


        /*
         * Run a NEW Quickbase REST query using the criteria.
         */
        loadPeople(where);
      }


      /*
       * ============================================================
       * LESSON 3 — QUERY QUICKBASE USING AN OPTIONAL WHERE CLAUSE
       * ============================================================
       *
       * Lesson 1B introduced this REST records query.
       *
       * Lesson 3 adds one major capability:
       *
       *     where
       *
       * The caller can now tell Quickbase which records should
       * qualify before Quickbase returns the result set.
       */
      async function loadPeople(where = "") {
        const startTime =
          performance.now();

        const status =
          document.getElementById(
            "status"
          );

        try {
          const temporaryToken =
            await getTemporaryToken();


          const query = {
            from: TABLE_DBID,


            /*
             * ======================================================
             * QUICKBASE QUERY FILTER
             * ======================================================
             *
             * This is the new Lesson 3 property.
             *
             * Example:
             *
             *     where: "{8.EX.'Blue'}"
             *
             * Quickbase evaluates this condition before returning
             * records.
             */
            where,


            select: [
              FIELD_IDS.recordId,
              FIELD_IDS.name,
              FIELD_IDS.age,
              FIELD_IDS.favoriteColor,
            ],

            options: {
              top: 200,
              skip: 0,
            },

            sortBy: [
              {
                fieldId:
                  FIELD_IDS.name,

                order: "ASC",
              },
            ],
          };


          const response = await fetch(
            "https://api.quickbase.com/v1/records/query",
            {
              method: "POST",

              headers: {
                "QB-Realm-Hostname":
                  REALM,

                Authorization:
                  `QB-TEMP-TOKEN ${temporaryToken}`,

                "Content-Type":
                  "application/json",
              },

              body:
                JSON.stringify(query),
            },
          );


          const {
            parsed,
            rawBody,
          } = await readQuickbaseResponse(
            response
          );


          if (!response.ok) {
            const details =
              parsed
                ? JSON.stringify(parsed)
                : rawBody;

            throw new Error(
              `HTTP ${response.status}: ${details}`
            );
          }


          const records =
            Array.isArray(parsed?.data)
              ? parsed.data
              : [];


          if (!Array.isArray(parsed?.data)) {
            throw new Error(
              "REST response did not include a data array."
            );
          }


          if (records.length === 0) {
            peopleRecords = [];

            renderRecords(
              peopleRecords
            );

            const endTime =
              performance.now();

            const renderTime =
              endTime - startTime;

            status.classList.remove(
              "error"
            );

            status.textContent =
              `REST query returned 0 record(s) in ${renderTime.toFixed(2)} ms.`;

            return;
          }


          /*
           * Keep the records returned by THIS search.
           *
           * Sorting from Lesson 2 can still operate on them afterward.
           */
          peopleRecords =
            records;


          renderRecords(
            peopleRecords
          );


          const endTime =
            performance.now();

          const renderTime =
            endTime - startTime;


          status.classList.remove(
            "error"
          );

          status.textContent =
            `REST query returned ${records.length} record(s) in ${renderTime.toFixed(2)} ms.`;

        } catch (error) {
          console.error(error);

          status.classList.add(
            "error"
          );

          status.textContent =
            `REST query failed: ${error.message}`;
        }
      }


      /*
       * ============================================================
       * IMPORTANT LESSON 3 CHANGE
       * ============================================================
       *
       * Notice that loadPeople() is NOT called here.
       *
       * The page begins with an empty result table.
       *
       * Quickbase is queried only after the learner supplies search
       * criteria and clicks Search Quickbase.
       */
      document.addEventListener(
        "DOMContentLoaded",
        () => {
          document
            .getElementById(
              "sortName"
            )
            .addEventListener(
              "click",
              sortByName
            );

          document
            .getElementById(
              "searchButton"
            )
            .addEventListener(
              "click",
              searchByFavoriteColor
            );
        }
      );
    </script>
  </body>
</html>

Lesson 3 Complete

You should now understand that a Quickbase REST query does not have to retrieve the entire available dataset.

A where expression can tell Quickbase which records qualify before the response is returned.

Lesson 2 gave us control after the data returned. Lesson 3 gives us control over what returns.