Querying, Searching, and Displaying Form Submissions

Querying, Searching, and Displaying Form Submissions

Once users start submitting forms in your portal, you'll likely need to fetch and display those submissions, whether it's for managers reviewing requests, admins auditing data, or users viewing submission history.

Kinetic's React SDK gives you full control to search for and display submissions using the searchSubmissions function and the defineKqlQuery builder.


Using searchSubmissions to Retrieve Form Data

The searchSubmissions is a helper function for querying submissions. You can also make a call to our API directly following our API docs here: https://docs.kineticdata.com/v6.1/reference/listformsubmissionsaspost

What is searchSubmissions?

searchSubmissions is an async function that queries the Kinetic Platform for form submission data. You can use it to:

  • Retrieve all submissions in a specific Kapp
  • Filter submissions by status, field value, or metadata
  • Sort and paginate large result sets
  • Include nested data like field values, form details, parent relationships, etc.

Parameters for searchSubmissions

NameTypeDescription
kappstringRequired. The slug of the Kapp to search in.
formstringForm slug to specify submissions for a specific form.
searchSearchMeta{}The search metadata derived from the SubmissionSearch
getbooleanIndicates whether the query should get the GET method instead of the default POST method; defaults to false.
limitnumberThe number of records to return. Defaults to 25. Max 1000.
pageTokenstringThe pageToken retrieves a subsequent page of a query (the search will return a nextPageToken if there is a next page)

What is SearchMeta?

The search parameter accepts a SearchMeta object. This object defines how the query will behave, what to inclue, how to filter, and how to sort.

PropertyTypeDescription
qstringA qualification to use when searching for submissions.
includestring[]details, activities, children, descendants, origin, parent, type, values, values.raw, values[FIELD NAME], form, form.{any form include property}Array of properties to include in each submission.
orderBystringComma separated list of submission properties and fields to sort the response by; all parts of the orderBy except the optionally specified timeline must be included in all indexes used by the query.
direction"ASC"| "DESC"Sort direction (defaults to "DESC")

Resolves

When searchSubmissions completes, it returns a resolved object with the following structure:

  • submissionsSubmission[]
    An array of submission objects that match the search criteria. Each object includes metadata and any included properties (like values, details, or form).

  • errorErrorResponse (optional)
    If the request fails, this field will contain error details such as the HTTP status code, message, and context. Always check for this when handling responses to gracefully manage failures.

Tip: A successful call will return submissions. If something goes wrong (like a 403 error or malformed query), the error field will be populated instead.

Basic Example: Search Submissions by Type and Status

Once you've defined your Kapp and understand the structure of a searchSubmissions call, you can begin retrieving relevant data. In this example, we search for all submissions of a specific type with an active status—this is a common pattern used for request queues, dashboards, or task lists.

import { searchSubmissions } from '@kineticdata/react';
import { useEffect, useState } from 'react';

const ExampleComponent = () => {
  const [submissions, setSubmissions] = useState([]);

  useEffect(() => {
    searchSubmissions({
      kapp: 'services',
      search: {
        q: 'type = "Service" AND values[Status] IN ("Active", "Pending")',
        include: ['values', 'details'],
        orderBy: 'createdAt',
        direction: 'DESC',
      },
    }).then(({ submissions }) => {
      setSubmissions(submissions);
    });
  }, []);

  return (
    <ul>
      {submissions.map((submission) => (
        <li key={submission.id}>
          {submission.values['Request Title']} — {submission.values['Status']}
        </li>
      ))}
    </ul>
  );
};

Useful include Values

By default, a submission result only contains minimal data. To enrich your results with additional context, such as form fields, timestamps, or relationships, you can use the include option in your searchSubmissions call.

These include values tell the API which related properties to return with each submission. This is especially helpful when displaying submissions in the UI, performing audits, or building dashboards with field-specific logic.

Below are the most commonly used include values:

Include ValueDescription
valuesField values submitted by the user
detailsMetadata such as ID, createdBy, timestaps
formThe form that the submission belongs to
parent, originRelated submissions
type, activitiesInternal tracking or workflow context

When to Use searchSubmissions

  • Displaying form results in a table or dashboard
  • Filtering and reviewing requests
  • Pulling submission data for reports or exports
  • Powering manager or admin views

Build Powerful Filters with defineKqlQuery

To build complex filters for use with searchSubmissions, you'll often need to construct a KQL qualification string. Manually writing these strings is error-prone and difficult to manage. That’s where defineKqlQuery comes in.

What is defineKqlQuery?

defineKqlQuery is a utility that helps you build query strings using a readable, chainable syntax. It returns a compiled query function that you can call with real values to generate a KQL string.

This makes it easy to dynamically build queries based on user input, roles, or business logic.

Example

const query = defineKqlQuery()
  .equals('type', 'type')                            
  .in('coreState', 'coreState')                      
  .or()                                              
    .equals('values[Requested For]', 'username')     
    .equals('values[Requested By]', 'username')      
  .end()                                            
  .startsWith('values[Status]', 'status')           
  .end();                                            
const result = query({
  type: 'Service',
  coreState: ['Draft', 'Submitted'],
  username: 'allan.allbrook',
  status: 'A',
});

 

Result

type = "Service"
AND coreState IN ("Draft", "Submitted")
AND (
  values[Requested For] = "allan.allbrook"
  OR values[Requested By] = "allan.allbrook"
)
AND values[Status] =* "A"

Why Use It?

  • Avoid manual string formatting
  • Build flexible and reusable filters
  • Support dynamic conditions with multiple values or optional fields
  • Combine AND / OR logic safely

Supported Methods

The methods below are used to define your query structure. Each method accepts a field name (e.g., values[Status]) and a key string, which corresponds to a property in the argument object passed to the compiled query function.

When the compiled function is executed, it replaces each key with its actual value. If a key is missing or its value is empty, that part of the query is automatically omitted unless you explicitly pass { strict: true } to force inclusion of null or empty values.

This gives you flexible control over dynamic search logic without having to write conditionals manually.

MethodDescription
and() -> SearchBuilder{}Begins an and context.
or() -> SearchBuilder{}Begins an or context.
end() -> SearchBuilder{}Closes the last and or or context, or compiles the query and returns the query function.
equals(field, value, strict) -> SearchBuilder{}Adds an equality check.
in(field, values, strict) -> SearchBuilder{}Adds an in statement (accepts an array of values).
startsWith(field, value) -> SearchBuilder{}Adds a starts with check.
between(field, minValue, maxValue, strict) -> SearchBuilder{}Adds a between check.
greaterThan(field, value, strict) -> SearchBuilder{}Adds a greater than check.
greaterThanOrEquals(field, value, strict) -> SearchBuilder{}Adds a greater than or equals check.
lessThan(field, value, strict) -> SearchBuilder{}Adds a less than check.
lessThanOrEquals(field, value, strict) -> SearchBuilder{}Adds a less than or equals check.

Best Practices

  • Always call .end() after .or() or .and() blocks
  • Reuse query builders for different inputs
  • Match key names used in the query with the object passed to the compiled function
  • Combine with searchSubmissions for highly targeted filtering

Displaying Submissions in Your Portal

Once you've queried for submissions using searchSubmissions, the next step is rendering those results in a user-friendly way. Whether you're building an admin dashboard, a request history view, or a manager approval queue, displaying submissions in your portal involves iterating over the returned data and formatting it for your use case.

Step 1: Run a Submission Query

Start by calling searchSubmissions with the appropriate filters and includes:

import { searchSubmissions } from '@kineticdata/react';
import { useEffect, useState } from 'react';

const SubmissionList = () => {
  const [submissions, setSubmissions] = useState([]);

  useEffect(() => {
    searchSubmissions({
      kapp: 'services',
      search: {
        q: 'values[Status] = "Open"',
        include: ['values', 'details'],
        orderBy: 'createdAt',
        direction: 'DESC',
      },
    }).then(({ submissions }) => setSubmissions(submissions));
  }, []);

  return (
    <div>
      <h2>Open Requests</h2>
      {submissions.length === 0 ? (
        <p>No submissions found.</p>
      ) : (
        <ul>
          {submissions.map((sub) => (
            <li key={sub.id}>
              <strong>{sub.values['Request Title']}</strong> — submitted by{' '}
              {sub.values['Requested By']} on{' '}
              {new Date(sub.createdAt).toLocaleDateString()}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

 

Step 2: Choose the Right Layout for Your Use Case

Depending on your audience and goals, you may want to display submissions in different layouts:

  • Table format for detailed data views or reports
  • Cards or tiles for a visual dashboard
  • Timelines or activity feeds for request progress

Here’s a basic table-style layout:

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Status</th>
      <th>Requested By</th>
      <th>Submitted On</th>
    </tr>
  </thead>
  <tbody>
    {submissions.map((s) => (
      <tr key={s.id}>
        <td>{s.values['Request Title']}</td>
        <td>{s.values['Status']}</td>
        <td>{s.values['Requested By']}</td>
        <td>{new Date(s.createdAt).toLocaleDateString()}</td>
      </tr>
    ))}
  </tbody>
</table>

 

Step 3: Add Interactivity

To create a richer, more useful UI, consider enhancing your submission display with:

  • Clickable rows to view or edit a submission
  • Status badges for quick visual interpretation
  • Pagination or infinite scroll for large result sets
  • Filter controls to support user-driven queries

Tips

  • Use include: ['values', 'details'] in your query to access field values and metadata
  • Always check for submissions.length === 0 to gracefully handle empty results
  • Use submission.form.name or submission.form.slug when displaying results from multiple forms