When using custom JQL functions that return large result sets, customers encounter this error:
The JQL function 'x' returns more issues (over 1000) than the maximum allowed.
Specific Error Details:
VALIDATION_ERRORCustom JQL functions often serve broad purposes that naturally return large datasets. Common examples:
For large enterprise customers, these queries can easily return thousands of results, which may have been acceptable in Data Center environments but hits the Cloud platform limit. The 1000 value limit exists primarily due to PostgreSQL database constraints and performance concerns in a multi-tenant cloud environment. Large IN clauses with thousands of values cause exponential increases in query planning time, consume excessive memory across multiple layers (app cache, database, network), and can degrade performance for all customers sharing the infrastructure. Keeping all these factors in mind, a limit of 1000 has been decided.
App developers and customers have several options to work within the 1000 value limit while achieving similar outcomes to Data Center environments.
Strategy: Add function parameters or additional JQL predicates to narrow down results.
Current function design:
1 2// Function: relatedIssues(parentKey) // Returns: ALL related issues for a parent (might be 5000+) function relatedIssues(parentKey) { const issues = getAllRelatedIssues(parentKey); return { jql: `key IN (${issues.join(', ')})` // ❌ Fails if > 1000 }; }
Improved design with filters:
1 2// Function: relatedIssues(parentKey, status, dateRange) // Returns: Filtered related issues (< 1000) function relatedIssues(parentKey, status, dateRange) { const issues = getFilteredRelatedIssues(parentKey, status, dateRange); return { jql: `key IN (${issues.join(', ')})` // ✅ Under 1000 }; }
Instead of:
1 2issue in relatedIssues("PARENT-123")
Use:
1 2issue in relatedIssues("PARENT-123", "Open", "-30d") AND priority = High
Strategy: Build pagination directly into custom JQL functions.
Add pagination parameters to function signatures:
1 2// Function: relatedIssues(parentKey, page, pageSize) function relatedIssues(parentKey, page = 1, pageSize = 500) { const allIssues = getAllRelatedIssues(parentKey); const totalResults = allIssues.length; const totalPages = Math.ceil(totalResults / pageSize); // Calculate pagination const startIndex = (page - 1) * pageSize; const endIndex = startIndex + pageSize; const pageResults = allIssues.slice(startIndex, endIndex); if (pageResults.length === 0) { return { error: `No results on page ${page}. Total pages: ${totalPages}`, storeErrorAsPrecomputation: true }; } return { jql: `key IN (${pageResults.join(', ')})` // ✅ Max 500 per page }; }
Query page by page:
1 2-- Page 1 (results 1-500) issue in relatedIssues("PARENT-123", 1, 500) -- Page 2 (results 501-1000) issue in relatedIssues("PARENT-123", 2, 500) -- Page 3 (results 1001-1500) issue in relatedIssues("PARENT-123", 3, 500)
Or combine pages:
1 2issue in relatedIssues("PARENT-123", 1, 500) OR issue in relatedIssues("PARENT-123", 2, 500)
Strategy: Return a JQL query that expresses the search criteria, rather than an explicit list of issue keys.
This is the most powerful and recommended approach as it bypasses the value limit entirely.
When a custom JQL function returns a response, it provides a JQL fragment that Jira then parses and executes.
❌ AVOID: Returning explicit value lists
1 2{ "jql": "key IN (ISSUE-1, ISSUE-2, ISSUE-3, ..., ISSUE-1500)" }
✅ PREFER: Returning JQL query conditions
1 2{ "jql": "project = XTP AND labels = test-execution AND 'Test Plan' = PLAN-123" }
The Jira platform validates JQL fragments by counting the number of literal values on the right-hand side of operators.
What counts as a "value":
key IN (A, B, C) → 3 valuesstatus IN (Open, Closed) → 2 valuesproject = ABC → 1 valueAND → 1 value per conditionValidation Logic:
The platform counts all literal values in the JQL fragment. If the total exceeds 1000, the validation fails with a TOO_LONG_VALUES_LIST error.
The key is to structure your data so you can query it using Jira's native fields and custom fields.
Approach A: Use Custom Fields
If your app stores metadata in custom fields:
1 2// Instead of returning a list of keys function relatedIssues(parentKey) { // ❌ OLD WAY - Returns explicit list // const issues = getAllRelatedIssues(parentKey); // return { jql: `key IN (${issues.join(', ')})` }; // ✅ NEW WAY - Query by custom field return { jql: `"Parent Reference" = "${parentKey}" AND type = "Sub-task"` }; }
Approach B: Use Labels
1 2function relatedIssues(parentKey) { // When issues are created, tag them with labels return { jql: `labels = "parent-${parentKey}" AND type = "Related Issue"` }; }
Approach C: Use Issue Links
1 2function relatedIssues(parentKey) { // Query issues linked to the parent return { jql: `issue in linkedIssues("${parentKey}", "relates to")` }; }
Approach D: Combine Multiple Conditions
1 2function complexQuery(projectKey, category, status) { return { jql: ` project = ${projectKey} AND "Category" = "${category}" AND status = ${status} AND created >= -90d `.trim() }; }
Transparent to the user:
1 2issue in relatedIssues("PARENT-123")
Under the hood, this expands to:
1 2issue in ( "Parent Reference" = "PARENT-123" AND type = "Sub-task" )
The platform executes this as a natural query, returning any number of results without hitting the value limit.
Strategy: Combine the above approaches for optimal results.
1 2function relatedIssues(parentKey, page = null, status = null) { // Build base query using JQL conditions let jqlParts = [ `"Parent Reference" = "${parentKey}"`, `type = "Sub-task"` ]; // Add filters if provided to narrow results if (status) { jqlParts.push(`status = "${status}"`); } const baseJql = jqlParts.join(' AND '); // If no pagination needed, return query directly if (page === null) { return { jql: baseJql }; } // If pagination requested, fall back to explicit enumeration const results = executeQuery(baseJql); const pageSize = 500; const startIndex = (page - 1) * pageSize; const pageResults = results.slice(startIndex, startIndex + pageSize); return { jql: `key IN (${pageResults.map(r => r.key).join(', ')})` }; }
Simple case (returns all via query):
1 2issue in relatedIssues("PARENT-123")
Filtered case:
1 2issue in relatedIssues("PARENT-123", null, "Open")
Paginated case:
1 2issue in relatedIssues("PARENT-123", 1)
| Criteria | Write more specific queries | Implement pagination | Return JQL queries | Hybrid approach |
|---|---|---|---|---|
| Bypasses 1000 limit | No | No | Yes ✓ | Conditional |
| Implementation complexity | Low | Medium | Medium-High | High |
| Performance | Good | Predictable | Best | Good |
| User experience | Requires extra parameters | Requires pagination calls | Transparent | Flexible |
| Scalability | Limited | Limited | Unlimited | Flexible |
| Maintenance | Low | Medium | Low | Medium-High |
| Best for | Small filtered datasets | Known large datasets | All use cases | Complex requirements |
| Requires data model changes | No | No | Yes | Yes |
| Platform optimization | Standard | Standard | Full leverage | Mixed |
This is the most scalable and user-friendly solution:
For functions that genuinely need explicit enumeration:
page and pageSize parameters to function signaturesFor all high-volume functions:
Rate this page: