Third-Party Tool Integration Custom Plugin Tutorial

Learn how to build your first third-party tool integration custom plugin to get familiar with integrating third-party tools or platforms into Staffbase.

Employee App
Staffbase Intranet
Basic

This tutorial guides you through building your first custom plugin in order to integrate a third-party tool in the Staffbase platform. By the end, you’ll be familiar with the key concepts and steps involved in connecting external platforms or services through custom plugin development.

Custom plugins are mini web applications that integrate seamlessly with the Staffbase platform to provide tailored functionality. Administrators can add and publish them in the same way they manage standard Staffbase plugins. Learn more about plugins.

This is an advanced tutorial. If you’re new to custom plugin development, start with the basic Hello User tutorial.

In this tutorial, you will:

  • Learn to develop a custom plugin to integrate a third-party tool using PHP.
  • Understand the key considerations for integrating tools that require authentication.
  • Gain the foundation to prepare for additional third-party tool integration scenarios.

This hands-on tutorial uses Greenhouse, an end-to-end hiring platform, as an example. You’ll learn how to display internal job postings from Greenhouse using a custom plugin. While integration specifics may vary by tool, this example gives you a solid foundation in handling authentication and using relevant libraries.

The image above conceptualizes how the Internal Job Board plugin could look after it’s available to users on the Staffbase platform.

This tutorial walks you through developing the plugin locally. To use it on the Staffbase platform, you will need to complete the following, which are not covered in the tutorial:

  • You have a basic understanding of Staffbase plugins and are familiar with terminal/command line usage.
  • PHP is installed on your system.
  • You have a code editor. For example, Visual Studio Code.
  • You have a Greenhouse instance you want to connect to your custom plugin.
  • You have a Greenhouse API token to connect your instance.
  • You have an understanding of how job boards are configured in your Greenhouse instance.

Download composer

To start building the plugin, you need to use the plugin SDK for PHP. This SDK provides the basic functionality to parse and verify the PHP token. Staffbase provides the SDK via a composer.

Run the following commands in your terminal:

  1. Create a folder for your project.
mkdir MyCustomPlugin
cd MyCustomPlugin
  1. Download the composer installer to the current directory.
    You should now see a file named composer.phar in your folder.

Install dependencies

Now you can use the composer to install the specific dependencies required for the plugin SDK.

  1. Install the custom plugin dependencies using composer.
./composer.phar require staffbase/plugins-sdk-php
  1. List the files installed.
ls -l

You should see:

  • composer.json
  • composer.lock
  • vendor

These are your dependency and package management files.

Open the folder in the code editor

  • Launch your project folder MyCustomPlugin, in your preferred code editor. It looks as shown in the screenshot:

Develop the plugin

Now that you have all the dependencies, you can create PHP files and a structure for plugin development.

Inside the vendor folder, you have the plugin SDK and all other dependencies it requires.

  1. Create an index.php file inside the project folder. In the index.php file, provide a namespace for the plugin.
<?php
namespace MyCustomPlugin;
  1. Import the library from the SDK to verify the SSO token. The SSO token here is a JSON Web Token that verifies whether the information is valid.
use Staffbase\plugins\sdk\SSOToken;
  1. Authenticate the user with an app secret.
try {
$appSecret = '{enter your secret here}';

The $appSecret is a public key that the plugin uses to verify the signature of any JWT received. For now, you can leave it blank. You receive the key from Staffbase when you register your plugin with Staffbase and need to provide it if you want to make the plugin available on the Studio Plugins page.

  1. Use the SSOToken class to validate the incoming JWT token. This typically happens via a GET request when a user accesses your plugin:
$sso = new SSOToken($appSecret, $_GET['jwt'] ?? '');

Here’s what a sample request looks like:

https://my.plugin.com/index.php?jwt=MII3ksglkgfglfgnfdl

This step ensures that the user is authenticated and the token data is valid before rendering any content or executing plugin logic.

  1. Use the exception library to add an exception rule for when the user accessing the plugin does not have the required permissions.
use Exception;
} catch (\Exception $e) {
print "Access denied.";
exit;
}

You need to connect to the third-party application. In this example, you need to connect the job board from Greenhouse. This allows the plugin to receive the information that you want to display.

Add the following lines inside the try block to fetch data from Greenhouse:

try {
$appSecret = '{enter your secret here}';
$sso = new SSOToken($appSecret, $_GET[`jwt`]?? "");
// https://my.plugin.com/index.php?jwt={enter-the-jwt-token}
$greenhouse_api_token = getenv('GREENHOUSE_API_TOKEN');
$opts = array(
'http' => array(
'method' => 'GET',
'header' => "Authorization: Basic " . $greenhouse_api_token
));
$context = stream_context_create($opts);
// Fetch internal job posts
$jobPosts = json_decode(file_get_contents('https://harvest.greenhouse.io/v1/job_posts?active=true&live=true&per_page=500', false, $context));
$internalJobs = array_filter($jobPosts, fn($job) => $job->internal);
// Fetch job profiles and departments
$jobProfiles = json_decode(file_get_contents('https://harvest.greenhouse.io/v1/jobs/?status=open&per_page=500', false, $context));
$departments = json_decode(file_get_contents('https://harvest.greenhouse.io/v1/departments/?per_page=500', false, $context));
function getUpperDepartment($allDepartmentsArray, $currentDepartment) {
if ($currentDepartment->parent_id == "") return $currentDepartment;
$parent = array_filter($allDepartmentsArray, fn($d) => $d->id == $currentDepartment->parent_id);
return getUpperDepartment($allDepartmentsArray, array_shift($parent));
}
}

The function in the code snippet above determines the top-level department for a job. This is an example of how the job portal is configured. Your organization’s department structure in Greenhouse may differ, so adapt the logic as needed.

Add this HTML structure to add a search function and use gridjs to display the plugin content in a table format:

$html = <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Staffbase Internal Job Board</title>
<link href="styles.css" rel="stylesheet" />
</head>
<body>
<div id="wrapper"></div>
<script src="js/gridjs.umd.js"></script>
<script>
new gridjs.Grid({
columns: ['Job Title', {
name: 'URL',
hidden: true
}],
data: [
HTML;
foreach ($internalJobs as $job) {
$jobProfile = array_filter($allJobProfiles, fn($profile) => $job->job_id == $profile->id);
$jobProfile = array_shift($jobProfile);
$department = getUpperDepartment($allDepartments, $jobProfile->departments[0]);
$locationCitiesArray = [];
foreach ($jobProfile->offices as $office) {
if (!empty($office->location->name) && is_string($office->location->name)) {
array_push($locationCitiesArray, explode(",", $office->location->name)[0]);
}
}
sort($locationCitiesArray);
$locationCities = implode(" • ", $locationCitiesArray);
$html .= <<<HTML
{
jobTitle: gridjs.html('<a>{$job->title}</a><br><small>{$locationCities}</small><span class="label">{$department->name}</span>'),
url: 'https://staffbase.greenhouse.io/internal_job_board/applications/{$job->id}'
},
HTML;
}
$html .= <<<HTML
],
search: true,
language: {
'search': {
'placeholder': '🔍 Search jobs, locations, keywords, etc.'
}
}
}).render(document.getElementById("wrapper"))
.on('rowClick', (...args) => openSDK(args[1]._cells[1].data));
</script>
<script src="js/plugins-client-sdk.umd.js" crossorigin></script>
<script>
async function openSDK(url){
var isNativeApp = window['plugins-client-sdk'].isNativeApp;
var openLinkExternal = window['plugins-client-sdk'].openLinkExternal;
if (await isNativeApp()){
openLinkExternal(url)
.then(function (opened) {
console.log(opened);
});
} else {
window.open(url, '_blank');
}
}
</script>
<small class="updated">Last updated: {$timestamp}</small>
</body>
</html>
HTML;

You’ve successfully developed a third-party integration custom plugin. This example plugin displays the job portal from your Greenhouse instance.

The entire code snippet in the index.php file should look like this:

<?php
namespace MyCustomPlugin;
use Staffbase\plugins\sdk\SSOToken;
try {
$appSecret = '{enter your secret here}';
$sso = new SSOToken($appSecret, $_GET[`jwt`]?? "");
// https://my.plugin.com/index.php?jwt={enter-the-jwt-token}
$greenhouse_api_token = getenv('GREENHOUSE_API_TOKEN');
$opts = array(
'http' => array(
'method' => 'GET',
'header' => "Authorization: Basic " . $greenhouse_api_token
));
$context = stream_context_create($opts);
// Fetch internal job posts
$jobPosts = json_decode(file_get_contents('https://harvest.greenhouse.io/v1/job_posts?active=true&live=true&per_page=500', false, $context));
$internalJobs = array_filter($jobPosts, fn($job) => $job->internal);
// Fetch job profiles and departments
$jobProfiles = json_decode(file_get_contents('https://harvest.greenhouse.io/v1/jobs/?status=open&per_page=500', false, $context));
$departments = json_decode(file_get_contents('https://harvest.greenhouse.io/v1/departments/?per_page=500', false, $context));
function getUpperDepartment($allDepartmentsArray, $currentDepartment) {
if ($currentDepartment->parent_id == "") return $currentDepartment;
$parent = array_filter($allDepartmentsArray, fn($d) => $d->id == $currentDepartment->parent_id);
return getUpperDepartment($allDepartmentsArray, array_shift($parent));
}
}
$html = <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Staffbase Internal Job Board</title>
<link href="styles.css" rel="stylesheet" />
</head>
<body>
<div id="wrapper"></div>
<script src="js/gridjs.umd.js"></script>
<script>
new gridjs.Grid({
columns: ['Job Title', {
name: 'URL',
hidden: true
}],
data: [
HTML;
foreach ($internalJobs as $job) {
$jobProfile = array_filter($allJobProfiles, fn($profile) => $job->job_id == $profile->id);
$jobProfile = array_shift($jobProfile);
$department = getUpperDepartment($allDepartments, $jobProfile->departments[0]);
$locationCitiesArray = [];
foreach ($jobProfile->offices as $office) {
if (!empty($office->location->name) && is_string($office->location->name)) {
array_push($locationCitiesArray, explode(",", $office->location->name)[0]);
}
}
sort($locationCitiesArray);
$locationCities = implode(" • ", $locationCitiesArray);
$html .= <<<HTML
{
jobTitle: gridjs.html('<a>{$job->title}</a><br><small>{$locationCities}</small><span class="label">{$department->name}</span>'),
url: 'https://staffbase.greenhouse.io/internal_job_board/applications/{$job->id}'
},
HTML;
}
$html .= <<<HTML
],
search: true,
language: {
'search': {
'placeholder': '🔍 Search jobs, locations, keywords, etc.'
}
}
}).render(document.getElementById("wrapper"))
.on('rowClick', (...args) => openSDK(args[1]._cells[1].data));
</script>
<script src="js/plugins-client-sdk.umd.js" crossorigin></script>
<script>
async function openSDK(url){
var isNativeApp = window['plugins-client-sdk'].isNativeApp;
var openLinkExternal = window['plugins-client-sdk'].openLinkExternal;
if (await isNativeApp()){
openLinkExternal(url)
.then(function (opened) {
console.log(opened);
});
} else {
window.open(url, '_blank');
}
}
</script>
<small class="updated">Last updated: {$timestamp}</small>
</body>
</html>
HTML;
  • Multi-source job boards: Combine listings from multiple hiring platforms into a unified internal board. For example, the plugin can display listings from both Greenhouse and Lever.
  • Role-based filtering: Show personalized job postings based on the user’s department, location, or role within Staffbase.
  • Application tracking: Add features that let users see their application status or history via the plugin.
  • Job alerts & notifications: Use the Staffbase Notifications API to push alerts for new internal job postings.
  • Other third-party applications: Use this understanding to integrate other third-party tools.