**Fibery GraphQL Developer Guide v1.0** --- # Fibery GraphQL Developer Guide v1.0 *A comprehensive reference for working with Fibery's GraphQL API* **Published:** October 2025 **Target Audience:** Developers, automation engineers, integration specialists **Scope:** Complete coverage of queries, mutations, filtering, and performance optimization --- ## Introduction This comprehensive guide covers Fibery's GraphQL API with emphasis on practical implementation, production patterns, and troubleshooting support. ### What This Guide Provides **Unified reference** - Complete coverage of queries, mutations, filtering, and performance optimization in a single searchable document. **Error troubleshooting** - Solutions to common GraphQL errors with side-by-side wrong/correct examples and specific fixes. **Production patterns** - Working implementations of rate limiters, pagination loops, background job monitoring, and exponential backoff for resilient integrations. **Real-world examples** - Complete CRUD workflows, bulk operations with background jobs, reporting queries with aliases, and multi-step automation chains. **Decision support** - Step-by-step decision trees for query construction, pre-submission validation checklists, and best practices guidance. **Complete operator reference** - Consolidated tables covering all text, number, date, boolean, and collection operators with usage examples. **Advanced implementations** - Notification systems with templates, file attachments, PDF generation, deep nesting strategies, and complex filtering patterns. **Critical pattern clarification** - Explicit separation of filtering BY collection content versus filtering collection results, with practical examples of each. ### How to Use This Guide - **Learning** - Read sections 1-4 sequentially for comprehensive understanding - **Building** - Jump to specific sections via table of contents - **Debugging** - Reference troubleshooting section for error solutions - **Optimizing** - Apply performance patterns from section 8 ### Prerequisites - Basic GraphQL knowledge - Fibery workspace with API access - API token (Settings → API) --- *Complements [Fibery's official API documentation](https://api.fibery.io) | Last updated: October 2025* --- ## Table of Contents 1. [Core Principles](#core-principles) 2. [Authentication & Endpoints](#authentication--endpoints) 3. [Queries (Read Operations)](#queries-read-operations) 4. [Mutations (Write Operations)](#mutations-write-operations) 5. [Filtering Reference](#filtering-reference) 6. [Rich Text & Comments](#rich-text--comments) 7. [Advanced Patterns](#advanced-patterns) 8. [Performance & Optimization](#performance--optimization) 9. [Complete Examples](#complete-examples) 10. [Troubleshooting Guide](#troubleshooting-guide) --- ## Core Principles ### 1. Field Naming: Always camelCase in Queries **Schema names use PascalCase/namespaces, GraphQL requires camelCase.** | Database Schema | GraphQL Query Method | |-----------------|---------------------| | `Projects/Project` | `findProjects` | | `Projects/Status` | `findStatuses` | | `Content/Article` | `findArticles` | | Field Schema | GraphQL Field | |-------------|--------------| | `Projects/Name` | `name` | | `Projects/Status` | `status` | | `Projects/Start Date` | `startDate` | | `fibery/id` | `id` | ### 2. Space-Scoped Endpoints Each space has its own GraphQL endpoint: ``` https://YOUR_ACCOUNT.fibery.io/api/graphql/space/YOUR_SPACE ``` List all available endpoints: ``` https://YOUR_ACCOUNT.fibery.io/api/graphql ``` ### 3. Field Selection Rules **Primitive types** (no nesting required): - Text/String - Number/Integer/Float - Date/DateTime - Boolean - URL/Email/Phone - UUID **Relation types** (require nesting): - References to other databases (e.g., `Projects/Team`) - Single-select/Enum fields - Collection fields (arrays) - Rich text fields (special: select format subfields) --- ## Authentication & Endpoints ### Token-Based Authentication ```bash # cURL example curl -X POST https://acme-corp.fibery.io/api/graphql/space/Software_Development \ -H "Authorization: Token YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"query":"{findBugs{id,name}}"}' ``` ### JavaScript/Node.js ```javascript const YOUR_SPACE_ENDPOINT = 'https://acme-corp.fibery.io/api/graphql/space/Software_Development'; const YOUR_TOKEN = 'your_token_here'; const response = await fetch(YOUR_SPACE_ENDPOINT, { method: 'POST', body: JSON.stringify({ query }), headers: { 'Content-Type': 'application/json', 'Authorization': `Token ${YOUR_TOKEN}`, } }); const result = await response.json(); ``` ### In Fibery Scripts ```javascript const fibery = context.getService('fibery'); // Space-scoped execution const response = await fibery.graphql('Projects', query); const data = response.data; const errors = response.errors; ``` ### Rate Limiting **3 requests per second per token** - Returns HTTP 429 "Too Many Requests" on violation - Plan for pagination and throttling in loops - Use background jobs for large operations --- ## Queries (Read Operations) ### Basic Query Structure ```graphql { find{DatabaseNamePlural}( {filterField}: { {subfield}: { {operator}: {value} } } limit: {number} offset: {number} orderBy: { {field}: {ASC|DESC} } ) { id {primitiveField} {relationField} { id name } {collectionField} { id name } } } ``` ### Simple Queries #### Query All Entities (with default limit) ```graphql { findProjects { id name startDate budget } } ``` **Default limit is 100** - always specify explicitly for safety. #### Query with Explicit Limit ```graphql { findProjects(limit: 50) { id name startDate } } ``` ### Filtering Queries #### Filter by Text Field ```graphql { findProjects( name: { contains: "Website" } limit: 10 ) { name budget } } ``` #### Filter by Number Field ```graphql { findProjects( budget: { greater: 50000 } limit: 20 ) { name budget } } ``` #### Filter by Date Field ```graphql { findProjects( startDate: { greater: "2025-01-01" } limit: 20 ) { name startDate } } ``` #### Filter by Boolean Field ```graphql { findProjects( archived: { is: false } limit: 50 ) { name } } ``` #### Filter by Relation (THREE-LEVEL NESTING) ```graphql { findProjects( status: { name: { is: "Active" } } ) { id name status { id name priority } } } ``` **Critical:** Relation filters require three levels: `relationField: { subfield: { operator: value } }` #### Multiple Filters (AND Logic) ```graphql { findProjects( status: { name: { is: "Active" } } team: { name: { contains: "Engineering" } } budget: { greater: 50000 } startDate: { greater: "2025-01-01" } limit: 20 ) { name budget startDate } } ``` All filters are combined with AND logic. ### Filtering BY Collection Content **Filter parent entities based on what's IN their collections:** ```graphql { findReleases( bugs: { containsAny: [ { state: { name: { is: "Open" } } } { effort: { greater: 0 } } ] } ) { name bugs { name state { name } } } } ``` **Collection filter operators:** - `isEmpty: Boolean` - Check if collection is empty - `contains: [Filter]` - ALL items match (AND) - `containsAny: [Filter]` - ANY item matches (OR) - `notContains: [Filter]` - NO items match (AND) - `notContainsAny: [Filter]` - NOT ANY items match (OR) ### Filtering Collection Results **Different from filtering BY collections - this filters WHAT SHOWS in the collection:** ```graphql { findReleases { name bugs(state: { name: { is: "To Do" } }) { # Only show To Do bugs name state { name } } } } ``` This doesn't filter which Releases are returned, it filters which bugs appear in each Release's bugs collection. ### Sorting #### Simple Sort ```graphql { findProjects( orderBy: { startDate: DESC } limit: 10 ) { name startDate } } ``` #### Multi-Field Sort ```graphql { findProjects( orderBy: { startDate: DESC name: ASC } limit: 10 ) { name startDate } } ``` #### Sort by Relation Field ```graphql { findProjects( orderBy: { status: { priority: ASC } } limit: 10 ) { name status { name priority } } } ``` #### Sort Collection Results ```graphql { findReleases { name bugs( orderBy: { name: ASC createdBy: { email: ASC } } ) { name } } } ``` ### Pagination #### Basic Pagination ```graphql # Page 1 { findProjects(limit: 50, offset: 0) { id name } } # Page 2 { findProjects(limit: 50, offset: 50) { id name } } # Page 3 { findProjects(limit: 50, offset: 100) { id name } } ``` #### Pagination Pattern ```javascript let offset = 0; const limit = 100; let hasMore = true; while (hasMore) { const query = `{ findProjects(limit: ${limit}, offset: ${offset}) { id name } }`; const result = await fibery.graphql('Projects', query); const projects = result.data.findProjects; // Process projects hasMore = projects.length === limit; offset += limit; } ``` ### Using Aliases (OR-like Logic) ```graphql { todo: findBugs(state: { name: { is: "To Do" } }) { name state { name } } inProgress: findBugs(state: { name: { is: "In Progress" } }) { name state { name } } done: findBugs(state: { name: { is: "Done" } }) { name state { name } } } ``` **Output:** ```json { "data": { "todo": [ { "name": "Fix login", "state": { "name": "To Do" } } ], "inProgress": [ { "name": "Refactor API", "state": { "name": "In Progress" } } ], "done": [ { "name": "Setup database", "state": { "name": "Done" } } ] } } ``` ### Working with Variables ```javascript const query = ` query findContactFromEmail($searchEmail: String) { findEmailAddresses(name: { is: $searchEmail }) { id contact { id name } } } `; const response = await fibery.graphql( 'CRM', query, { searchEmail: 'user@example.com' } ); ``` --- ## Mutations (Write Operations) ### Basic Mutation Structure ```graphql mutation { database(filter) { action1(args) { message, entities { id, type } } action2(args) { message, entities { id, type } } actionN(args) { message, entities { id, type } } } } ``` **Key principles:** - Mutations modify data (create, update, delete) - Can chain multiple actions in one mutation - Use filters to select entities for modification - Actions return `message` and affected `entities` - Use batch operations for multiple items - Cannot duplicate operations (use batch instead) ### Create Operations #### Create Single Entity ```graphql mutation { projects { create(name: "New Project") { message entities { id type } } } } ``` **Output:** ```json { "data": { "projects": { "create": { "message": "Create: 1 Project added", "entities": [ { "id": "abc-123", "type": "Projects/Project" } ] } } } } ``` #### Create with Relations ```graphql mutation { releases { create( name: "Urgent Release" bugs: { state: { name: { is: "To Do" } } } ) { entities { id type } } } } ``` This creates a release and links all bugs in "To Do" state. #### Create Multiple Entities (Batch) ```graphql mutation { bugs { createBatch(data: [ { name: "Login failure", state: { name: { is: "In Progress" } } } { name: "Design broken", state: { name: { is: "To Do" } } } { name: "API timeout", state: { name: { is: "Open" } } } ]) { message entities { id } } } } ``` #### Create and Chain Actions ```graphql mutation { bugs { createBatch(data: [ { name: "Login failure" } { name: "Design broken" } ]) { message } assignToMe { message } addComment(value: "I will fix this ASAP") { message } } } ``` **Output:** ```json { "data": { "bugs": { "createBatch": { "message": "Create: 2 Bugs added" }, "assignToMe": { "message": "Assign to me: User(s) assigned to 2 Bugs" }, "addComment": { "message": "Add comment: Comments added to 2 Bugs" } } } } ``` ### Update Operations #### Update with Filter ```graphql mutation { bugs( effort: { is: 15 } state: { name: { is: "To Do" } } ) { update( release: { name: { is: "1.0" } } effort: 10 ) { message entities { id } } countOfEntities # How many were found } } ``` #### Verify Before Update ```graphql # Step 1: Check what will be updated mutation { bugs(state: { name: { is: "To Do" } }) { listEntities { id name } countOfEntities } } # Step 2: Execute update mutation { bugs(state: { name: { is: "To Do" } }) { update(effort: 5) { message } } } ``` ### Delete Operations #### Delete with Filter ```graphql mutation { bugs(state: { name: { is: "Done" } }) { delete { message } } } ``` **Output:** ```json { "data": { "bugs": { "delete": { "message": "Delete: 4 Bugs deleted" } } } } ``` #### Safety Pattern: Verify Before Delete ```graphql # ALWAYS verify first mutation { bugs(state: { name: { is: "Done" } }) { listEntities { id name } countOfEntities } } # Then delete if count looks correct mutation { bugs(state: { name: { is: "Done" } }) { delete { message } } } ``` ### Relation Operations #### Create and Link Relations ```graphql mutation { releases { release: create(name: "3.0.1") { entities { id type } } tasks: addTasksItemBatch(data: [ { name: "Do design" } { name: "Do development" } ]) { entities { id type } } bugs: addBugsItemBatch(data: [ { name: "Fix design" } { name: "Fix development" } { name: "Remove code" } ]) { entities { id type } } } } ``` Note the use of aliases (`release:`, `tasks:`, `bugs:`) for readable output. #### Link Existing Relations ```graphql # Link bugs with effort > 5 to a release mutation { releases(name: { is: "3.0" }) { linkBugs(effort: { greater: 5 }) { message } } } ``` #### Unlink Relations ```graphql # Unlink bugs with effort >= 15 from release mutation { releases(bugs: { isEmpty: false }) { unlinkBugs(effort: { greaterOrEquals: 15 }) { message } } } # Unlink all releases from bugs mutation { bugs(release: { id: { isNull: false } }) { unlinkRelease { message } } } ``` #### Delete Relations ```graphql # Delete release from bugs (deletes the Release entity) mutation { bugs(release: { id: { isNull: false } }) { deleteRelease { message } } } # Delete bugs in "Done" state from releases mutation { releases(bugs: { isEmpty: false }) { deleteBugs(state: { name: { is: "Done" } }) { message } } } ``` **Warning:** `delete` operations permanently remove entities from the system. --- ## Filtering Reference ### Text Operators ```graphql name: { is: "Exact Match" } name: { isNot: "Not This" } name: { contains: "substring" } name: { notContains: "avoid this" } name: { startsWith: "prefix" } name: { endsWith: "suffix" } name: { in: ["Option1", "Option2", "Option3"] } name: { notIn: ["Exclude1", "Exclude2"] } name: { isNull: true } # Case-insensitive (if supported) name: { greater: "M" } # Alphabetical comparison name: { less: "M" } ``` ### Number Operators ```graphql budget: { is: 50000 } budget: { isNot: 50000 } budget: { greater: 50000 } budget: { greaterOrEquals: 50000 } budget: { less: 100000 } budget: { lessOrEquals: 100000 } budget: { in: [50000, 75000, 100000] } budget: { notIn: [0, 10000] } budget: { isNull: true } ``` ### Float Operators ```graphql rating: { is: 4.5 } rating: { isNot: 3.0 } rating: { greater: 4.0 } rating: { greaterOrEquals: 4.0 } rating: { less: 5.0 } rating: { lessOrEquals: 5.0 } rating: { in: [4.5, 5.0] } rating: { notIn: [1.0, 2.0] } rating: { isNull: false } ``` ### Date Operators ```graphql startDate: { is: "2025-01-15" } startDate: { isNot: "2025-01-01" } startDate: { greater: "2025-01-01" } startDate: { greaterOrEquals: "2025-01-01" } startDate: { less: "2025-12-31" } startDate: { lessOrEquals: "2025-12-31" } startDate: { between: { start: "2025-01-01", end: "2025-12-31" } } startDate: { in: ["2025-01-01", "2025-06-01", "2025-12-01"] } startDate: { isNull: false } ``` ### Boolean Operators ```graphql active: { is: true } archived: { is: false } completed: { isNull: true } ``` ### ID Operators ```graphql id: { is: "abc-123-def-456" } id: { isNot: "old-id" } id: { in: ["id1", "id2", "id3"] } id: { notIn: ["excluded-id"] } id: { isNull: false } ``` ### Relation Operators ```graphql # Filter by relation's field status: { name: { is: "Active" } } status: { name: { in: ["Active", "Planning"] } } # Check if relation exists status: { id: { isNull: false } } # Filter by relation's nested field team: { department: { name: { is: "Engineering" } } } ``` ### Collection Operators ```graphql # Check if collection is empty tasks: { isEmpty: true } tasks: { isEmpty: false } # Check if collection contains item matching filter (AND logic) tasks: { contains: [ { name: { is: "Frontend" } } { active: { is: true } } ] } # Check if collection contains ANY item matching filters (OR logic) tasks: { containsAny: [ { name: { is: "Frontend" } } { name: { is: "Backend" } } ] } # Check if collection does NOT contain items matching filters (AND logic) tasks: { notContains: [ { active: { is: false } } ] } # Check if collection does NOT contain ANY items matching filters (OR logic) tasks: { notContainsAny: [ { name: { is: "Archived" } } { name: { is: "Deprecated" } } ] } ``` --- ## Rich Text & Comments ### Rich Text Field Formats Rich text fields can be queried in four formats: ```graphql { findBugs { id name description { text # Plain text, no formatting md # Markdown format html # HTML format jsonString # JSON representation } } } ``` **Output:** ```json { "data": { "findBugs": [ { "id": "abc-123", "name": "Fix login bug", "description": { "text": "User cannot login with valid credentials", "md": "User **cannot** login with valid credentials", "html": "
User cannot login with valid credentials
", "jsonString": "{\"type\":\"doc\",\"content\":[...]}" } } ] } } ``` ### Comments Comments are queryable just like rich text fields: ```graphql { findBugs { name comments { text md html } } } ``` ### Rich Text Mutations #### Overwrite Content ```graphql mutation { bugs(id: { is: "abc-123" }) { overwriteDescription(value: "New content") { message } } } ``` #### Append Content ```graphql mutation { bugs { appendContentToDescription(value: "Additional text") { message } } } ``` #### Prepend Content ```graphql mutation { bugs { prependContentToDescription(value: "Important note: ") { message } } } ``` ### Using Templates in Rich Text Templates support the same syntax as Fibery automation templates: ```graphql mutation { bugs(assignees: { containsAny: { email: { contains: "john" } } }) { appendContentToStepsToReproduce( value: "Current state: {{State.Name}}" ) { message } } } ``` **Template variables:** - `{{Name}}` - Current entity field - `{{State.Name}}` - Related entity field - `{{CurrentUser.Email}}` - Current user info - `{{Assignees}}` - Collection fields ### Add Comments ```graphql mutation { bugs { addComment(value: "I will fix this ASAP") { message } } } ``` With template: ```graphql mutation { bugs { addComment(value: "Assigned to: {{Assignees}}") { message } } } ``` --- ## Advanced Patterns ### Send Notifications ```graphql mutation { bugs(release: { id: { isNull: false } }) { notifyCreatedBy(subject: "Please take a look") { message } notifyAssignees( subject: "Fix ASAP" message: "Fix bug {{Name}} ASAP" ) { message } notifyUsers( to: { email: { contains: "john" } } message: "Waiting for fix..." ) { message } } } ``` **Output:** ```json { "data": { "bugs": { "notifyCreatedBy": { "message": "Notify Created By: 3 notifications sent" }, "notifyAssignees": { "message": "Notify Assignees: 5 notifications sent" }, "notifyUsers": { "message": "Notify Users: 1 notification sent" } } } } ``` ### Attach Files from URL ```graphql mutation { bugs(release: { id: { isNull: false } }) { addFileFromUrl( name: "Logo" url: "https://example.com/logo.svg" ) { message } } } ``` ### Generate PDF from Template ```graphql mutation { bugs { attachPdfUsingTemplate( value: "Report generated by {{CurrentUser.Email}}" ) { message } } } ``` ### Current User Reference ```graphql { findTasks( assignees: { contains: { id: { is: "$my-id" } } } ) { name assignees { name email } } } ``` Use `$my-id` to reference the current authenticated user. ### Deep Nesting (3+ Levels) ```graphql { findProjects { name teams { name active lead { name email department { name division { name manager { name } } } } } } } ``` No limit on nesting depth, but consider performance. --- ## Performance & Optimization ### Avoiding Timeouts For long-running operations, use one of these strategies: #### 1. Background Jobs Execute mutations asynchronously to avoid timeouts: ```graphql # Start background job mutation { features { executeAsBackgroundJob { jobId actions { createBatch(data: [ { name: "Feature 1" } { name: "Feature 2" } { name: "Feature 3" } ]) { message } } } } } ``` **Output:** ```json { "data": { "features": { "executeAsBackgroundJob": { "jobId": "6fc2d2b3-7b31-4511-b829-99a8cefb36b3", "actions": null } } } } ``` Check job status: ```graphql { job(id: "6fc2d2b3-7b31-4511-b829-99a8cefb36b3") { status # EXECUTING, FAILED, or COMPLETED message actions { actionName result { message entities { id } } } } } ``` When status is `COMPLETED`, actions will be populated: ```json { "data": { "job": { "status": "COMPLETED", "message": "Completed successfully", "actions": [ { "actionName": "createBatch", "result": { "message": "Create: 3 Features added", "entities": [ { "id": "id-1" }, { "id": "id-2" }, { "id": "id-3" } ] } } ] } } } ``` **Note:** Job results are available for ~30 minutes. #### 2. Paging for Mutations Process data in batches to avoid timeouts: ```graphql # Execute with limit until no more entities match mutation { stories( limit: 10 sprint: { id: { isNull: true } } ) { update( sprint: { limit: 1 orderBy: { dates: { start: ASC } } } ) { message } countOfEntities } } ``` **First execution:** ```json { "data": { "stories": { "update": { "message": "Update: 10 Stories updated" }, "countOfEntities": 10 } } } ``` Execute again until `countOfEntities` < `limit`: ```json { "data": { "stories": { "update": { "message": "Update: 4 Stories updated" }, "countOfEntities": 4 } } } ``` Now stop - processed fewer than limit. #### Pagination Loop Pattern ```javascript async function updateAllInBatches(spaceName, filterQuery, updateArgs) { const batchSize = 100; let processedCount = 0; while (true) { const mutation = ` mutation { entities(${filterQuery} limit: ${batchSize}) { update(${updateArgs}) { message } countOfEntities } } `; const result = await fibery.graphql(spaceName, mutation); const count = result.data.entities.countOfEntities; processedCount += count; console.log(`Processed ${processedCount} entities`); if (count < batchSize) { break; // Done } // Optional: add small delay to avoid rate limiting await new Promise(resolve => setTimeout(resolve, 350)); } return processedCount; } ``` ### Default Limits - **Query default:** 100 records - **Maximum limit:** 1000 records per query - Use `offset` and `limit` for pagination - Use `orderBy` for consistent pagination ### Rate Limiting Strategy With 3 requests/second limit: ```javascript class RateLimiter { constructor(requestsPerSecond = 3) { this.delay = 1000 / requestsPerSecond; this.lastRequest = 0; } async throttle() { const now = Date.now(); const timeSinceLastRequest = now - this.lastRequest; if (timeSinceLastRequest < this.delay) { await new Promise(resolve => setTimeout(resolve, this.delay - timeSinceLastRequest)); } this.lastRequest = Date.now(); } } // Usage const limiter = new RateLimiter(3); async function makeGraphQLRequest(query) { await limiter.throttle(); return await fibery.graphql('Projects', query); } ``` ### Non-ASCII Character Handling Non-ASCII or non-English characters in field or database names will be transliterated to English: - `Тест` → `Test` - `项目` → `Xiangmu` - `Projet` → `Projet` (already ASCII) --- ## Complete Examples ### Example 1: Complex Query with All Features ```graphql { findProjects( status: { name: { is: "Active" } } team: { name: { contains: "Engineering" } } budget: { greater: 50000 } startDate: { greater: "2025-01-01" } tasks: { contains: { active: { is: true } } } orderBy: { startDate: DESC } limit: 50 ) { id name startDate budget status { id name priority } team { id name department { name } } tasks(active: { is: true }) { id name active lead { id name email } } description { md } comments { text } } } ``` **This demonstrates:** - Multiple filters (status, team, budget, date, collection) - Sorting (by startDate descending) - Explicit limit (50 records) - Primitive fields (name, startDate, budget) - To-one relations (status, team) - Deep nesting (team → department) - To-many collections (tasks) - Collection filter on parent query - Collection filter on results (active tasks only) - Rich text field (description) - Comments field - Proper camelCase throughout - Correct three-level filter nesting ### Example 2: Complete CRUD Workflow ```graphql # 1. Create project with relations mutation { projects { create( name: "Mobile App Redesign" budget: 75000 startDate: "2025-02-01" status: { name: { is: "Planning" } } team: { name: { is: "Mobile Team" } } ) { message entities { id type } } } } # 2. Query the created project { findProjects(name: { is: "Mobile App Redesign" }) { id name budget status { name } team { name } } } # 3. Update the project mutation { projects(name: { is: "Mobile App Redesign" }) { update( budget: 80000 status: { name: { is: "Active" } } ) { message } appendContentToDescription( value: "Budget increased due to scope expansion" ) { message } } } # 4. Add tasks to project mutation { projects(name: { is: "Mobile App Redesign" }) { addTasksItemBatch(data: [ { name: "UI Design" } { name: "Backend API" } { name: "Testing" } ]) { message } } } # 5. Query with tasks { findProjects(name: { is: "Mobile App Redesign" }) { name budget status { name } tasks { id name } } } # 6. Delete when done (with safety check) mutation { projects(name: { is: "Mobile App Redesign" }) { listEntities { id name } countOfEntities } } mutation { projects(name: { is: "Mobile App Redesign" }) { delete { message } } } ``` ### Example 3: Bulk Operations with Background Job ```graphql # Process 1000+ features asynchronously mutation { features { executeAsBackgroundJob { jobId actions { createBatch(data: [ { name: "Feature 1", priority: { name: { is: "High" } } } { name: "Feature 2", priority: { name: { is: "Medium" } } } # ... 998 more items ]) { message } assignToMe { message } update(state: { name: { is: "Backlog" } }) { message } } } } } # Check status { job(id: "your-job-id") { status message actions { actionName result { message } } } } ``` ### Example 4: Reporting Query with Aliases ```graphql { totalProjects: findProjects { id } activeProjects: findProjects( status: { name: { is: "Active" } } ) { id name budget team { name } } planningProjects: findProjects( status: { name: { is: "Planning" } } ) { id name startDate } completedProjects: findProjects( status: { name: { is: "Completed" } } ) { id name budget } highBudgetProjects: findProjects( budget: { greater: 100000 } ) { id name budget } } ``` Process results: ```javascript const result = await fibery.graphql('Projects', query); const report = { total: result.data.totalProjects.length, active: result.data.activeProjects.length, planning: result.data.planningProjects.length, completed: result.data.completedProjects.length, highBudget: result.data.highBudgetProjects.length, activeList: result.data.activeProjects }; console.log(`Total Projects: ${report.total}`); console.log(`Active: ${report.active}`); console.log(`Planning: ${report.planning}`); console.log(`Completed: ${report.completed}`); console.log(`High Budget (>100K): ${report.highBudget}`); ``` --- ## Troubleshooting Guide ### Common Errors and Solutions #### Error: "Field 'X' of type 'Y' must have a selection of subfields" **Problem:** Relation field without nested selection. ```graphql # ❌ WRONG { findProjects { name status } } ``` **Solution:** Add subfield selection. ```graphql # ✅ CORRECT { findProjects { name status { id name } } } ``` #### Error: "Field 'X' must not have a selection since type 'String' has no subfields" **Problem:** Primitive field with subfield selection. ```graphql # ❌ WRONG { findProjects { name { id value } } } ``` **Solution:** Remove subfield selection. ```graphql # ✅ CORRECT { findProjects { name } } ``` #### Error: "Field 'is' is not defined by type 'XxxFilter'" **Problem:** Missing intermediate field in relation filter. ```graphql # ❌ WRONG findProjects(status: { is: "Active" }) ``` **Solution:** Add three-level nesting through field. ```graphql # ✅ CORRECT findProjects(status: { name: { is: "Active" } }) ``` #### Error: "Cannot query field 'Name' on type 'Project'" **Problem:** Using PascalCase instead of camelCase. ```graphql # ❌ WRONG { findProjects { Name Status { Name } } } ``` **Solution:** Use camelCase. ```graphql # ✅ CORRECT { findProjects { name status { name } } } ``` #### Error: "Unknown type 'findProject'" **Problem:** Using singular instead of plural. ```graphql # ❌ WRONG { findProject { name } } { findProjectsProjects { name } } ``` **Solution:** Use find + PluralCamelCase. ```graphql # ✅ CORRECT { findProjects { name } } ``` #### Error: HTTP 429 "Too Many Requests" **Problem:** Exceeded 3 requests per second rate limit. **Solution:** Implement rate limiting. ```javascript const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); async function rateLimitedRequests(queries) { const results = []; for (const query of queries) { results.push(await fibery.graphql('Projects', query)); await delay(350); // 350ms between requests = ~2.8 req/sec } return results; } ``` #### Error: Request Timeout **Problem:** Operation taking too long. **Solutions:** 1. Use background jobs for large operations 2. Use paging with limit/offset 3. Break into smaller batches ```graphql # Use background job mutation { entities { executeAsBackgroundJob { jobId actions { # your operations } } } } # Or use paging mutation { entities(limit: 100) { update(field: value) { message } countOfEntities } } ``` #### Error: "Cannot read property 'X' of null/undefined" **Problem:** Accessing field that doesn't exist or is null. **Solution:** Handle null values in code. ```javascript const result = await fibery.graphql('Projects', query); const projects = result.data?.findProjects || []; projects.forEach(project => { const statusName = project.status?.name || 'No Status'; const teamName = project.team?.name || 'Unassigned'; console.log(`${project.name}: ${statusName}, ${teamName}`); }); ``` --- ## Decision Trees ### For Queries ``` When writing a Fibery GraphQL query: 1. Identify database name └─> Convert to: find{DatabasePlural} in camelCase Example: Projects/Project → findProjects 2. For each field in selection: ├─> Is it primitive (text/number/date/bool/id)? │ └─> YES: Write field name only (camelCase) │ Example: Projects/Name → name │ └─> Is it a relation/collection? └─> YES: Add nested selection { id name ... } Example: status { id name } 3. For rich text fields: └─> Add format subfield { text / md / html / jsonString } 4. For filters on relations: └─> ALWAYS use: relationField: { subfield: { operator: value } } Example: status: { name: { is: "Active" } } (Three-level nesting required) 5. For filters on collections: ├─> Filter BY collection content: │ └─> tasks: { contains: { filter } } │ └─> Filter collection RESULTS: └─> tasks(filter) { fields } 6. Always add explicit limit for safety └─> limit: 100 (or appropriate number) 7. Use orderBy for consistent results └─> orderBy: { field: ASC/DESC } 8. Use offset for pagination └─> offset: 0, 100, 200, ... ``` ### For Mutations ``` When writing a Fibery GraphQL mutation: 1. Determine operation type: ├─> Create new entities → create / createBatch ├─> Modify existing → update / updateBatch ├─> Remove entities → delete ├─> Link relations → link{Relation} / add{Relation}Item ├─> Unlink relations → unlink{Relation} └─> Delete relations → delete{Relation} 2. Apply filter if targeting existing entities: └─> database(filter) { actions } Example: bugs(state: { name: { is: "Done" } }) 3. Chain actions if needed: └─> All actions in same mutation block create → assignToMe → addComment 4. Use batch for multiple items: └─> createBatch(data: [...]) updateBatch(data: [...]) 5. For safety: ├─> Verify with listEntities / countOfEntities first └─> Then execute delete 6. For large operations: ├─> Use executeAsBackgroundJob └─> Or use limit + loop pattern 7. Return message and entities: └─> { message, entities { id, type } } ``` --- ## Quick Validation Checklist ### Before Submitting a Query - [ ] All field names are camelCase (not PascalCase) - [ ] No `Projects/` or `fibery/` prefixes in field names - [ ] All relation fields have `{ id name }` or similar subselection - [ ] Rich text fields have format subfield `{ md }` or `{ text }` - [ ] No primitive fields have subselection - [ ] Relation filters use three-level nesting: `field: { subfield: { operator: value } }` - [ ] Collection filters use correct operator: `contains`, `containsAny`, `isEmpty` - [ ] Query has explicit `limit` parameter - [ ] Database method is `find{Plural}` not `find{Singular}` - [ ] If filtering collection results, syntax is: `collection(filter) { fields }` ### Before Submitting a Mutation - [ ] Mutation block starts with `mutation {` - [ ] Database name is camelCase plural without `find` prefix - [ ] Filter syntax matches query filter syntax - [ ] Actions return `{ message }` or `{ message, entities { id, type } }` - [ ] Using batch operations for multiple items - [ ] For deletes: verified with `listEntities` / `countOfEntities` first - [ ] For large operations: using background jobs or paging - [ ] Action names are correct: `create`, `update`, `delete`, etc. - [ ] Relation operations use correct syntax: `link{Relation}`, `unlink{Relation}` - [ ] Rich text operations specify correct field name --- ## Best Practices Summary ### Queries 1. **Always specify limit** - Default 100 may not be what you expect 2. **Use orderBy for pagination** - Ensures consistent results across pages 3. **Filter at the source** - Filter in query, not in code when possible 4. **Request only needed fields** - Improves performance 5. **Use aliases for multiple filtered sets** - Better than multiple queries 6. **Handle null values** - Use optional chaining in code ### Mutations 1. **Verify before delete** - Always check count/list first 2. **Use batch operations** - More efficient than loops 3. **Chain related actions** - Single mutation is atomic 4. **Use background jobs for bulk** - Avoid timeouts 5. **Implement pagination for large updates** - Process in chunks 6. **Return entities for chaining** - Get IDs for follow-up operations ### General 1. **Respect rate limits** - 3 requests/second 2. **Use camelCase consistently** - Never PascalCase or namespaces 3. **Test filters before mutations** - Use queries to verify 4. **Monitor background jobs** - Check status regularly 5. **Handle errors gracefully** - Always check response.errors 6. **Document complex queries** - Add comments for maintenance --- ## Additional Resources - **GraphQL Playground**: `https://your-account.fibery.io/api/graphql/space/Your_Space` - **Fibery API Docs**: Explore schema using Docs panel in GraphQL Playground - **Markdown Templates**: [Fibery Documentation](https://fibery.io/help) - **Authentication Tokens**: Generate in Fibery Settings → API --- ## Version History - **v1.0** (October 2025): Initial public release - comprehensive guide covering queries, mutations, filtering, rich text, performance optimization, and troubleshooting --- ## Disclaimer This guide is maintained independently and is not officially endorsed by Fibery. For official support and the most current API specifications, please visit [api.fibery.io](https://api.fibery.io) or contact Fibery support. --- **End of Fibery GraphQL Developer Guide v1.0**