Skip to main content Accessibility Feedback

Automating Slack invites with vanilla JavaScript

Yesterday, I wrote about the tech behind my education platform, but I forgot to mention one big and very important piece: Slack.

Anyone who purchases one of my pocket guides or video courses, joins my Academy training program, or subscribes to my Vanilla JS Projects video series gets access to my private Slack channel. Academy and Projects members also get their own dedicated channels just for them.

I’m routinely told it’s one of the biggest perks of purchasing my products.

But Slack doesn’t provide any sort of simple, native way to invite people to a channel. Out-of-the-box, you have to manually invite people to join.

Today, I want to show you how I use the Slack API and some vanilla JS to invite people to my Slack channels.

The Slack API

Unfortunately, the Slack API doesn’t have a publicly documented method for inviting people to a channel.

There is an undocumented endpoint for doing that, though. Slack’s official position is that this endpoint could change in the future and is only used internally, which is why it’s not documented.

For now, it works, and has been stable for a couple of years. So I (and various other people) use it to help automate things.

Creating a middleman API

The Slack API requires a token to work. This is unique to you, and shouldn’t be stored in JavaScript where anyone can find it and use it.

As a result, we need to create a middleman API that you can call with your JavaScript. This API uses your private token (stored somewhere on the server) to call the Slack API directly.

I mentioned in my article yesterday that I use a headless instance of WordPress. I wrote a Slack API WordPress plugin for this.

The Slack API for WordPress plugin dashboard

Once installed, you can add your team name and private token in the dashboard. You can also set a key/secret combo to include as a hidden field in the invite form for extra protection.

I also included an option to specify approved domains that can call the API. Calls from any domains besides these are rejected.

Creating an invite form

In my learning portal, I have a form users can submit to generate their own Slack invite.

<form id="slack-invite">

	<label for="email">Email Address</label>
	<input type="email" id="email" name="email">

	<input type="hidden" name="{FORM_KEY}" value="{FORM_SECRET}">
	<input type="hidden" name="channels" value="12345, abcdef">

	<div id="slack-status"></div>

	<button>Get Your Invite</button>

</form>

The hidden {FORM_KEY} field includes the key and secret I set in the WordPress plugin Dashboard. These can be anything at all. I used my password manager app to generate some random strings.

If you want to invite the user to any channels besides the default public ones, you can add those as a field to the form, comma-separated.

And of course, you need an email address to send the invite to.

The empty #slack-status element is where we can add error messages if something goes wrong.

Using the API with vanilla JavaScript

First, we need to listen for submissions from the invite form with addEventListener().

We’ll prevent the default form submission from happening. Then, we’ll call a submitForm() helper method.

var form = document.querySelector('#slack-invite');
form.addEventListener('submit', function (event) {
	event.preventDefault();
	submitForm();
}, true);

Serializing form data

In order to submit to the API, we need to serialize the data from our form into a string of key/value pairs (aka a query string).

email=hi@gomakethings.com&channels=1234&randomString=randomValue

I have a helper function for serializing form data. If you’re interested in how it works, I wrote about it last year.

I drop that into my code. Then, in my submitForm() function, I use it to serialize the form data and append it to the endpoint for my middleman API.

var submitForm = function () {

	// The endpoint URL
	var url = 'https://my-headless-wordpress.com/wp-json/gmt-slack/v1/invite?' + serialize(form);

};

Calling the API

Next, we need to make the actual API call. If you’ve never worked with XHR before, or need a primer, here’s an introduction article I wrote.

Since we’re adding a new user to the channel, this is a POST request.

var submitForm = function () {

	// The endpoint URL
	var url = 'https://my-headless-wordpress.com/wp-json/gmt-slack/v1/invite?' + serialize(form);

	// Set up our HTTP request
	var xhr = new XMLHttpRequest();

	// Setup our listener to process request state changes
	xhr.onreadystatechange = function () {

		// Only run if the request is complete
		if (xhr.readyState !== 4) return;

		// Process our return data
		if (xhr.status >= 200 && xhr.status < 300) {
			// This will run when the request is successful
			// It checks to make sure the status code is in the 200 range
			console.log('success!', xhr);
		} else {
			// This will run when it's not
			console.log('The request failed!');
		}

	};

	// Create and send a POST request
	xhr.open('POST', url);
	xhr.send();

};

Handling responses

After we call the API, we need to handle whatever responses we get back. The middleman API sends back responses in this format:

var response = {
	code: 200,
	status: 'success',
	message: 'An invitation to join the Slack workspace has been sent.'
};

Each response includes a status code, a status ID, and a message.

Let’s use two helper functions to handle successful and failed responses, respectively: inviteSuccess() and inviteFail().

The data we get back is in string form. We’ll use JSON.parse() to convert the xhr.responseText into a JSON object. Then, we’ll pass it into our helper functions.

// Setup our listener to process request state changes
xhr.onreadystatechange = function () {

	// Only run if the request is complete
	if (xhr.readyState !== 4) return;

	// Get the data
	var data = JSON.parse(xhr.responseText);

	// Process our return data
	if (xhr.status >= 200 && xhr.status < 300) {
		// This will run when the request is successful
		// It checks to make sure the status code is in the 200 range
		inviteSuccess(data);
	} else {
		// This will run when it's not
		inviteFail(data);
	}

};

Successful responses

If the invite was successfully sent, we can show a success message. First, let’s get the #slack-status element to show our message in.

var inviteSuccess = function (data) {

	// Get the message element
	var msg = document.querySelector('#slack-status');
	if (!msg) return;

};

Then, we’ll user textContent to show our message.

I’m adding a unique class to the message for styling purposes, but this is optional.

var inviteSuccess = function (data) {

	// Get the message element
	var msg = document.querySelector('#slack-status');
	if (!msg) return;

	// Show the message
	msg.textContent = data.message;

	// Update the class
	msg.classList.add('success');

};

Failed responses

Handling failed responses works pretty much the same way.

var inviteFail = function (data) {

	// Get the message element
	var msg = document.querySelector('#slack-status');
	if (!msg) return;

	// Show the message
	msg.textContent = data.message;

	// Update the class
	msg.classList.add('error');

};

Clearing the response message on submit

One last detail: when a user submits the form, we should clear the message and class. We’ll add a clearStatus() helper function to the event handler.

form.addEventListener('submit', function (event) {
	event.preventDefault();
	submitForm();
	clearStatus();
}, true);

In our clearStatus() helper, we’ll get the #slack-status element. Then we’ll wipe out it’s content and remove any classes.

var clearStatus = function () {

	// Get the message element
	var msg = document.querySelector('#slack-status');
	if (!msg) return;

	// Wipe out the status message
	msg.innerHTML = '';

	// Remove the classes
	msg.classList.remove('success');
	msg.classList.remove('error');

};

Putting it all together

Here’s the complete script.

// The form
var form = document.querySelector('#slack-invite');

/*!
 * Serialize all form data into a query string
 * (c) 2018 Chris Ferdinandi, MIT License, https://gomakethings.com
 * @param  {Node}   form The form to serialize
 * @return {String}      The serialized form data
 */
var serialize = function (form) {

	// Setup our serialized data
	var serialized = [];

	// Loop through each field in the form
	for (var i = 0; i < form.elements.length; i++) {

		var field = form.elements[i];

		// Don't serialize fields without a name, submits, buttons, file and reset inputs, and disabled fields
		if (!field.name || field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') continue;

		// If a multi-select, get all selections
		if (field.type === 'select-multiple') {
			for (var n = 0; n < field.options.length; n++) {
				if (!field.options[n].selected) continue;
				serialized.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(field.options[n].value));
			}
		}

		// Convert field data to a query string
		else if ((field.type !== 'checkbox' && field.type !== 'radio') || field.checked) {
			serialized.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value));
		}
	}

	return serialized.join('&');

};

// Show a message for successful invites
var inviteSuccess = function (data) {

	// Get the message element
	var msg = document.querySelector('#slack-status');
	if (!msg) return;

	// Show the message
	msg.textContent = data.message;

	// Update the class
	msg.classList.add('success');

};

// Show a message for failed invites
var inviteFail = function (data) {

	// Get the message element
	var msg = document.querySelector('#slack-status');
	if (!msg) return;

	// Show the message
	msg.textContent = data.message;

	// Update the class
	msg.classList.add('error');

};

// Submit the form to the API
var submitForm = function () {

	// The endpoint URL
	var url = 'https://my-headless-wordpress.com/wp-json/gmt-slack/v1/invite?' + serialize(form);

	// Set up our HTTP request
	var xhr = new XMLHttpRequest();

	// Setup our listener to process request state changes
	xhr.onreadystatechange = function () {

		// Only run if the request is complete
		if (xhr.readyState !== 4) return;

		// Get the data
		var data = JSON.parse(xhr.responseText);

		// Process our return data
		if (xhr.status >= 200 && xhr.status < 300) {
			// This will run when the request is successful
			// It checks to make sure the status code is in the 200 range
			inviteSuccess(data);
		} else {
			// This will run when it's not
			inviteFail(data);
		}

	};

	// Create and send a POST request
	xhr.open('POST', url);
	xhr.send();

};

// Clear the status message on form submit
var clearStatus = function () {

	// Get the message element
	var msg = document.querySelector('#slack-status');
	if (!msg) return;

	// Wipe out the status message
	msg.innerHTML = '';

	// Remove the classes
	msg.classList.remove('success');
	msg.classList.remove('error');

};

// Listen for submit events on the form
form.addEventListener('submit', function (event) {
	event.preventDefault();
	submitForm();
	clearStatus();
}, true);

Let me know if you have any questions or if I did a bad job explaining something.