qlik-sdk-typescript
The Qlik Platform SDKs are a suite of tools, libraries and, documentations that simplifies building high-quality and performant applications on top of the Qlik Sense Platform.
Getting started
Node.js needs to be installed to use @qlik/sdk in a node.js project.
The node.js version is important, please use a version later than or equal to 18.10.0
.
If you are using an earlier version of node.js then make sure that the fetch function is available.
Please see the fetch section for details: Fetch
With node.js installed, create a node.js project Install qlik/sdk and try the API keys example.
Install
npm i @qlik/sdk
yarn add @qlik/sdk
Authentication options
API keys
An API key is a token representing a user in your tenant. Anyone may interact with the platform programmatically using the API key. The token contains the user context, respecting the access control privileges the user has in your tenant. More info can be found on Qlik Dev Portal.
For a step-by-step guide on how to get an API key for your tenant, check this tutorial.
import { Auth, AuthType, Config } from '@qlik/sdk';
const config: Config = {
authType: AuthType.ApiKey,
host: 'my-tenant.qlikcloud.com',
apiKey: '<apiKey>',
};
const auth = new Auth(config);
await auth.rest('/users/me');
Web integration ID
A web integration ID will allow you to handle CORS requests in Qlik Sense SaaS, which by default does not allow third-party domains to interact with your tenant APIs. More info on Qlik Dev Portal.
For a step-by-step guide on how to get a web integration ID for your tenant, check Qlik Help.
import { Auth, AuthType, Config } from '@qlik/sdk';
const config: Config = {
authType: AuthType.WebIntegration,
host: 'my-tenant.qlikcloud.com',
webIntegrationId: '<webintegrationId>',
autoRedirect: true,
};
const auth = new Auth(config);
if(!auth.isAuthenticated()){
auth.authenticate();
}
JWT Auth
A digitally signed JSON web token that can be verified and trusted using a public / private key pair. More info on Qlik Dev Portal.
For a step-by-step guide on how to create a signed token for JWT Authorization for your tenant, check Qlik Dev Portal
import { Auth, AuthType, Config } from '@qlik/sdk';
const config: Config = {
authType: AuthType.JWTAuth,
host: 'my-tenant.qlikcloud.com',
webIntegrationId: '<webintegrationId>',
fetchToken: () => Promise.resolve('<signedToken>'),
};
const auth = new Auth(config);
await auth.getSessionCookie();
OAuth2
OAuth is a standard security protocol for authorization and delegation. It allows third party applications to access API resources without disclosing the end-user credentials.
For a step-by-step guide on how to create an OAuth client for your tenant, check Creating and managing OAuth clients
import { Auth, AuthType, Config } from '@qlik/sdk';
const config: Config = {
authType: AuthType.OAuth2,
host: 'my-tenant.qlikcloud.com',
clientId: '<clientId>',
clientSecret: '<clientSecret>',
redirectUri: '<redirectUri>',
scopes: '<scopes>',
};
const auth = new Auth(config);
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
if (code) {
await auth.authorize(window.location.href);
const wsUrl = await auth.generateWebsocketUrl(appId);
}
if (! await auth.isAuthorized()) {
const { url } = await auth.generateAuthorizationUrl();
window.location = url;
}
await auth.deauthorize();
await auth.refreshToken();
Fetch
The global.fetch function is assumed to be available.
If it is not availble then it can be added using:
ES6-Modules
- Add
node-fetch
as dependency - Import by overriding global fetch
global.fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
CommonJS
- Add
cross-fetch
as dependency - Use this code snippet:
const fetch = require('cross-fetch');
global.fetch = fetch
Object events
It is possible to register object event listener functions using the syntax: .on('event-name', listener)
.
In this example the object changed
event is listened to.
const qlik = new Qlik({
authType: AuthType.APIKey,
host: "",
apiKey: "",
});
const randomId = Math.random().toString(32).substring(3);
const sessionAppId = `SessionApp_${randomId}`;
const app = await qlik.apps.createSessionApp(sessionAppId);
const session = await app.open();
const script = `
TempTable:
Load
RecNo() as ID,
Rand() as Value
AutoGenerate 100
`;
await app.setScript(script);
const properties = {
qInfo: {
qType: 'my-straight-hypercube',
},
qHyperCubeDef: {
qDimensions: [
{
qDef: { qFieldDefs: ['ID'] },
},
],
qMeasures: [
{
qDef: { qDef: '=Sum(Value)' },
},
],
qInitialDataFetch: [
{
qHeight: 5,
qWidth: 2,
},
],
},
};
const hypercube = await app.createObject(properties);
await hypercube.getLayout();
hypercube.on('changed', () => {
console.log('changed');
});
await app.doReload();
await session.close();
Paginated responses
Some endpoints have paginated responses.
A paginated response means that a list of elements are returned together with a link to more elements of that type.
A purpose of paginated responses is to limit the number of returned elements at a time.
A get request on /items will return a paginated response.
The qlik-sdk handles paginated responses.
Examples
Express OAuth
const express = require('express');
const path = require('path');
const app = express();
require('dotenv').config({ path: path.join(__dirname, '.env') });
const Qlik = require('@qlik/sdk').default;
const { AuthType } = require('@qlik/sdk');
const qlik = new Qlik({
authType: AuthType.OAuth2,
host: process.env.OAUTH_M2M_WEB_QCS_SERVER,
clientId: process.env.OAUTH_M2M_WEB_CLIENT_ID,
clientSecret: process.env.OAUTH_M2M_WEB_CLIENT_SECRET,
redirectUri: 'http://localhost:3000/login/callback',
scopes: ['offline_access'],
});
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
let status = '';
app.get('/', async (req, res) => {
try {
const userMe = await qlik.users.getMe();
status = `Logged in as: ${userMe.name}`;
res.render(
'index',
{ loggedIn: true, status },
);
} catch (err) {
res.render('index', { loggedIn: false, status: 'Not logged in' });
}
});
app.get('/login', async (req, res) => {
const { url } = await qlik.auth.generateAuthorizationUrl();
res.redirect(301, url);
});
app.get('/login/callback', async (req, res) => {
try {
await qlik.auth.authorize(
(new URL(req.url, `http://${req.headers.host}`)).href,
);
res.redirect(301, '/');
} catch (err) {
res.sendStatus(401);
res.render('error', { error: err });
}
});
app.get('/logout', async (req, res) => {
try {
await qlik.auth.deauthorize();
res.redirect(301, '/');
} catch (err) {
res.sendStatus(500);
res.render('error', { error: err });
}
});
app.get('/refresh', async (req, res) => {
try {
await qlik.auth.refreshToken();
res.redirect(301, '/');
} catch (err) {
res.sendStatus(500);
res.render('error', { error: err?.message || err });
}
});
app.get('/websocket', async (req, res) => {
const randomId = Math.random().toString(32).substring(3);
const appId = `SessionApp_${randomId}`;
try {
const rpcSession = await qlik.auth.rpc(appId);
try {
await rpcSession.open();
const {
result: {
qReturn: {
qHandle: appHandle,
},
},
} = await rpcSession.send({
handle: -1,
method: 'GetActiveDoc',
params: [],
});
await rpcSession.send({
handle: appHandle,
method: 'SetScript',
params: { qScript: 'Load RecNo() as N autogenerate(10);' },
});
await rpcSession.send({
handle: appHandle,
method: 'DoReload',
params: [],
});
const evalResult = await rpcSession.send({
handle: appHandle,
method: 'Evaluate',
params: ['SUM([N])'],
});
res.render(
'index',
{ evalResult: evalResult.result.qReturn, loggedIn: true, status },
);
await rpcSession.close();
console.log('rpcSession closed');
} catch (err) {
console.log('rpcSession error:', err);
} finally {
await rpcSession.close();
}
} catch (err) {
res.sendStatus(500);
res.render('error', { error: err?.message || err });
}
});
module.exports = app;
Node.js
const path = require('path');
const process = require('process');
require('dotenv').config({ path: path.join(__dirname, '.env') });
const { AuthType } = require('@qlik/sdk');
const Qlik = require('@qlik/sdk').default;
const envVars = ['QCS_SERVER', 'QCS_API_KEY'];
for (let i = 0; i < envVars.length; i += 1) {
if (!(envVars[i] in process.env)) {
console.log(`Missing environment variable: ${envVars[i]}`);
process.exit(1);
}
}
(async () => {
const qlik = new Qlik({
authType: AuthType.APIKey,
host: process.env.QCS_SERVER,
apiKey: process.env.QCS_API_KEY,
});
const user = await qlik.users.getMe();
console.log(`Logged in as: ${user.name}`);
const app = await qlik.apps.create();
await app.set({ attributes: { name: 'example' } });
await app.open();
const script = 'Load RecNo() as N autogenerate(100);';
await app.setScript(script);
await app.doReload();
const evalResult = await app.evaluate('SUM([N])');
console.log(`Eval result: ${evalResult}`);
await app.delete();
})();
Node16
const path = require('path');
const process = require('process');
require('dotenv').config({ path: path.join(__dirname, '.env') });
const { Auth, AuthType } = require('@qlik/sdk');
const fetch = require('cross-fetch');
global.fetch = fetch;
const envVars = ['QCS_SERVER', 'QCS_API_KEY'];
for (let i = 0; i < envVars.length; i += 1) {
if (!(envVars[i] in process.env)) {
console.log(`Missing environment variable: ${envVars[i]}`);
process.exit(1);
}
}
const auth = new Auth({
authType: AuthType.APIKey,
host: process.env.QCS_SERVER,
apiKey: process.env.QCS_API_KEY,
});
(async () => {
try {
const res = await auth.rest('/users/me');
if (res.status !== 200) {
console.log('Failed to get /users/me');
process.exit(1);
}
const userData = await res.json();
console.log(`Logged in as: ${userData.name}`);
} catch (error) {
console.log('Error on get /users/me');
console.log(error);
}
})();
const path = require('path');
const process = require('process');
require('dotenv').config({ path: path.join(__dirname, '.env') });
const { AuthType } = require('@qlik/sdk');
const Qlik = require('@qlik/sdk').default;
const envVars = ['QCS_SERVER', 'QCS_API_KEY'];
for (let i = 0; i < envVars.length; i += 1) {
if (!(envVars[i] in process.env)) {
console.log(`Missing environment variable: ${envVars[i]}`);
process.exit(1);
}
}
(async () => {
const qlik = new Qlik({
authType: AuthType.APIKey,
host: process.env.QCS_SERVER,
apiKey: process.env.QCS_API_KEY,
});
const user = await qlik.users.getMe();
const items = await qlik.items.getItems(
{ resourceType: 'app', createdByUserId: user.id },
);
const firstItem = items[0];
console.log(`First item name = ${firstItem.name}`);
const allItemNames = [];
for await (const item of items.pagination) {
allItemNames.push(item.name);
}
const lastItem = items[items.length - 1];
console.log(`Last item name = ${lastItem.name}`);
console.log(allItemNames);
})();
Single Page Application OAuth
import { Auth, AuthType } from '@qlik/sdk';
import enigma from 'enigma.js';
import schema from 'enigma.js/schemas/12.1477.0.json';
(async () => {
const auth = new Auth({
authType: AuthType.OAuth2,
host: process.env.OAUTH_QCS_SERVER2,
clientId: process.env.OAUTH_CLIENT_ID2,
redirectUri: process.env.OAUTH_REDIRECT_URI,
});
const authorizeBtnEl = document.getElementById('authorize');
const deauthorizeBtnEl = document.getElementById('deauthorize');
const status = document.getElementById('status');
const setStatus = async (loggedIn) => {
if (loggedIn) {
authorizeBtnEl.setAttribute('disabled', true);
deauthorizeBtnEl.removeAttribute('disabled');
const user = await auth.rest('/users/me').then((resp) => resp.json());
status.innerHTML = `Logged in as: ${user.name}`;
} else {
authorizeBtnEl.removeAttribute('disabled');
deauthorizeBtnEl.setAttribute('disabled', true);
status.innerHTML = 'Not authorized';
}
};
if (await auth.isAuthorized()) {
await setStatus(true);
} else {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
if (code) {
await auth.authorize(window.location.href);
const url = new URL(window.location);
url.searchParams.delete('code');
url.searchParams.delete('state');
await setStatus(true);
window.history.replaceState(null, null, url);
} else {
setStatus(false);
}
}
authorizeBtnEl.addEventListener('click', async (e) => {
e.preventDefault();
const { url } = await auth.generateAuthorizationUrl();
window.location = url;
});
authorizeBtnEl.setAttribute('clickable', 'true');
deauthorizeBtnEl.addEventListener('click', async (e) => {
e.preventDefault();
await auth.deauthorize();
setStatus(false);
});
const randomId = Math.random().toString(32).substring(3);
const appId = `SessionApp_${randomId}`;
const enigmaBtnEl = document.getElementById('enigma');
enigmaBtnEl.onclick = async () => {
const evalResult = document.getElementById('evalResult');
evalResult.hidden = true;
evalResult.innerText = '';
let wsUrl = false;
try {
wsUrl = await auth.generateWebsocketUrl(appId);
} catch (error) {
console.log(error);
wsUrl = false;
}
if (!wsUrl) {
return;
}
const global = await enigma
.create({
schema,
url: wsUrl,
})
.open();
const app = await global.getActiveDoc();
const script = 'Load RecNo() as N autogenerate(100);';
await app.setScript(script);
await app.doReload();
const result = await app.evaluate('SUM([N])');
evalResult.innerText = result;
evalResult.hidden = false;
};
const rpcClientBtnEl = document.getElementById('websocket');
rpcClientBtnEl.onclick = async () => {
const evalResult = document.getElementById('evalResult');
evalResult.hidden = true;
evalResult.innerText = '';
const rpcSession = await auth.rpc(appId);
let res;
try {
await rpcSession.open();
const {
result: {
qReturn: {
qHandle: appHandle,
},
},
} = await rpcSession.send({
handle: -1,
method: 'GetActiveDoc',
params: [],
});
await rpcSession.send({
handle: appHandle,
method: 'SetScript',
params: { qScript: 'Load RecNo() as N autogenerate(150);' },
});
await rpcSession.send({
handle: appHandle,
method: 'DoReload',
params: [],
});
res = await rpcSession.send({
handle: appHandle,
method: 'Evaluate',
params: ['SUM([N])'],
});
await rpcSession.close();
console.log('rpcSession closed');
} catch (err) {
console.log('rpcSession error:', err);
} finally {
await rpcSession.close();
}
evalResult.innerText = res.result.qReturn;
evalResult.hidden = false;
};
})();
Web App
import enigma from 'enigma.js';
import schema from 'enigma.js/schemas/12.1477.0.json';
import { Auth, AuthType } from '@qlik/sdk';
(async () => {
const webIntegrationId = process.env.TEST_WEB_INT_ID;
const tenantUri = process.env.QCS_SERVER;
if (!webIntegrationId) {
console.log('Missing webIntegrationId');
return;
}
if (!tenantUri) {
console.log('Missing tenantUri');
return;
}
const config = {
host: tenantUri,
authType: AuthType.WebIntegration,
webIntegrationId,
autoRedirect: false,
};
const auth = new Auth(config);
const status = document.getElementById('status');
const wsUrlElem = document.getElementById('wsUrl');
status.innerText = 'Not logged in';
const authenticateButton = document.getElementById('authenticateButton');
authenticateButton.onclick = () => {
auth.authenticate();
};
const deAuthenticateButton = document.getElementById('deAuthenticateButton');
deAuthenticateButton.onclick = () => {
auth.deauthenticate();
};
const isLoggedIn = await auth.isAuthenticated();
if (isLoggedIn) {
status.innerText = 'Logged in as: ...';
const meRes = await auth.rest('/users/me');
if (!meRes.ok) {
return;
}
const me = await meRes.json();
status.innerText = `Logged in as: ${me.name}`;
authenticateButton.classList.add('hidden');
deAuthenticateButton.classList.remove('hidden');
const randomId = Math.random().toString(32).substring(3);
const appId = `SessionApp_${randomId}`;
const rpcSession = await auth.rpc(appId);
try {
await rpcSession.open();
const {
result: {
qReturn: {
qHandle: appHandle,
},
},
} = await rpcSession.send({
handle: -1,
method: 'GetActiveDoc',
params: [],
});
await rpcSession.send({
handle: appHandle,
method: 'SetScript',
params: { qScript: 'Load RecNo() as N autogenerate(10);' },
});
await rpcSession.send({
handle: appHandle,
method: 'DoReload',
params: [],
});
const res = await rpcSession.send({
handle: appHandle,
method: 'Evaluate',
params: ['SUM([N])'],
});
const evalResult = document.getElementById('evalResult');
evalResult.innerText = res.result.qReturn;
evalResult.hidden = false;
await rpcSession.close();
console.log('rpcSession closed');
} catch (err) {
console.log('rpcSession error:', err);
} finally {
await rpcSession.close();
}
const wsUrl = await auth.generateWebsocketUrl(appId);
wsUrlElem.innerText = wsUrl;
const global = await enigma
.create({
schema,
url: wsUrl,
})
.open();
const app = await global.getActiveDoc();
const script = 'Load RecNo() as N autogenerate(100);';
await app.setScript(script);
await app.doReload();
const result = await app.evaluate('SUM([N])');
const enigmaEvalResult = document.getElementById('enigmaEvalResult');
enigmaEvalResult.innerText = result;
enigmaEvalResult.hidden = false;
}
})();
Webpack App
import enigma from 'enigma.js';
import schema from 'enigma.js/schemas/12.936.0.json';
import { Auth, AuthType } from '@qlik/sdk';
(async () => {
const webIntegrationId = process.env.TEST_WEB_INT_ID;
const tenantUri = process.env.QCS_SERVER;
if (!webIntegrationId) {
console.log('Missing webIntegrationId');
return;
}
if (!tenantUri) {
console.log('Missing tenantUri');
return;
}
const config = {
host: tenantUri,
authType: AuthType.WebIntegration,
webIntegrationId,
autoRedirect: false,
};
const auth = new Auth(config);
const status = document.getElementById('status');
const wsUrlElem = document.getElementById('wsUrl');
status.innerText = 'Not logged in';
const authenticateButton = document.getElementById('authenticateButton');
authenticateButton.onclick = () => {
auth.authenticate();
};
const deAuthenticateButton = document.getElementById('deAuthenticateButton');
deAuthenticateButton.onclick = () => {
auth.deauthenticate();
};
const isLoggedIn = await auth.isAuthenticated();
if (isLoggedIn) {
status.innerText = 'Logged in as: ...';
const meRes = await auth.rest('/users/me');
if (!meRes.ok) {
return;
}
const me = await meRes.json();
status.innerText = `Logged in as: ${me.name}`;
authenticateButton.classList.add('hidden');
deAuthenticateButton.classList.remove('hidden');
const randomId = Math.random().toString(32).substring(3);
const appId = `SessionApp_${randomId}`;
const rpcSession = await auth.rpc(appId);
try {
await rpcSession.open();
const {
result: {
qReturn: {
qHandle: appHandle,
},
},
} = await rpcSession.send({
handle: -1,
method: 'GetActiveDoc',
params: [],
});
await rpcSession.send({
handle: appHandle,
method: 'SetScript',
params: { qScript: 'Load RecNo() as N autogenerate(10);' },
});
await rpcSession.send({
handle: appHandle,
method: 'DoReload',
params: [],
});
const res = await rpcSession.send({
handle: appHandle,
method: 'Evaluate',
params: ['SUM([N])'],
});
const evalResult = document.getElementById('evalResult');
evalResult.innerText = res.result.qReturn;
evalResult.hidden = false;
await rpcSession.close();
console.log('rpcSession closed');
} catch (err) {
console.log('rpcSession error:', err);
} finally {
await rpcSession.close();
}
const wsUrl = await auth.generateWebsocketUrl(appId);
wsUrlElem.innerText = wsUrl;
const global = await enigma
.create({
schema,
url: wsUrl,
})
.open();
const app = await global.getActiveDoc();
const script = 'Load RecNo() as N autogenerate(100);';
await app.setScript(script);
await app.doReload();
const result = await app.evaluate('SUM([N])');
const enigmaEvalResult = document.getElementById('enigmaEvalResult');
enigmaEvalResult.innerText = result;
enigmaEvalResult.hidden = false;
}
})();