Plugin Client Command Reference
Introduction
The Plugin Client is used to initialize a Plugin, register handlers for
messages the Plugin will receive, and communicate with the page hosting the
Plugin (the "Plugin Host"). An instance of the Plugin Client is created using
the initialize function documented below.
initialize
Asynchronously sets up the Plugin Client instance to be used for the rest of
the Plugin's execution. You only need to call initialize once to
take care of all of the message handler registration.
PluginClient.initialize(messageHandlers)
A messageHandlers object must be provided to receive or respond to messages
from the Host page. messageHandlers maps the names of handlers to handler
functions, and the handler names themselves are defined by the Plugin Host.
Each handler function will receive two parameters:
data: object- An object that contains the data sent from the Plugin Host -- If no data was sent,datawill benullpluginClient: PluginClient- The instance of the currentPluginClient-- This allows the individual message handlers to access the various methods provided by thePluginClient, such asgetContext
To respond to the Plugin Host's message, simply return the data to pass back:
const eventHandlers = {
helloWorld: function(data, pluginClient) {
console.log('Data from the Host page:', data)
console.log('The current PluginClient instance:', pluginClient)
return 'hello host!' // Send back the string 'hello world!' to the Host
},
}
Parameters
messageHandlers: object<string, function>- An object containing all message handler functions
Return Value
A Promise that resolves to an instance of the PluginClient. Any calls to
initialize after the first will result in undefined behavior -- we recommend
only initializing once.
Example
The Plugin in this example handles creating and updating a bar chart visualization.
// an example made-up class for visualizing data
dataVisualizer = new DataVisualizer()
// a handler can be declared inline or outside of the messageHandlers object
const handleSetup = function(data, pluginClient) {
const width = data.width
const height = data.height
dataVisualizer.setDimensions(width, height) // sets the x,y dimensions of this data visualizer
}
// the dataVisualizer is passed into handleBarChartCreate through this binding later
const handleBarChartCreate = function(data, pluginClient) {
const chartData = data.chartPoints
const chartName = data.chartName
dataVisualizer.createBarChart(chartName, chartData)
}
const handleChartReset = function(data, pluginClient) {
dataVisualizer = new DataVisualizer()
const context = pluginClient.getContext()
const width = context.width
const height = context.height
dataVisualizer.setDimensions(width, height)
}
const eventHandlers = {
setup: handleSetup,
handleBarChartCreate: handleBarChartCreate,
handleChartReset: handleChartReset,
}
pluginClient.initialize(eventHandlers).then((pluginClient) => {
/* code that uses the client */
})
postMessage
Sends a message to the Plugin Host page. Each Plugin Type will send and receive various messages unique to their type. That allow Plugins to interact with the XM Platform in novel ways. For information on messages available for a given Plugin Type check out the Plugin Guides.
pluginClient.postMessage(name, data, timeout)
Parameters
name: string- The name of the message to send to the Plugin Host page. The Host page must have registered a handler for this message name to receive it, similar to how the Plugin registers handlers to receive messages from the Host pagedata: serializable object(optional) - A serializable object to be sent along with the message. Defaults toundefinedtimeout: number(optional) - Specifies the number of milliseconds after which to reject the returned Promise if no response has been received from the Plugin Host. Defaults to10000milliseconds (10 seconds)
Return Value
Returns a Promise that will resolve with the Plugin Host's response, or reject if the Plugin Host throws an error. The Promise will also reject if the Host fails to respond before the timeout.
Example
In this example, the plugin informs the host of a user activity within the plugin:
pluginClient.postMessage('elementClicked', {elementId: 'saveButton'})
In this example, the plugin is requesting chartData from the host and will
wait for up to 2 seconds:
let chartData
pluginClient
.postMessage('getChartData', {chart: 'bar'}, 2000)
.then((response) => {
chartData = response.chartData
})
.catch((error) => {
// log error, etc.
})
fetch2
Sends a request to the provided URL. Securely injects user defined account for
authentication if specified in connection property. Works similarly to the
vanilla JavaScript fetch function.
pluginClient.fetch2(url, fetchConfig)
HTTP Request Size: Please note that the combined size of the URL and payload in any inbound or outbound HTTP call is limited to 8 MB. You can chunk payloads over 8 MB into multiple calls to get around the limit.
Parameters
url: string- The URL of the third party service to make a request to. Ex: https://graph.facebook.com/fetchConfig: object- An object that configures the fetch request:method: string- The HTTPS method to use. Ex:"POST","GET","PUT", etc.headers: object<string, string>(optional) - Key/value pairs representing the request header(s). Ex:"Content-Type": "application/json"body: string | object(optional) - Thebodyof the requestparams: object<string, string>(optional) - Key/value pairs representing the query parameter(s) to add to the request URL. Ex:"filter": "random"Would add?filter=randomto the URL.connection: object(optional) Configures how a connection should be injected into the request:connectionName: string- The name of the connection to use (usecontext.connectionsto get a list of available connection names and schemes)paramFormat: string- Where to inject the credential. Can be"header","body", or"query"paramName: string- What to name the credential parameter. As in, the field name for a body param, the query name for a query param, or the header name for a header. Ex: for a bearer token, this might be"Authorization"paramTemplate: string- What to insert the credential as. Use it similarly to a string passed toString.format. Ex:"Bearer %s"would be a template for bearer tokens,"%s"inserts the token as-is
timeout: number(optional) Specifies the number of milliseconds after which to reject the returned Promise if no response has been received. Defaults to10000milliseconds (10 seconds)
Return Value
Returns a Promise that resolves to the HTTPS response from the third party
service or rejects on an error (such as the fetchConfig being improperly
formatted), timeout, or non-2xx response code.
On a successful response from the third party service, the Promise resolves to an object with two properties:
status: number- The HTTP status code of the response from the the third party service. Will be a2xxcode, in this case.responseData: unknown- The response data from the third party service. On a request timeout, internal service error, or any non-200responses from the third party service, throws aFetchErrorwith two properties:status: number- The HTTP status code of the response from the the third party service. For programmatic detection of what went wrong.responseData: unknown- Data that was returned in the response body; will likely contain additional details as to why the Error happened.
Interpolation
Account information may be interpolated into the request at various points, using placeholders surrounded by double-braces, e.g. {{foo}}.
Inside the URL
In the url parameter, placeholders referencing configuration attributes like {{foo}} are replaced with the user-provided value for that account configuration attribute. Assuming an attribute called foo that contains the value bar:
client.fetch2('https://{{foo}}.example.com/', {
method: 'GET',
connection: {
connectionName: 'example connection',
paramFormat: 'header',
paramName: 'Authorization',
paramTemplate: 'Bearer %s'
},
})
// calls https://bar.example.com/
An alternative to this method is to interpolate using user-provided values inside the account. An example of such a placeholder is
{{/credential/brandId}}: The brand ID value of the account, which we will set to begoogle.
client.fetch2('https://{{/credential/brandId}}.com/', {
method: 'GET',
connection: {
connectionName: 'example connection',
paramFormat: 'header',
paramName: 'Authorization',
paramTemplate: 'Bearer %s'
},
})
// calls https://google.com/
Note that both interpolation methods can be used simultaneously in a URL.
client.fetch2('https://{{foo}}.{{/credential/brandId}}.com/', {
method: 'GET',
connection: {
connectionName: 'example connection',
paramFormat: 'header',
paramName: 'Authorization',
paramTemplate: 'Bearer %s'
},
})
// calls https://bar.google.com/
Additionally, attempting to interpolate using a credential secret path like {{/credential/secret}} is disallowed for security reasons.
Inside the fetch config
In strings inside the fetchConfig parameter, placeholders are replaced with the user-provided values inside the account. Valid placeholders include:
{{/credential/secret}}: The secret value of the account (e.g. for an API Key account, the API key){{/credential/metadata/foo}}: The value of the account metadata fieldfoo
client.fetch2('https://example.com/', {
method: 'GET',
headers: {
auth: '{{/credential/secret}}',
},
params: {
q: '{{/credential/metadata/foo}}'
},
connection: {
connectionName: 'example connection',
paramFormat: 'header',
paramName: 'Authorization',
paramTemplate: 'Bearer %s'
},
})
// calls https://example.com/?q=bar with the header 'auth' set to the credential's secret
Placeholders may be used inside the headers, params, and body keys of the fetch config.
Limitations
The payload size returning from a fetch2 request is limited to 8 MB.
Example
Making a request to an external API at example.com:
// simple GET request
pluginClient
.fetch2('https://example.com/my/api/route', {method: 'GET'})
.then(function(response) {
// do stuff with response
console.log('status:', response.status)
console.log('data:', response.responseData)
})
.catch(function(error) {
// handle error
console.log('something went wrong!')
console.log('status:', error.status)
console.log('data:', error.responseData)
})
// get the first available connection name
const connectionName = pluginClient.getContext().connections[0].name
// fully configured POST request
pluginClient.fetch2('https://example.com/my/other/api/route', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: {
myParam: 'myValue',
},
query: {
filter: 'random',
},
connection: {
connectionName: connectionName,
paramFormat: 'header',
paramName: 'Authorization',
paramTemplate: 'Bearer %s'
},
})
// ignore any response by not having a .then()
fetch3
Makes a request to the provided URL. Supports all content types including FormData for file uploads and binary data. Works similarly to the vanilla JavaScript fetch function and extends the features of our fetch2 API.
pluginClient.fetch3(url, fetchConfig, timeout)
Key differences from fetch2:
- Supports all content types including FormData and binary data (images, PDFs, etc.)
- Auto-decodes response data based on content-type (JSON->object, text->string, binary->ArrayBuffer)
- Query parameters should be included directly in the URL, not in a separate
paramsfield - Automatic retry logic: If the destination server returns a 401/403 due to an expired credential, the request will automatically be retried with the refreshed credential
Parameters
url: string- The URL of the third party service to make a request to. Include query parameters directly in the URL. Ex:https://api.example.com/data?filter=activefetchConfig: object- An object that configures the fetch request:method: string- The HTTPS method to use. Ex:"POST","GET","PUT", etc.headers: object<string, string>(optional) - Key/value pairs representing the request header(s). Ex:"Content-Type": "application/json". Note: For FormData requests, do not setContent-Typemanually as the browser will set it automatically with the correct boundarybody: string | object | array | FormData(optional) - Thebodyof the request. Can be a string, object, array, or FormData (for file uploads)connection: object(optional) Configures how a connection should be injected into the request:connectionName: string- The name of the connection to use (usecontext.availableConnectionsto get a list of available connection names)paramFormat: string- Where to inject the credential. Can be"header","body", or"query"paramName: string- What to name the credential parameter. As in, the field name for a body param, the query name for a query param, or the header name for a header. Ex: for a bearer token, this might be"Authorization"paramTemplate: string- What to insert the credential as. Use it similarly to a string passed toString.format. Ex:"Bearer %s"would be a template for bearer tokens,"%s"inserts the token as-is
checkBodyInjection: boolean(optional) - Whether to enable credential template injection in the request body. When enabled, you can use placeholders like{{/credential/field}}in your request body to inject credential metadata. Defaults tofalse
timeout: number(optional) Specifies the number of milliseconds after which to reject the returned Promise if no response has been received. Defaults to10000milliseconds (10 seconds)
Return Value
Returns a Promise that resolves to the response from the third party service or rejects on an error (such as the fetchConfig being improperly formatted), timeout, or non-2xx response code.
On a successful response from the third party service, the Promise resolves to an object with properties:
data: unknown- The response data from the third party service, auto-decoded based on theContent-Typeheader:application/json-> parsed JavaScript object;text/*-> string; binary types (images, PDFs, etc.) -> ArrayBufferstatus: number- The HTTP status code of the response from the third party service. Will be a2xxcode, in this casestatusText: string- The HTTP status text of the response. Ex:"OK","Created", etc.headers: object<string, string>- The response headers from the third party service
On a request timeout, internal service error, or any non-2xx responses from the third party service, throws a FetchError with the same properties as above.
Requirements
- Plugin submitted with
@qualtrics/plugin-clientversion 1.10.4 or greater.
Example
Making a simple GET request:
pluginClient
.fetch3('https://api.example.com/users?status=active', {
method: 'GET',
})
.then(function(response) {
console.log('status:', response.status) // 200
console.log('statusText:', response.statusText) // "OK"
console.log('data:', response.data) // auto-decoded JSON object
console.log('content-type:', response.headers['content-type'])
})
.catch(function(error) {
console.error('Request failed:', error.message)
console.log('status:', error.status)
console.log('data:', error.data)
})
Making a POST request with authentication:
// get the first available connection name
const connectionName = pluginClient.getContext().availableConnections[0]
pluginClient
.fetch3('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: {
name: 'John Doe',
email: 'john@example.com',
},
connection: {
connectionName: connectionName,
paramFormat: 'header',
paramTemplate: 'Bearer %s',
paramName: 'Authorization',
},
})
.then(function(response) {
console.log('Created resource:', response.data)
})
.catch(function(error) {
console.error('Failed to create resource:', error)
})
Uploading a file using FormData:
// Create FormData with a file
const formData = new FormData()
formData.append('name', 'My Document')
formData.append('file', fileInput.files[0]) // File from an <input type="file">
formData.append('category', 'reports')
pluginClient
.fetch3('https://api.example.com/upload', {
method: 'POST',
body: formData,
// Do NOT set Content-Type header - browser will set it automatically with boundary
})
.then(function(response) {
console.log('File uploaded successfully:', response.data)
})
.catch(function(error) {
console.error('Upload failed:', error)
})
Downloading a binary file (PDF, image, etc.):
pluginClient
.fetch3('https://api.example.com/documents/123/download', {
method: 'GET',
})
.then(function(response) {
// response.data will be an ArrayBuffer for binary content
const blob = new Blob([response.data], {
type: response.headers['content-type'],
})
const url = URL.createObjectURL(blob)
// Create download link
const a = document.createElement('a')
a.href = url
a.download = 'document.pdf'
a.click()
URL.revokeObjectURL(url)
})
.catch(function(error) {
console.error('Download failed:', error)
})
Using credential template injection in the request body:
const connectionName = pluginClient.getContext().availableConnections[0]
pluginClient
.fetch3('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: {
apiKey: '{{/credential/apiKey}}', // Will be replaced with actual credential value
data: 'some data',
},
connection: {
connectionName: connectionName,
paramFormat: 'body',
paramTemplate: '%s',
paramName: 'apiKey',
},
checkBodyInjection: true, // Enable credential template injection
})
.then(function(response) {
console.log('Request succeeded:', response.data)
})
qualtricsApiFetch2
Used for making requests to the Qualtrics Public
API. Only a select few API endpoints are permitted
for Plugin requests (listed below). This function was added as a substitute for
qualtricsApiFetch. qualtricsApiFetch2 and has clearer Error messaging and a
more detailed response object than the original qualtricsApiFetch.
pluginClient.qualtricsApiFetch2(url, config, timeout)
The entire Qualtrics Public API is available to your plugin if your XM Extension defines qualtricsScopes
in the manifest.json. See Manifest.json Properties Reference for more information on how to add Qualtrics Scopes to your XM Extension. Brand Admins will be required to consent to the provided scopes before your XM Extension is available to use in customers' brands.
Parameters
url: string- The relative URL path after/API/v3/. Ex:"whoami"will make a request to/API/v3/whoamiconfig: object- An object that configures the request:method: string- The HTTPS method to use. Ex:"POST","GET","PUT", etc.body: object | string(optional) - Thebodyof the request
timeout: number(optional) - Specifies the number of milliseconds to wait before rejecting the returned Promise if no response has been received. Defaults to10000milliseconds (10 seconds).
Return Value
Returns a Promise that resolves to the HTTPS response or rejects on an error
(such as the config being improperly formatted), timeout, or non-2xx
response code.
On a successful response from the Qualtrics API, the Promise resolves to an object with two properties:
status: number- The HTTP status code of the response. Will be2xxin this case.responseData: unknown- The response data from the Qualtrics API. See the Qualtrics API docs for what kind of values to expect to be returned by a successful call.
On a request timeout or any non-200 responses from the Qualtrics API, throws a FetchError with two properties:
status: number- The HTTP status code of the response for programmatic detection of what went wrong.responseData: unknown- Data that was returned in the response body; will likely contain additional details as to why the Error happened.
Example
// makes a request to GET /API/v3/surveys
pluginClient
.qualtricsApiFetch2('surveys', {method: 'GET'})
.then((res) => {
console.log('Got a successful request with stats code: ', res.status)
console.log('Surveys available to the current user: ', res.responseData)
})
.catch((err) => {
console.error('Something went wrong', err.responseData)
console.log('Here is the status code of the error', err.status)
})
getText
Retrieves localized strings from the Plugin's translation files, specifically
from the translation that matches the User's language. Valid keys are defined
by top-level properties of the strings object in the translation file.
pluginClient.getText(key, placeHolders, defaultValue)
Parameters
key: string- The key for the text template to useplaceHolders: object<string, string>(optional) - An object with values for any named placeholders in the template textdefaultValue: string(optional) - A default value to return if the key or translation doesn't exist
Return Value
A string with the requested placeHolder values populated.
Example
Inside the EN.json translation file for this Plugin:
{
"meta": {
"summary": "Facebook App Language Strings - EN"
},
"strings": {
"name": {
"text": "My Cool Facebook App",
"context": "The App Name"
},
"description": {
"text": "Does something cool with Facebook",
"context": "Displayed when listing the app"
},
"user.notifications": {
"text": "You have {{number}} notifications",
"context": "Displays when a user is checking how many notifications they have"
},
"comments.notification": {
"text": "{{user}} got a comment from {{name}}",
"context": "Displays when a user is checking a comment notification"
},
"duplicate": {
"text": "{{number}} is {{number}}",
"context": "Displays the same number twice at the bottom of the app UI"
}
}
}
Plugin code:
pluginClient.getText('name')
// My Cool Facebook App
pluginClient.getText('user.notifications', {number: 2})
// You have 2 notifications
pluginClient.getText('comments.notification', {user: 'Bob', name: 'Alice'})
// Bob got a comment from Alice
pluginClient.getText('duplicate', {number: 2})
// 2 is 2
// The following two will use the default, since comments.reaction doesn't exist in EN.json
pluginClient.getText('comments.reaction', {}, 'Thumbs Up')
// Thumbs Up
pluginClient.getText('comments.reaction', {number: 2}, '{{number}} Thumbs Up')
// 2 Thumbs Up
getContext
Used to retrieve the context for the running plugin, provided by the Plugin Host page.
pluginClient.getContext()
Return Value
An object containing the unique context data provided by the host page, as well as additional properties such as the user's language code, connections and the translation objects.
Example
In this example, the host page provides a surveyId for the survey associated with this Plugin.
const context = client.getContext()
const surveyId = context.surveyId
getLanguage
Returns the language code of the logged in user. Language codes follow the Qualtrics standard language codes.
pluginClient.getLanguage()
Return Value
A string representing the language code of the logged in user. Can be used to locate the correct translation object in the context.
Example
const languageCode = pluginClient.getLanguage()
console.log("User's chosen language: ", languageCode)
onCredentialStatusChange
Passes a callback function to retrieve the credential status of a plugin. This is only relevant to plugin types that support rendering without a credential. Otherwise, the plugin can assume it has a valid credential available in its context.
pluginClient.onCredentialStatusChange(callback: (data: PluginCredentialData) => void): void
export type PluginCredentialData = {
isCredentialSelected: boolean
credentialStatus: CredentialStatus
connections?: {
name: string
scheme: string
}[]
}
export enum CredentialStatus {
INITIALIZED = 'initialized',
NEW = 'new',
DELETED = 'deleted',
UPDATED = 'updated',
}
Example
In this example, the host triggers the callback when the credential is updated outside of the plugin UI. The plugin uses React to update the UI when the credential status changes.
const [
credentialStatusData,
setCredentialStatusData
] = React.useState<PluginCredentialData>();
React.useEffect(() => {
// initialize the callback on mount
props.client.onCredentialStatusChange(setCredentialStatusData);
}, []);
React.useEffect(() => {
// do something with the new credential status data
}, [ credentialStatusData ]);