Obtain a list of sheets and objects on that sheet from an analytics app
This guide shows you how to explore the internal structure of sheet objects in a Qlik Sense app
programmatically using the Qlik API TypeScript library (@qlik/api).
Building on the app sheet list example, you’ll learn how to:
- Understand the relationships between sheets and their child objects
- Retrieve the list of objects belonging to a sheet
- Resolve objects down to their root visualizations through recursive traversal
- Handle container objects that nest other visualizations
- Identify and resolve master item references
- Prevent infinite loops when traversing circular references
- Track master item usage across sheets
- Generate visualization type distribution reports
- Create comprehensive app inventories for analysis
Use this guide to build tools that introspect and automate app management.
The complete working code for this example is available in the qlik-cloud-examples repository.
Prerequisites
- Node.js 22+ and npm
- Access to a Qlik Cloud tenant
- OAuth client credentials (Client ID and Secret with
user_defaultscopes) - A Qlik Sense app with sheets and visualizations (or use the Qlik Cloud App Analyzer as this contains both master items and container objects)
Understanding generic objects and their relationships
In Qlik Sense, generic objects are flexible JSON
structures representing app components (sheets, visualizations, bookmarks, master items, etc.).
Generic objects are identified by their qType property and can contain or reference other generic objects.
There’s no explicit parent-child structure in Qlik apps. Instead, objects form relationships through specific properties:
cells- Sheet properties contain acellsarray listing the direct children on the sheetqChildList- Object layouts may include this to reference nested objects (for containers)qExtendsId- Objects based on master items use this to reference the master object definition
When working with visualizations, you’ll encounter these common object types:
- Sheets - Generic objects with
qType: "sheet"that contain child visualizations in theircellsproperty - Visualizations - Chart objects (bar chart, KPI, table, etc.) identified by their
visualizationproperty - Container objects - Visualizations that hold other visualizations (they have
cellsin properties and/or children in layout) - Master items - Reusable object definitions referenced via
qExtendsId - Extension objects - Custom visualizations with their own
visualizationidentifier
The key challenge is resolving objects that reference or contain other objects. This guide shows you how to traverse these relationships to find all visualizations and understand their definitions.
Set up environment configuration
Create a .env file in your project root:
QLIK_HOST=your-tenant.region.qlikcloud.comQLIK_CLIENT_ID=your-client-idQLIK_CLIENT_SECRET=your-client-secretQLIK_APP_ID=your-app-idSAVE_LAYOUTS=falseSet SAVE_LAYOUTS=true if you want to save the layout JSON for each object to disk for inspection.
Initialize the Qlik API Client with OAuth2
import 'dotenv/config';import { auth, qix } from "@qlik/api";import fs from 'fs';import path from 'path';
const hostConfig = { authType: "oauth2", host: process.env.QLIK_HOST, clientId: process.env.QLIK_CLIENT_ID, clientSecret: process.env.QLIK_CLIENT_SECRET,};
auth.setDefaultHostConfig(hostConfig);This example uses OAuth2 M2M (machine-to-machine) authentication, which is ideal for server-side scripts and automation tools.
Why OAuth instead of API Key?
This example requires a WebSocket connection to the Qlik Engine API. While API keys work, they require configuring a web integration for WebSocket connections. OAuth provides direct WebSocket access without additional configuration.
Open the app session
const appId = process.env.QLIK_APP_ID;const session = qix.openAppSession({ appId, withoutData: true });const app = await session.getDoc();The withoutData: true option opens the app without loading data into memory, which improves performance when you only
need to inspect the app structure.
Understand how sheet children are stored
Unlike what you might expect from the sheet list, the actual child objects on a sheet are stored in the sheet’s properties, not its layout:
// Get the sheet list (contains basic metadata)const sheetList = await app.getSheetList();
for (const sheet of sheetList) { console.log(`Sheet: ${sheet.qMeta.title}`);
// Get the actual sheet object const sheetObject = await app.getObject(sheet.qInfo.qId);
// Get properties - this is where children are stored const sheetProperties = await sheetObject.getProperties();
// The children are in the cells property const children = sheetProperties.cells || [];
console.log(`└─ Sheet has ${children.length} object(s)`);
for (const child of children) { console.log(` Object ID: ${child.name}`); }}getSheetList()returns lightweight metadata includingqChildList, but this doesn’t contain all the information- To get the actual child object IDs, you must call
getObject()andgetProperties()on the sheet - Sheet children are in the
cells[]array in properties, where each cell has anameproperty containing the object ID - Properties contain the configuration (what’s defined), while layout contains the runtime state (what’s evaluated). For more information, see Properties vs layout: understanding the difference.
Recursively traverse child objects
Some objects contain other objects (containers). You need to recursively traverse these relationships:
// Track visited objects to prevent infinite loopsconst visitedObjects = new Set();
async function getChildObjects(objectId, indent = ' ') { // Prevent circular references if (visitedObjects.has(objectId)) { return 0; }
visitedObjects.add(objectId);
try { const childObject = await app.getObject(objectId); const childProperties = await childObject.getProperties(); const layout = await childObject.getLayout();
// Extract title and type const title = layout.title || layout.qMeta?.title || 'Untitled'; const type = layout.visualization;
console.log(`${indent}Object ID: ${objectId}`); console.log(`${indent} Name: ${title}`); console.log(`${indent} Type: ${type}`);
let childCount = 0; const childIds = new Set();
// Check properties for container cells if (childProperties.cells && childProperties.cells.length > 0) { for (const cell of childProperties.cells) { if (cell.name) { childIds.add(cell.name); } } }
// Check layout for child list if (layout.qChildList && layout.qChildList.qItems) { for (const layoutChild of layout.qChildList.qItems) { if (layoutChild.qInfo && layoutChild.qInfo.qId) { childIds.add(layoutChild.qInfo.qId); } } }
// Process all children recursively if (childIds.size > 0) { console.log(`${indent} └─ Has ${childIds.size} child object(s):`); for (const childId of childIds) { childCount += await getChildObjects(childId, indent + ' '); } }
return 1 + childCount; } catch (error) { // Object might not be accessible return 0; }}Why fetch both properties and layout?
You need to call both getProperties() and getLayout() for each object to build a detailed object inventory:
- Properties gives child object IDs (in
cells[]) and configuration details likeqExtendsId - Layout gives display information (title, visualization type) and sometimes additional child information in
qChildList
Different object types store child references in different places, so checking both ensures you find all nested objects.
Why track visited objects?
Apps can have circular references or shared objects. The visitedObjects Set prevents infinite loops by skipping
objects you’ve already processed.
Two places to find children:
properties.cells[]- Contains children for container visualizations (the configuration)layout.qChildList.qItems[]- Contains children for some object types (the evaluated state)
Check both locations to ensure you find all nested objects.
Identify master item references
Objects can reference master items through the qExtendsId property:
async function getChildObjects(objectId, indent = ' ') { // ... previous code ...
const childObject = await app.getObject(objectId); const childProperties = await childObject.getProperties(); const layout = await childObject.getLayout();
// Check if this is a master item instance const isMasterItem = childProperties.qExtendsId && !objectId.includes('qlik-compound-context'); const masterItemId = childProperties.qExtendsId || null;
const title = layout.title || layout.qMeta?.title || 'Untitled'; const type = layout.visualization;
console.log(`${indent}Object ID: ${objectId}`); console.log(`${indent} Name: ${title}`); console.log(`${indent} Type: ${type}`);
if (isMasterItem && masterItemId) { console.log(`${indent} Master Item ID: ${masterItemId}`); }
// ... rest of function ...}Important notes:
qExtendsIdis found in properties, not layout - this is why you need to callgetProperties()to look for master item relationships- You filter out
qlik-compound-contextobjects because these are internal system objects that technically haveqExtendsIdbut aren’t user-facing master items - The
titleandvisualizationtype come from the layout because those are runtime-evaluated values
Optional: Save layouts and properties to disk
For debugging and analysis, you can save each object’s layout and properties as JSON files to disk, by
setting SAVE_LAYOUTS=true in your .env file.
This creates a layouts/<app-id>/ directory with JSON files for sheets, objects, and a comprehensive inventory:
- Each sheet’s layout:
sheet-layout_<sheet-id>.json - Each sheet’s properties:
sheet-properties_<sheet-id>.json - Each object’s layout:
viz_<object-id>.json - Complete app analysis:
object-library.json
The layout and properties files contain full metadata, configuration, and child object information.
The Object Library File
The object-library.json file contains a comprehensive analysis of your app:
- Sheets: List of all sheets with IDs, titles, and object counts
- Objects: Complete inventory of all objects with ID, title, type, master item status, depth, and associated sheet
- Master Item Usage: Detailed tracking showing each master item ID, total usage count, and every sheet/object where it’s used
- Visualization Types: Distribution of visualization types across the app (sorted by frequency)
- Summary Statistics: Total sheets, objects, unique visualization types, and master items used
Use Cases for Saving Layouts:
- Debugging object structures - Inspect the complete JSON structure of complex objects
- Understanding object relationships - See how objects reference each other
- Analyzing app composition - Query the object library to build reports on what types of objects are used
- Tracking master item usage - Identify where master items are used across sheets
- Comparing sheets vs objects - See the difference between sheet properties and object layouts
- Exporting configurations - Document or migrate object and sheet definitions
- Auditing visualizations - Understand the distribution of visualization types in your app
Get the complete example
The complete working example that combines all these concepts is available in the qlik-cloud-examples repository:
- Clone or download the repository
- Navigate to the
app-sheet-list-objectsdirectory - Copy
.env.exampleto.envand add your credentials - Run
npm installto install dependencies - Run
npm startto execute the script
The repository includes:
- Complete source code with all the patterns discussed
- Package.json with dependencies configured
- README with detailed setup instructions
- Example .env file for configuration
Use this as a template for building your own app analysis tools.
Understanding the output
When you run the script from the repository, you’ll see output like this:
Example Output:
Sheet: Dashboard└─ Sheet has 3 object(s): - Object ID: abc123 Name: Sales Overview Type: barchart Master Item ID: master-123 - Object ID: def456 Name: KPI Panel Type: sn-layout-container └─ Has 2 child object(s): - Object ID: ghi789 Name: Total Sales Type: kpi - Object ID: jkl012 Name: Total Orders Type: kpi
==================================================Summary: Total Sheets: 1 Total Objects: 5==================================================
==================================================Master Item Usage:==================================================
Master Item ID: master-123 Used 1 time(s): └─ Sheet: "Dashboard" (sheet1) 1 instance(s) - abc123: "Sales Overview"
==================================================Visualization Type Distribution:================================================== kpi: 2 barchart: 1 sn-layout-container: 1==================================================The output includes four main sections:
Sheet and object details
For each object, the script displays:
- Object ID: The unique identifier for the generic object
- Object Name: The title from the layout (user-visible name)
- Object Type: The visualization type from
layout.visualization - Master Item ID (if applicable): The ID of the master item this object is based on
- Child objects: Any nested objects within containers, with recursive traversal
Summary statistics
Shows the total count of sheets and objects (including nested objects) found in the app.
Master item usage report
For apps that use master items, this section shows:
- Each master item ID
- How many times it’s used across the app
- Which sheets contain the master item
- The specific object instances (ID and title) on each sheet
This helps identify:
- Which master items the app uses most
- Where specific master items are referenced
- Opportunities to consolidate duplicate visualizations into master items
Visualization type distribution
Shows all visualization types found in the app, sorted by frequency. This helps you:
- Understand the composition of your app
- Identify which chart types are most common
- Audit custom extensions vs. native visualizations
Properties vs layout: Understanding the difference
Generic objects have two critical components you interact with through separate API calls. Understanding the difference is essential for working with Qlik apps.
Properties (getProperties()) - Design-time configuration:
cells[]- Child object IDs (what objects are on the sheet)qExtendsId- Master item referenceqMetaDef- Basic metadata (sheets only)- Dimension and measure definitions
- Configuration settings
Layout (getLayout()) - Runtime evaluated state:
qMeta- Rich metadata (owner, privileges, timestamps)qChildList- Evaluated child objects with their layoutsvisualization- Visualization type- Computed titles and labels
- Current data and calculated values
- User permissions
When to use each:
- Use properties when you need to modify/clone objects, access child IDs, or view master item relationships
- Use layout when you need display information, runtime data, or user permissions
In this example, you need both because children can appear in properties.cells[] OR layout.qChildList, depending on
the object type.
Key patterns
- Find children: In
sheetProperties.cells[](must callgetProperties()on the sheet),containerProperties.cells[], andlayout.qChildList.qItems[] - Prevent loops: Track visited objects within a set to avoid infinite depth recursion
- Master items: Filter out
qlik-compound-contextwhen checkingqExtendsId - Handle missing titles: Use
layout.title || layout.qMeta?.title || 'Untitled'. This checkslayout.titlefirst, then falls back tolayout.qMeta.title(for master items), and finally defaults to'Untitled'.
Advanced: Using the object inventory
The example code already includes a starter inventory tracker built-in. When you enable SAVE_LAYOUTS=true, it
generates per-object files, and an object-library.json file with detailed analysis.
Querying the Object Library
You can use tools like jq to query the inventory file:
# Find all objects of a specific typejq '.objects[] | select(.type == "barchart")' layouts/<app-id>/object-library.json
# Count objects by typejq '.visualizationTypes | sort_by(-.count)' layouts/<app-id>/object-library.json
# Find which sheets use a specific master itemjq '.masterItemUsage[] | select(.masterItemId == "abc123") | .usages' layouts/<app-id>/object-library.json
# Get objects at a specific nesting depthjq '.objects[] | select(.depth == 2)' layouts/<app-id>/object-library.json
# List all sheets with more than 10 objectsjq '.sheets[] | select(.objectCount > 10)' layouts/<app-id>/object-library.jsonExtending the Inventory
The inventory is built using JavaScript Maps during traversal:
const inventory = { sheets: [], objects: [], masterItemUsage: new Map(), visualizationTypes: new Map(),};During object traversal, the script:
- Tracks each visualization type and its count
- Records master item usage with sheet and object details
- Captures object depth for understanding nesting complexity
- Associates each object with its parent sheet
You can extend this pattern to track additional metrics like:
- Custom properties or themes
- Data model fields used in dimensions/measures
- Objects with specific configurations
- Performance characteristics
Summary
You now understand how to:
- Work with the generic object model in Qlik Sense
- Open an app session with
withoutData: truefor structure-only inspection - Navigate from sheets to their child objects using
getProperties()and thecells[]array - Retrieve full object details with
getObject(),getProperties(), andgetLayout() - Recursively traverse container relationships through both
properties.cells[]andlayout.qChildList - Resolve master item references using the
qExtendsIdproperty (while filtering compound context objects) - Prevent infinite loops by tracking visited objects
- Handle fallback values for missing object titles
- Track and analyze master item usage across sheets
- Generate visualization type distribution reports
- Create comprehensive object inventories with the
object-library.jsonfile - Query inventory data for app composition analysis
Next steps
- Use these patterns to build automation tools, migration scripts, app analyzers, or documentation generators.
- Clone the complete working example from the qlik-cloud-examples repository and use it as a template for your own tools.