Appearance
Grids: Filtering, Sorting & Pagination
The primaTime API provides a powerful grid system for querying collections of entities. This system supports cursor-based pagination, multi-field filtering, and sorting — enabling you to build efficient data tables, reports, and lists.
Connection Pattern
All collection queries follow the Relay Connection specification:
graphql
type ProjectConnection {
edges: [ProjectEdge!]!
pageInfo: PageInfo!
}
type ProjectEdge {
node: Project!
cursor: String!
}
type PageInfo {
endCursor: String
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
totalCount: Int!
}Pagination
Basic Pagination
Use first and after for forward pagination:
graphql
query GetProjects($first: Int!, $after: String) {
projects(first: $first, after: $after) {
edges {
node {
id
title
code
}
cursor
}
pageInfo {
hasNextPage
endCursor
totalCount
}
}
}First page:
json
{ "first": 20 }Next page:
json
{ "first": 20, "after": "cursor_from_previous_page" }Backward Pagination
Use last and before for backward pagination:
graphql
query GetProjectsBackward($last: Int!, $before: String) {
projects(last: $last, before: $before) {
edges {
node { id title }
cursor
}
pageInfo {
hasPreviousPage
startCursor
}
}
}Skip Pagination
For offset-based pagination (useful for "jump to page"):
graphql
query GetProjectsWithSkip($first: Int!, $skip: Int) {
projects(first: $first, skip: $skip) {
edges { node { id title } }
pageInfo { totalCount }
}
}Page 3 with 20 items per page:
json
{ "first": 20, "skip": 40 }Filtering
Filters are passed via the query parameter using SearchFilterQuery:
graphql
input SearchFilterQuery {
search: Search # Full-text search
filters: Filters # Field-specific filters
orders: [OrderBy!] # Sorting
}Full-Text Search
Search across multiple text fields:
graphql
query SearchProjects($term: String!) {
projects(first: 20, query: {
search: { term: $term }
}) {
edges {
node { id title code }
}
}
}json
{ "term": "website redesign" }Field Filters
The Filters input supports three filter types:
graphql
input Filters {
simple: [SimpleFilter!] # Single value comparison
list: [ListFilter!] # IN / NOT_IN operations
between: [BetweenFilter!] # Range comparisons
}Filter Operators
| Operator | Description | Example Use |
|---|---|---|
EQUAL | Exact match | Status = "Active" |
NOT_EQUAL | Not equal | Priority ≠ "Low" |
GREATER_THAN | Greater than | Created > date |
GREATER_THAN_OR_EQUAL | Greater or equal | Amount ≥ 100 |
LESS_THAN | Less than | Due date < date |
LESS_THAN_OR_EQUAL | Less or equal | Budget ≤ 1000 |
LIKE | Pattern match | Title contains "web" |
IN | Value in list | Status in ["Open", "Active"] |
NOT_IN | Value not in list | Type not in ["Draft"] |
BETWEEN | Range (inclusive) | Date between X and Y |
IS_NULL | Value is null | Client is not set |
IS_NOT_NULL | Value is not null | Assignee is set |
Simple Filters
Single value comparisons:
graphql
query FilteredTasks {
tasks(first: 20, query: {
filters: {
simple: [
{ field: TASK_PRIORITY, operator: EQUAL, value: "HIGH" }
{ field: TASK_STATUS_TYPE, operator: NOT_EQUAL, value: "DONE" }
]
}
}) {
edges { node { id title priority } }
}
}List Filters
IN/NOT_IN operations for multiple values:
graphql
query TasksInStatuses {
tasks(first: 20, query: {
filters: {
list: [
{
field: TASK_STATUS_TYPE,
operator: IN,
values: ["TODO", "IN_PROGRESS"]
}
]
}
}) {
edges { node { id title status { title } } }
}
}Between Filters
Range queries:
graphql
query TimeRecordsInRange {
timeRecords(first: 50, query: {
filters: {
between: [
{
field: TIME_RECORD_START,
operator: BETWEEN,
valueFrom: "2024-01-01T00:00:00.000Z",
valueTo: "2024-01-31T23:59:59.999Z"
}
]
}
}) {
edges {
node {
id
start
end
duration
}
}
}
}Null Checks
graphql
# Tasks with an assignee
query AssignedTasks {
tasks(first: 20, query: {
filters: {
simple: [
{ field: TASK_ASSIGNEE, operator: IS_NOT_NULL, value: "" }
]
}
}) {
edges { node { id title assignee { profile { firstName } } } }
}
}
# Tasks without a due date
query TasksWithoutDueDate {
tasks(first: 20, query: {
filters: {
simple: [
{ field: TASK_DUE_DATE, operator: IS_NULL, value: "" }
]
}
}) {
edges { node { id title } }
}
}Combining Filters
Multiple filters are combined with AND logic:
graphql
query ComplexFilter {
tasks(first: 20, query: {
search: { term: "api" },
filters: {
simple: [
{ field: TASK_PRIORITY, operator: IN, value: "HIGH" },
{ field: COMMON_ARCHIVED, operator: EQUAL, value: "ONLY_NOT_ARCHIVED" }
],
list: [
{ field: TASK_PROJECT, operator: IN, values: ["proj_1", "proj_2"] }
],
between: [
{
field: TASK_DUE_DATE,
operator: BETWEEN,
valueFrom: "2024-01-01",
valueTo: "2024-03-31"
}
]
}
}) {
edges { node { id title } }
}
}Sorting
Specify sort order with orders:
graphql
query SortedProjects {
projects(first: 20, query: {
orders: [
{ field: PROJECT_CLIENT, direction: ASC },
{ field: PROJECT_TITLE, direction: ASC }
]
}) {
edges {
node {
id
title
client { title }
}
}
}
}Sort Direction
graphql
enum OrderDirection {
ASC # Ascending (A-Z, 0-9, oldest first)
DESC # Descending (Z-A, 9-0, newest first)
}Available Filter Fields
Query available filters for any entity type:
graphql
query AvailableTaskFilters {
availableFilters(entityType: TASK) {
filters {
field
dataType
operators
entityType # For relation fields
enumType # For enum fields
}
}
availableOrders(entityType: TASK) {
orders
}
}Common Filter Fields
COMMON_IDCOMMON_CREATED_ATCOMMON_ARCHIVED
Archive Filter
Control visibility of archived items:
graphql
query ActiveProjects {
projects(first: 20, query: {
filters: {
simple: [
{ field: COMMON_ARCHIVED, operator: EQUAL, value: "ONLY_NOT_ARCHIVED" }
]
}
}) {
edges { node { id title } }
}
}Archive filter values:
ONLY_ARCHIVED— Show only archived itemsONLY_NOT_ARCHIVED— Show only active items (default behavior)ALL— Show both archived and active items