Skip to content

Queries ​

Queries define reusable ways to retrieve records from a Project Database table. Use them when a Project needs a consistent set of records selected, ordered, or shaped for a particular purpose.

For example, the same Registrations table might support queries for registrations associated with one event, pending registrations, or records awaiting review. Each query describes a particular retrieval pattern without requiring every Page or workflow to define that pattern again.

Named queries can be used by Page Flow, Cloud Flow, Data Table Widgets, Data Design rendering workflows, custom JavaScript using ioLoop APIs, and supported AI Agent capabilities.

When to Use a Named Query ​

Use a named query when:

  • A Page needs only a subset of a table's records.
  • Several application components need the same retrieval pattern.
  • The user chooses a filter, category, customer, event, or other input at runtime.
  • Records need consistent sorting or a defined set of returned fields.
  • Results should include related data from another table through a configured join.
  • A workflow should retrieve matching records without repeating the same query criteria in several places.

Typical query purposes include:

QueryExample Purpose
UpcomingEventsRetrieve upcoming events for a listing Page.
RegistrationsByEventRetrieve registrations for a selected event.
OpenSupportTicketsRetrieve tickets that still need attention.
OrdersByCustomerRetrieve orders for a particular customer.
PendingApprovalsRetrieve items awaiting administrative review.

These names are examples, not preconfigured ioLoop queries. Create the queries your Project needs.

A named query is not necessary for every Database operation. Simple record lookups and other supported direct data operations may be sufficient when the retrieval pattern does not need a reusable query definition.

How Queries Fit Into the Database ​

A named query belongs to a Database table and uses the table's available fields.

ConceptResponsibility
TableDefines the records and fields available for retrieval.
Named QueryDefines the reusable retrieval pattern for those records.
Query InputsSupply values that vary each time a parameterized query runs.
Calling ComponentSelects the query and supplies any required inputs.
Query ResultProvides matching records for the Page, Widget, Cloud Flow, or other caller to process.
Table AccessDetermines whether the caller is permitted to access the underlying data.

For example, a query named RegistrationsByEvent can express the retrieval pattern for the Registrations table. The current event is supplied when the query is executed rather than requiring a separate query for every event.

Queries retrieve records. They do not, by themselves, create, update, or delete those records.

Before Creating a Query ​

Start by identifying the records that the consuming workflow needs.

Consider:

  • Which table contains the records?
  • What conditions should records satisfy?
  • Which runtime input will each condition use?
  • Should the records appear in a particular order?
  • Does the caller need every field, or only selected values?
  • Does the result need related data from another table?
  • Will a Widget or Page Flow load results in pages?
  • Which Project Users or User Groups should be able to retrieve the data?
  • Which Pages, Cloud Flows, or AI Agents will depend on the query?

Use the table's generated Field IDs and meaningful query names. Both can become dependencies in many Project components long after the original Page is changed.

Creating a Query ​

To create a named query:

  1. Open Backend → Database in the Project Editor.
  2. Select the table whose records should be queried.
  3. Open the table's Queries area.
  4. Add a new query and give it a meaningful name.
  5. Configure the query's Search Criteria, Field Selection, Data Joins, and Sorting Order as needed.
  6. Review the positional inputs created by the configured criteria.
  7. Save the query.
  8. Test it against representative records in the development database.
  9. Connect it to the Page, Widget, Page Flow, Cloud Flow, or other component that needs the results.

The available configuration controls depend on the query definition and table. Use the controls presented in the Query editor rather than assuming that every Database operation or expression can be configured in the same way.

Query Editor Sections ​

The Query editor is organized around four main sections.

ConceptPurposeExample
Search CriteriaSelect records that satisfy specified conditions.Registrations belonging to a selected event.
Field SelectionChoose which fields are returned by the query.Return only the values needed for a summary list.
Data JoinsInclude related data from another table by joining fields.Add customer details to order records.
Sorting OrderArrange the matching records in an intended order.Show the newest registrations first.

These are the user-facing sections in the Query editor. Internal database terms may appear in API references, but product documentation should use the editor labels when explaining how Creators configure queries.

Search Criteria ​

Search Criteria defines which records should be included in the query result.

Criteria compare field values with runtime inputs. Supported comparisons include common operations such as equals, does not equal, greater than, less than, greater than or equal to, less than or equal to, and pattern matching where available.

Criteria can also be grouped with logical rules such as:

  • AND — all criteria in the group must be true.
  • OR — at least one criterion in the group must be true.
  • NOT — the criterion must not be true.

For example, a registration query might use Search Criteria to return records where EventId equals Input-1 and Status equals Input-2. The caller supplies both values when it runs the query.

To set up Search Criteria:

  1. Open the query and choose Search Criteria.
  2. Add the first criterion.
  3. Choose the table field to compare.
  4. Choose the comparison, such as equals, greater than, or matches pattern.
  5. Select the positional query input used by the comparison.
  6. Add additional criteria as needed.
  7. Use logical groups such as AND, OR, or NOT when the query needs multiple conditions.
  8. Save and test with records that should match and records that should not match.

The Query editor does not provide a literal fixed-value operand for Search Criteria. Each comparison uses a positional runtime input shown as Input-1, Input-2, and so on. If a condition is conceptually fixed, such as Status equals Active, the caller must still supply Active in the corresponding input position.

Keep Search Criteria focused on the retrieval pattern. Do not rely on hidden frontend values or user-supplied IDs as authorization by themselves.

Field Selection ​

Field Selection controls which fields are returned.

Select fields when the caller needs only a smaller result shape. If no fields are selected, the editor's default behavior is to return all fields.

Keep the results aligned with the consuming interface. A compact event list generally does not need every internal field stored in the Events table.

Field Selection can also include the property created by a configured Data Join.

To set up Field Selection:

  1. Open the query and choose Field Selection.
  2. Review the available fields for the table.
  3. Select each field that should be included in the query result.
  4. If Data Joins are configured, select the Join Property Name when the joined result should be returned.
  5. Save and test the query through the component that consumes it.

Returning all fields can be convenient during early development, but production-facing Pages and workflows are often easier to maintain when the query returns only the fields the caller actually uses.

When you remove a field from Field Selection, review every Page, Widget, Page Flow, Cloud Flow, AI Agent, or custom code path that consumes the query. A consumer may fail or show incomplete information if it expects a field that is no longer returned.

Data Joins ​

Data Joins let a query include related data from another table.

A join identifies:

  • The field on the current table.
  • The table to join with.
  • The field on the joined table.
  • The property name where the joined data should appear.

Use Data Joins when the consuming workflow needs related data alongside the main table's records. For example, an Orders query might join customer information so the result can show both order details and customer details.

Use joins deliberately. Joining extra data can make the result more useful, but it also creates a dependency on another table's fields and access behavior.

To set up a Data Join:

  1. Open the query and choose Data Joins.
  2. Add a join.
  3. Choose the field on the current table.
  4. Choose the related table to join with.
  5. Choose the field on the related table that should match the current table field.
  6. Enter the Join Property Name where the joined data should appear in the result.
  7. Save the join.
  8. Review Field Selection and select the Join Property Name if the joined result should be returned.
  9. Test with records that have related data and records that do not.

For example, an Orders table might contain a CustomerId field. A Data Join can connect Orders.CustomerId to the customer table's ID field and expose the related customer data under a join property such as Customer.

Choose clear Join Property Names. They become part of the result shape that Pages, Widgets, Cloud Flow, AI Agents, or custom code may depend on.

Field Selection exposes the joined property as a whole. The Query editor does not project individual fields from the joined record as separate selectable fields.

When a query uses Data Joins, test missing or deleted related records. The consuming workflow should handle cases where the joined data is absent.

Sorting Order ​

Sorting Order controls the order of returned records.

If no specific sort fields are selected, the query uses its default sorting behavior. Add sorting fields when the caller needs a predictable order, such as newest first, alphabetical by name, or highest priority first.

Sorting fields can be configured as ascending or descending. When multiple sorting fields are selected, their order matters.

To set up Sorting Order:

  1. Open the query and choose Sorting Order.
  2. Select the first field to sort by.
  3. Choose Ascending or Descending.
  4. Add additional sorting fields in the order they should be applied.
  5. Save and test with enough records to verify the order.

Use ascending order for values that should increase from first to last, such as names from A to Z or dates from oldest to newest. Use descending order for values that should decrease from first to last, such as newest dates first or highest priority first.

If two records have the same value for the first sorting field, the next sorting field can determine their relative order. Add a secondary sort when the display needs stable ordering.

Example: Building an Event Registration Query ​

Consider a Registrations table with fields such as EventId, Status, AttendeeName, and SubmittedAt, along with an Events table containing event details. These field names are illustrative; use the actual fields in your Project.

A query named ConfirmedRegistrationsByEvent could be configured as follows:

Query Editor SectionExample Configuration
Search CriteriaEventId equals Input-1 AND Status equals Input-2. The caller supplies the event ID and Confirmed in that order.
Field SelectionReturn the registration fields needed by the Page, such as AttendeeName and SubmittedAt, plus the Join Property Name if the joined record is needed.
Data JoinsMatch the registration's EventId to the related event ID and expose the related data under a Join Property Name such as Event.
Sorting OrderSort by SubmittedAt in Descending order to show recent registrations first.

The same query can serve different events by changing the event ID supplied by its caller. Before using the result, verify that the calling user is permitted to access the requested registrations and related event data.

Parameterized Queries ​

A parameterized query accepts input values when it is executed. This allows one query definition to serve different users, records, or selections.

For example, RegistrationsByEvent might use an input representing an event identifier:

InvocationInput ValueIntended Result
Event A registration PageEvent A IDRegistrations for Event A.
Event B administration PageEvent B IDRegistrations for Event B.
Event C dashboardEvent C IDRegistrations for Event C.

The query definition stays the same. The calling workflow supplies the event identifier for its current context.

Other examples of query inputs include a customer ID, status, category, date range, or a value selected in a Form.

Positional Inputs ​

The Query editor creates positional inputs as criteria are configured. They are displayed as Input-1, Input-2, and so on; the editor does not provide a separate control for giving those inputs custom names.

When planning query inputs:

  1. Decide which values must vary at runtime.
  2. Record which meaning belongs to each numbered input.
  3. Configure each caller to provide every value in the required order.
  4. Update callers if criteria are reordered, added, or removed.
  5. Test with different values, including values that return no records.

A named query does not automatically receive the current Project User, selected record, URL parameter, or Page state. Supply the required context through the calling component or the documented runtime mechanism.

Page Flow Inputs Versus Direct API Inputs ​

The way inputs are supplied depends on the calling interface.

  • In Page Flow, the Data Read block provides a query input connection for a configured input object. Use the corresponding blocks and their documented input requirements.
  • In custom frontend or backend JavaScript, the documented $io.db.readQuery function takes a queryInput array. The values in that array must follow the parameter order defined by the named query.

Do not assume that an input object used in a Page Flow block can be passed unchanged to the direct JavaScript API. Use the interface-specific form documented in the Programming Guide.

For a direct API call, the documented signature is:

js
$io.db.readQuery(tableName, dsName, perPage, queryId, queryInput, options)

For example, assuming RegistrationsByEvent has one query parameter for the event ID:

js
const selectedEventId = 'event-123';

const result = await $io.db.readQuery(
  'Registrations',
  'registrationsList',
  25,
  'RegistrationsByEvent',
  [selectedEventId],
  {}
);

if (result.status) {
  const registrations = result.data;
  // Update the interface using the retrieved records.
} else {
  // Show appropriate feedback based on result.err.
}

This is a Client API usage example, not syntax for defining the query in the Database editor. Replace the example table name, query name, parameter values, and dataset name with those configured in your Project. The same named query may also be invoked from server-side code using the documented Server API.

For full API requirements, see the $io.db Client API and $io.db Server API.

Using Queries in Project Features ​

A query can serve different presentation and processing needs.

Project AreaTypical Use
Data Table WidgetPopulate a record-oriented table with an appropriate result set.
Data DesignRetrieve records that Page Flow renders into custom cards, tiles, or lists.
Page FlowRetrieve records, handle filters, update the interface, or coordinate further actions.
Cloud FlowRetrieve records for trusted validation, processing, and server-side workflows.
AI AgentsSupport configured table-oriented knowledge or data operations where available.
Custom FrontendCall supported ioLoop Client APIs directly and render the resulting records using custom HTML and JavaScript.

Built-in Widgets and Page Flow blocks provide convenient higher-level interfaces to ioLoop's client functionality. A fully custom frontend implementation can use the same Client APIs to retrieve and present data, subject to the same access rules.

For broader guidance on frontend Database usage, see Using Database from Pages.

Working with Results, Datasets, and Paging ​

Some Page Flow data operations use a dataset name to identify the records loaded by a read operation. The dataset is runtime state used by the calling flow; it is not a separate Database table or a saved query definition.

When designing a paged interface:

  1. Choose the query and provide the current inputs.
  2. Start the read operation with a suitable page size.
  3. Wait for the operation to complete successfully before using the returned records.
  4. Render the current records in the Data Table or custom interface.
  5. Use supported next/previous operations when the user changes pages.
  6. Start a new appropriate query/read sequence when the user changes the filter or query inputs.

The Client and Server APIs document readQuery, readNext, and readPrev, as well as dataset operations and result handling. In those APIs, readQuery accepts a perPage value greater than zero and no greater than 100.

Distinguish the records in the currently loaded page from the total number of records matching a query. Do not assume a dataset's current record count equals the full result count.

Empty Results and Errors ​

An empty result set can mean that the query ran successfully but found no matching records. It is different from an operation failure.

Use an appropriate empty state, such as No registrations match the selected event, when the query succeeds with no records. Display a different message when the read fails due to permissions, invalid inputs, or another error.

When using direct ioLoop APIs, inspect the documented result status before using returned data. For Page Flow, use the relevant data-operation status or completion behavior.

Queries and Access Control ​

Queries select records; they do not grant permission to retrieve those records.

The table's access configuration and any required trusted authorization checks still apply.

For example, OrdersByCustomer may accept a customer ID, but passing an ID does not establish that the current Project User is authorized to see that customer's orders.

For protected workflows:

  • Apply the appropriate Table Access rules.
  • Do not rely on a hidden Widget, a disabled button, or a client-supplied query input as an authorization control.
  • Validate record ownership, group membership, or other sensitive conditions through an appropriate trusted Backend workflow when required.
  • Return only data the caller is authorized to receive.
  • Test both allowed and denied requests.

Cloud Flow can coordinate protected operations when a query must depend on trusted server-side decisions. A frontend query should not retrieve sensitive records broadly and then rely on browser-side filtering to hide unauthorized data.

For details, see Table Access and Cloud Flow Access and Security.

Editing, Cloning, and Deleting Queries ​

A named query may become a shared dependency across the Project. Treat changes to it accordingly.

Editing a Query ​

Before editing a query, check whether its consumers depend on:

  • The query name or identifier.
  • A particular input count, order, or meaning.
  • Fields included in the returned records.
  • Sorting Order or result limits.
  • The existing Search Criteria.
  • Data Joins and Join Property Names.

Changing these details can affect Pages, Widgets, custom code, Cloud Flow, or AI Agents that already use the query.

After editing, retest each important consumer rather than checking only that the query returns records.

Cloning a Query ​

Use Clone when you want to start from an existing query but create a different retrieval pattern.

A clone is a separate query definition; it does not automatically redirect existing Pages or workflows to the new query. Review the new query's name, inputs, Search Criteria, Field Selection, Data Joins, Sorting Order, and consumers before using it.

System Queries ​

ioLoop maintains the system queries qfind, qget, and qgetid. They support built-in Database operations and are hidden from the normal named-query list. Create and maintain application-specific retrieval patterns as separate named queries; do not depend on editing these system queries.

Deleting a Query ​

Delete a query only after confirming that it is no longer referenced by:

  • Page Flow or custom JavaScript.
  • Data Table Widgets or Data Design rendering workflows.
  • Cloud Flow modules.
  • AI Agent Knowledge Sources.
  • Any other saved Project configuration that calls it by name or identifier.

Removing a referenced query can cause runtime failures even when the Page layout itself has not changed.

Testing Queries ​

Test queries using realistic development records and the actual component that calls them.

TestWhat to Verify
Matching recordsThe expected records are included.
Nonmatching recordsUnrelated records are excluded.
Different parametersThe same query works for different input values.
Empty resultsThe interface handles a successful query with no matches.
Field SelectionResults include the fields the caller expects and omit fields it should not need.
Data JoinsJoined data appears under the expected Join Property Name and missing related records are handled.
Sorting OrderResults appear in the expected order.
PagingResults remain correct when moving between pages.
PermissionsAllowed users can retrieve data and denied users cannot.
Shared consumersPages, Widgets, and Cloud Flows continue working after query changes.
Failure handlingInvalid inputs and failed operations produce useful feedback.

Development and Production Data ​

During Preview, built-in Database operations automatically use the Project's development database. This includes built-in reads that use named queries.

The published application uses its production data environment where supported. Matching query definitions do not imply matching records in development and production.

Test queries in Preview with representative development data, then verify the published experience after the appropriate publishing steps. External integrations and custom operations that reach outside the built-in Database environment may have different side effects or environment settings.

For more information, see Development and Production Data and Previewing.

Troubleshooting ​

IssueWhat to Check
No records returnedSearch Criteria, input values, data environment, and whether matching records exist.
Unexpected recordsSearch Criteria, supplied inputs, and the table being queried.
Wrong parameter behaviorParameter definition and order, particularly for direct $io.db.readQuery calls.
Missing fieldsField Selection, Field IDs, Data Joins, Join Property Names, and assumptions in the consuming component.
Joined data missingData Join configuration, related table records, join fields, and access behavior.
Unexpected orderSorting Order, selected sort fields, ascending or descending choice, and secondary sorting fields.
Old results remain visibleWhether the Page or Widget started a fresh query and updated its displayed data.
Paging behaves unexpectedlyDataset identity, page size, query inputs, and current-page versus full-result counts.
Access deniedTable permissions, current user context, and any required Backend authorization.
Query stopped working after editingChanges to inputs, names, returned fields, or shared consumers.

When troubleshooting, test the query with known records first, then test it through the actual Page, Widget, or Backend workflow that consumes it.

Good Practices ​

  • Name queries by purpose. Prefer PendingApprovals or RegistrationsByEvent over Query1.
  • Reuse retrieval patterns. Avoid maintaining the same query conditions independently in several workflows.
  • Keep parameters explicit. Document what each numbered input means and provide values in the correct order.
  • Limit returned data. Retrieve the fields and records the consumer needs where supported.
  • Handle completion and failure. Do not assume an asynchronous query has succeeded merely because it started.
  • Plan for paging and empty results. Test realistic record counts and no-match cases.
  • Check dependencies before editing. A query may be shared across many Project components.
  • Enforce access separately. Query conditions are not a substitute for authorization.

Common Mistakes ​

Avoid these patterns:

  • Duplicating a commonly used retrieval pattern instead of creating a named query.
  • Passing query parameters in the wrong order to the direct Client or Server API.
  • Expecting Search Criteria to contain a fixed literal value instead of a runtime input.
  • Assuming query inputs can be assigned custom names in the Query editor.
  • Selecting a Join Property Name and expecting individual joined fields to become separate projections.
  • Assuming that the query automatically knows the current user or selected Page record.
  • Using query filtering alone to protect sensitive data.
  • Treating an empty successful result as a query failure.
  • Assuming a current dataset page contains the full query result.
  • Editing or deleting a shared query without testing dependent workflows.
  • Testing only as an administrator or only with ideal sample records.

Queries are most useful when they express a clear, reusable data retrieval pattern, with explicit inputs, predictable results, and appropriate security controls.

Technical Reference ​

Use the Programming Guide for exact blocks, input types, API parameters, outputs, and runtime behavior: