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.
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.
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>
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:
eu-api.example.com
.get
or post
)./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.
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:
action
attributemethod
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.
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.
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>
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:
providers__index
would disqualify it.{ "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
.
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.
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>
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.
Except for the {{ page }}
variable you can incorporate a {{ locale }}
variable into your API path. We may add additional variables in the future.
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.