Intro

IMPORTANT! Creating addons is currently enabled per invitation only. Please reach out to get an invitation.

An addon typically consists of two main parts. The most important part is an app (or system, platform or what ever you call it) of your own making. This app should be what projects want to subscribe to. If you are going to create your app from scratch we recomend that you make it a project on Goaddon. Our own addons, like for example Goexplore and Gofetch , are actually Goaddon projects that are using the Go2us addon. In fact, the Go2us addon itself is that too. And goaddon.com, the website you are looking at right now, is using Go2us.

Anyway, these docs will walk you through how to create the second part of your addon: an interface that is embedded on Goaddon, so projects can interact with you imediately after having subscribed to your addon.

Your interface will consist of pages with informations, instructions and forms that a project can fill out.

Your pages can set up with Liquid flavored HTML , which extends a wide range of functions (like math, iteration, variables, etc.). Additionally you can have us import Javascript and CSS libraries. The following libraries are already loaded:

CSS:

Javascript:

Your pages are loaded inside an iframe without access to the parent window, so your page won't be affected by our scripts and styles.

The root page

You are required to have a page named root. Every other page will be loaded into it. A basic root page could look like this:

<!-- Page name: root -->
<style>
  a, button, input, textarea {
    outline: none !important;
    box-shadow: none !important;
    -webkit-box-shadow: none !important;
  }

  a {
    text-decoration: none !important;
  }

  code {
    word-break: keep-all !important;
  }
</style>
{% yield %}

The above example overrides some CSS and then yields whatever page that is actually being requested.

The frontpage

You are also required to have a page named frontpage. Assuming that your addon is called My Addon a basic frontpage could look like this:

<!-- Page name: frontpage -->
<h5 class="mb-4">Welcome to My Addon</h5>
<p>All is set, you're good to go!</p>
Page variables

Say you want to display some kind of project specific status to the user. When a page of yours is requested by a user we'll check if you need us to request your API for variables.

Say you own the domain example.com.

The follwing informations are needed from you:

  • The URL of your API, e.g. eu-api.example.com.
  • The API HTTP method (get or post).
  • The API path, e.g. /pages/frontpage.

The first is found from the menu at ACCOUNT > WEBHOOKS, the others are found once you have navigated to WIZARD > PAGES and created a page called frontpage.

Given the above example values, and assuming that your webhook token is 5c5a5dbe-8e18-43d6-933f-b7ad493ff7f5 you could receive a get request like this:

GET /?locale=en
Host: https://eu-api.example.com
Content-Type: application/json
Authorization: Basic NWM1YTVkYmUtOGUxOC00M2Q2LTkzM2YtYjdhZDQ5M2ZmN2Y1
project-id: 5c1255b83ab42f16f609497d

Or a post request like this:

POST /pages/frontpage
Host: https://eu-api.example.com
Content-Type: application/json
Authorization: Basic NWM1YTVkYmUtOGUxOC00M2Q2LTkzM2YtYjdhZDQ5M2ZmN2Y1
project-id: 5c1255b83ab42f16f609497d
{
  "locale": "en",
  "query": null
}

In the above examples, the request header is a Base64 encoded version of your webhook token.

When receiving the request, you are expected to respond with a JSON hash, e.g.:

{
  "password": "0123456789"
}

With Liquid, you can now access a remote_data variable similar to this:

{
  "project": {
    "_id": "5c1255b83ab42f16f609497d"
  },
  "user": {
    "email": "example@example.com"
  },
  "addon": {
    "password": "0123456789"
  }
}

With that variable of key/value pairs you could make your frontpage dynamic like this:

<!-- Page name: frontpage -->
<h5 class="mb-4">Welcome to My Addon, {{ remote_data.user.email }}</h5>
{% if remote_data.addon.password != nil and remote_data.addon.password != "" %}
  {% assign text = "All is set, you're good to go!" %}
{% else %}
  {% assign text = "Please <a href='#page=form'>fill out our form</a>!" %}
{% endif %}
<p>{{ text }}</p>

Please notice that the above example also exemplifies how you can link between your pages.

Forms

The new frontpage links to a page called form where the user is apparently expected to submit informations to you. Let's create that page:

<!-- Page name: form -->
<h5 class="mb-4">Form</h5>
<form class="goa-form" action="/projects/{{ remote_data.project._id }}" method="patch">
  <div class="mb-3">
    <b><label for="password">Your password</label></b>
    <input class="goa-text form-control" type="text" name="password" value="{{ remote_data.addon.password }}" placeholder="E.g. 0123456789" data-regex="^.+$">
    <div class="small form-text text-muted" data-help="password">This field must not be empty.</div>
  </div>
  <div class="mb-4">
    <input type="submit" class="btn btn-outline-primary" value="Save">
    <span class="text-danger goa-form-errors"></span>
  </div>
</form>

On the above page you enable the user to submit some kind of password to you.

The page expects a password variable, so let's have it trigger an API request to https://eu-api.example.com/pages/form before the page is displayed. Your API should respond with a JSON hash like this:

{
  "password": null
}

This is how the form works:

1: Initializing the form

When a form is given the class goa-form data submitted by the user will find it's way, through us, to your API.

2: Initializing input fields

Every input field you want to include in the submission should be given specific classes. The examples below show how you can submit an input named foobar.

Text fields (goa-text), e.g.:

<div class="input-group">
  <b><label for="your_input">Your input</label></b>
  <input class="goa-text form-control" type="text" name="your_input">
</div>

Checkboxes (goa-checkbox), e.g.:

{% for option in options %}
  <div class="input-group mb-3">
    <label class="form-check-label">
      <input class="goa-checkbox form-check-input me-2" type="checkbox" value="{{ option.name }}" name="your_options">
      {{ option.label }}
    </label>
  </div>
{% endfor %}

Radio buttons (goa-radio), e.g.:

{% assign bools = "true false" | split: " " %}
{% for bool in bools %}
  <div class="input-group mb-1">
    <label class="form-check-label">
      <input class="goa-radio form-check-input" type="radio" name="your_choice" value="{{ bool }}">
      {{ bool }}
    </label>
  </div>
{% endfor %}

Select tags (goa-select), e.g.:

<select class="goa-select form-control">
  <option value="option_1">Option 1</option>
  <option value="option_2">Option 2</option>
</select>

3: Getting the submitted content

The request will go to:

  • The URL of your API
  • The path defined in forms action attribute
  • The HTTP method defined in the forms method attribute (post, patch or put)

So given the example values used previously, and assuming the user entered 0123456789 as password in the form, you should expect to receive this request:

PATCH /projects/5c1255b83ab42f16f609497d
Host: https://eu-api.example.com
Content-Type: application/json
Authorization: Basic NWM1YTVkYmUtOGUxOC00M2Q2LTkzM2YtYjdhZDQ5M2ZmN2Y1
project-id: 5c1255b83ab42f16f609497d
{
  "payload": {
    {
      "password": "0123456789"
    }
  }
}

The exemplified path above is chosen to keep things RESTful, but don't trust the {{ project_id }} variable in the path. The user could easily manipulate the HTML source code before submitting the form. You should only trust the headers project-id if you want to know which project submitted the form.

4: Validating text fields

If you define a data-regex attribute in a input field with the class goa-text and provide it with a regular expression we'll test the value before allowing the form to be submitted.

As you see in our form the text input field for password is followed by an explaination inside a div with the attribute data-help=password. If the input value fails the regular expression test, we'll highlight that div.

Additionally, if you include a div with the class goa-form-errors we'll diplay the number of errors inside it.

Please be advised that you should do your own tests before accepting and saving submitted data. The user could easily manipulate the HTML source code and circumvent our test.

Responding to a form request

You should respond with a 200 OK status even if something in the request doesn't pass your tests.

Your response body should either be empty or contain a script that we can excecute on your page. You could for example inform the user if you have accepted and saved the submitted data. Try it out by responding with $('.goa-form .btn-outline-primary').html('Saved');.

You could also flash a message to the user by calling the flashAlert function:

// Flash a confirmation:
flashAlert("success", "**Confirmed!** Good job.");

// Flash an error:
flashAlert("error", "**Error!** That did not work...");

// Flash an message:
flashAlert("message", "Hm, ok!");

// Flash a message with links:
flashAlert("light", "######This is a h6 headline\\n\\nThis is an [external link ](https://example.com) and an [internal link ](#page=pagex)", "99999");

As you see the alerts accept markdown, and you can optionally set the duration with the third argument. Links can contain FontAwesome icons, using markdown like this [i fas fa-external-link-alt] (in the above example our syntax highlighting library is apparently determined to render the icons rather than showing the code, sorry about that!).
If you use the icon [i fas fa-external-link-alt] the link will be rendered with a target=_blank attribute.

Snippets

If you find yourself duplicating the same lines of code on different places, you could move these lines to a snippet.

The snippet content will be placed where ever you reference it, and it will have access to the same variables as if it was a part of the page that referenced it.

Consider a situation where several pages wants to display a status of some kind, one of them being the frontpage we already outlined. Let's change the last line:

<!-- Page name: frontpage -->
<h5 class="mb-4">Welcome to My Addon, {{ remote_data.user.email }}</h5>
{% if remote_data.addon.password != nil and remote_data.addon.password != "" %}
  {% assign text = "All is set, you're good to go!" %}
{% else %}
  {% assign text = "Please <a href='#page=form'>fill out our form</a>!" %}
{% endif %}
{% include "status" %}

If the HTML used for displaying a status is the same for all pages, but the logic behind the status is different, they could share a snippet, e.g. with the name status:

<!-- Snippet name: status -->
<p>{{ text }}</p>
Child pages

You might have pages you don't want display in the menu. But at the same time the user is lost if he don't see an active menu tab.

Therefore you should structure a page to either being accessible from the menu, or being a child page of one that is.

Let's assume that your addon connects projects to a range of providers, whatever they may provide. Say you want a page to display all these providers and a child page with the details about a specific provider.

We'll call the parent page providers and the child page providers__show.

The parent page

The parent page becomes accessible in the menu by meeting two conditions:

  1. Its name must not contain two underscores. So providers__index would disqualify it.
  2. It must have a title, defined under Menu locales, e.g.:
{
  "en": "Providers"
}

The child page

The child page should contain the name of its parent page, followed by two underscores, and end with its own name, so the name providers__show refers back to the parent page providers.

Linking to child pages

A child page often comes into play when it can't be requested without additional parmeters.

If you linked to the child page with e.g. <a href='#page=providers__show>View this provider</a> it wouldn't be possible to know what provider to show.

Instead you need to include a query in the link. The query should be formatted as json like .e.g.:

<!-- Page name: providers -->
<h5 class="mb-4">providers</h5>
{% for provider in remote_data.addon.providers %}
  {% assign query = '{"_id":"id_placeholder"}' %}
  {% assign query = query | replace: "id_placeholder", provider._id %}
  <a href="#page=providers__show&query={{ query | url_encode }}">View {{ provider.name }}</a>
{% endfor %}

The strange two step strategy for formatting the JSON query seems necessary due to limitations in Liquid. But the query parameter would now be included in the body of the request you receive from us before we display the page.

Locales

You can keep your HTML code clean by putting all text content into the Locales hash. You are not obligated to do so, and currently only English is supported, but if you use locales from the beginning you'll be prepared to support multiple languages if it becomes relevant later on.

Say you want to localize the providers__show page from our previous section, this could be your locales hash:

{
  "en": {
    "headline": "View details about",
    "info": "Interesting info"
  }
}

And this could be the page:

<!-- Page name: providers__show -->
<h5 class="mb-4">{{ l.headline }} {{ remote_data.addon.provider.name }}</h5>
<p>{{ l.info }}: {{ remote_data.addon.provider.info }}</p>
Simplifying your API request settings

We've described how you need to register what API path we should request before displaying a page of yours. In our examples, we displayed pages called frontpage, form, providers and providers__show.

We chose the API path /pages/frontpage for the frontpage page and /pages/form for the form page.

If this is a general pattern for your pages, you could simplify things by simply choosing /pages/{{ page }} for the root path, along with a preferred API HTTP method. The {{ page }} variable will be replaced with the actual page name.

If you need to override this for a specific page, simply register an API path for it.

Path variables

Except for the {{ page }} variable you can incorporate a {{ locale }} variable into your API path. We may add additional variables in the future.

API

As your addon becomes more and more advanced, you may find our addon API relevant. Our own addons are using it exclusively, and we never use the web interface for updating.