TLDR; With Tether's Custom Data Passthrough, you can pass important information about your users and pages to Tether to provide more context for feedback.
You and your team know your application best. You know what information about your users and pages is important to you. With Custom Data Passthrough, you can pass that information along to Tether, ensuring submitted feedback has as much useful and
actionable context as possible.
For example, let's say your user model has an is_subscribed
attribute. One of your subscriber benefits is feedback priority, so you want to pass that information along with any feedback so that it can be used to prioritize items in the
Tether dashboard.
All Custom Data fields can be viewed in the Tether dashboard on the "Custom Data" tab of the feedback item's technical details.
We're also working on some exciting new features that rely on Data Passthrough functionality to do some pretty cool stuff - more on that soon!
Tether offers two different ways for you to define Data Passthrough variables, either by adding new attributes to your Tether installation script tag, or by defining new object properties on the global tetherSettings
object. The
script-tag
method is the easiest way to get started, but the tetherSettings
object is more flexible and allows you to define variables dynamically and update them on-the-fly until the moment feedback is submitted.
Define your Custom Data attribute on the Tether install script by simply prefixing your attributes with data-custom-
. The entire attribute name should be lowercase, and use kebab-case for multi-word attributes. These kebab-case variable names will be
converted to camel-case on the backend. For example, if you wanted to pass through a variable named isSubscribed
, you would add the attribute data-custom-is-subscribed
to your Tether script tag.
{alert.fa-exclamation-circle} The
data-custom-
prefix is required for all Data Passthrough variables defined in the script tag. If you forget to add the prefix, the variable will not be passed through to Tether.
{info.fa-info-circle} This script tag method is best suited for static variables that are known at page load, and will not change thereafter. If you need to pass through dynamic variables or update variables on-the-fly, you should use the
tetherSettings
method documented below.
<script
defer
type="text/javascript"
src="https://app.tethered.dev/js/tether.js"
data-id="tether-client"
data-token="****************************************"
data-project-uuid="********-****-****-****-************"
data-custom-is-subscribed="true"
></script>
Of course, in its current state, this example is not very useful. We can do better.
Rather than a hard-coded true/false value we can use interpolation, if supported by your framework or language, to pass through a more dynamic value. Let's also
track what subscription plan the user is on for additional context when reviewing feedback.
<html>
<head>
...
<script
defer
type="text/javascript"
src="https://app.tethered.dev/js/tether.js"
data-id="tether-client"
data-token="****************************************"
data-project-uuid="********-****-****-****-************"
data-custom-is-subscribed="{{ Auth::user()->is_subscribed }}"
data-custom-subscription-plan="{{ Auth::user()->subscription->plan_id }}"
data-custom-is-admin="{{ Auth::user()->is_admin }}"
></script>
</head>
...
</html>
import React from 'react';
// The authenticated user object could be passed as a prop, retrieved from global state, extracted from session, etc.
const DataPassthroughExample = ({ user }) => {
return (
<>
<script
defer
type="text/javascript"
src="https://app.tethered.dev/js/tether.js"
data-id="tether-client"
data-token="****************************************"
data-project-uuid="********-****-****-****-************"
data-custom-is-subscribed="{ user.isSubscribed }"
data-custom-subscription-plan="{ user.subscription.planId }"
data-custom-is-admin="{ user.isAdmin }"
></script>
...
</>
);
};
export default DataPassthroughExample;
<template>
<script
defer
type="text/javascript"
src="https://app.tethered.dev/js/tether.js"
data-id="tether-client"
data-token="****************************************"
data-project-uuid="********-****-****-****-************"
data-custom-is-subscribed="{{ user.isSubscribed }}"
data-custom-subscription-plan="{{ user.subscription.planId }}"
data-custom-is-admin="{{ user.isAdmin }}"
></script>
...
</template>
<script>
export default {
props: {
user: {
type: Object,
required: true,
},
},
}
</script>
tetherSettings
ObjectThe second option for setting Custom Data is to define properties on the global tetherSettings.customData
object. This option is more flexible and allows you to define variables dynamically using Javascript
at any time up until the moment feedback is
submitted. This is also the preferred method for defining Custom Data if you are using a stack that does not support HTML interpolation or server-side rendering.
Since this method has all the power of Javascript behind it, you can do some pretty cool stuff. For example, you could define a property on the tetherSettings.customData
based on the result of an API request, when the user clicks a
button, submits a form, or scrolls the page. The possibilities are only limited by your imagination.
{info.fa-info-circle} For some browsers or browser versions, you may need to access Tether settings via the window object
window.tetherSettings.customData
{info.fa-info-circle} Note that
customData
is persisted for the duration of the user's session. Upon reloading the page,customData
properties are restored. In order to clear or remove acustomData
property, it must be explicitly reassigned, or deleted:delete tetherSettings.customData.myProperty
<html>
<head>
...
<script
defer
type="text/javascript"
src="https://app.tethered.dev/js/tether.js"
data-id="tether-client"
data-token="****************************************"
data-project-uuid="********-****-****-****-************"
></script>
<script>
window.tetherSettings.customData = {
isSubscribed: true,
subscriptionPlan: 'Gold',
isAdmin: false,
};
window.addEventListener('tether.feedback.submitted', () => {
// Increment feedbackCount every time the user submits feedback
if (!window.tetherSettings.customData['feedbackCount']) {
window.tetherSettings.customData['feedbackCount'] = 1;
}
else {
window.tetherSettings.customData.feedbackCount++;
}
});
</script>
</head>
...
</html>
import React, { useEffect, useState } from 'react';
const DataPassthroughExample = () => {
const [isSubscribed, setIsSubscribed] = useState(false);
useEffect(() => {
// Make an API request to get the user's subscription status
fetch('/api/user/subscription')
.then(response => response.json())
.then(data => {
// Update the `window.tetherSettings` object with the result
window.tetherSettings.customData['isSubscribed'] = data.subscription.active;
});
}, []);
return (
<div>
<div>
<button onClick={ () => setIsSubscribed(true) }>Subscribe</button>
<button onClick={ () => setIsSubscribed(false) }>Unsubscribe</button>
</div>
...
</div>
);
};
export default DataPassthroughExample;
<template>
<div>
<div>
<button @click="isSubscribed = true">Subscribe</button>
<button @click="isSubscribed = false">Unsubscribe</button>
</div>
...
</div>
</template>
<script>
export default {
data() {
return {
isSubscribed: false,
};
},
mounted() {
// Make an API request to get the user's subscription status
fetch('/api/user/subscription')
.then(response => response.json())
.then(data => {
// Update the `tetherSettings.customData` object with the result
window.tetherSettings.customData['isSubscribed'] = data.subscription.active;
});
},
}
</script>
{info.fa-info-circle} When both Custom Data methods are used, their values will be merged - with precedence given to the properties defined in
tetherSettings.customData
when there are conflicts.