[Legacy] Building an Add-on
Last updated February 06, 2024
Table of Contents
This article describes using v1 of the Add-on Partner API. If you created your add-on before April 2018, this article probably pertains to it. You can find your add-on’s API version in the Partner Portal under Settings > Provisioning API. V3 integrations should use the current Building an Add-on article.
This article describes the basic steps for developing an add-on for the Heroku Elements Marketplace. For more advanced add-on development topics, see Advanced integrations.
Because add-ons are cloud services, you can develop them in whatever language and framework you prefer. Add-ons are not required to run on Heroku.
The primary technical requirements for an add-on are:
- It must be able to receive HTTPS requests.
- It must be able to parse JSON.
Before you begin: Register as an add-on partner
Visit addons-next.heroku.com and log in if you haven’t yet. You’ll be prompted to accept the Partner Terms of Service. After you do, you’ll be directed to the Heroku Partner Portal, and you can begin developing your add-on.
Step 1: Generate your add-on manifest
Every add-on has an addon-manifest.json
file that contains metadata, configuration, and credentials for the add-on. You create your add-on manifest with Heroku’s addons-admin CLI plugin.
First ensure you’ve installed the Heroku CLI and logged in via heroku login
.
Then, install the addons-admin plugin:
$ heroku plugins:install addons-admin
You can then generate a manifest in your add-on’s root directory with the following command:
$ heroku addons:admin:manifest:generate
This will create the file addon-manifest.json
in the current working
directory. A manifest contains secrets and should not be checked into source
control.
The generated manifest is a skeleton that includes defaults and placeholder values. You’ll need to modify some attributes in the manifest to create a working add-on service.
For comprehensive descriptions of manifest fields, see Add-on Manifest Format.
Step 2: Integrate with the Add-on Partner API
When a Heroku customer performs certain administrative actions related to your add-on (such as provisioning it or changing their current plan), Heroku sends a POST
request to your add-on so it can perform necessary actions. These requests are part of the Add-on Partner API.
Your add-on must be configured to receive and handle Add-on Partner API requests appropriately. Please see the Add-on Partner API for guidelines on SSL, idempotency, request body validation, versioning headers, and handling errors.
Basic authentication
The Add-on Partner API uses Basic Auth for all operations. The username will be the id
in your Manifest (the identifier you chose above) and the password will be the password
field found in your Manifest.
use Rack::Auth::Basic, "Heroku API" do |username, password|
username == "<id from Manifest>" && password == "<password from Manifest>"
end
The provisioning request
When a customer makes a request to provision your add-on, Heroku sends a POST
request with a JSON payload to your add-on’s /heroku/resources
endpoint.
When an add-on receives a provisioning request from Heroku, it must do all of the following:
- Provision the necessary resources for the customer
- Persist the metadata included in Heroku’s request to allow future add-on requests (upgrades/downgrades, deprovisions, SSO dashboard sign-ins, and so on)
- Return customer-specific configuration information to Heroku
You can configure the root URL for your add-on service (for example, example.com/api/heroku/resources
). See the manifest documentation for details.
Example request
# POST /heroku/resources
{
"plan": "test",
"uuid": "4b922d1d-f1d2-4fa9-9e6a-95f8f1aa58fb",
"region": "amazon-web-services::us-east-1",
"options": {},
...
}
Example implementation (Ruby)
post '/heroku/resources' do
# Create an instance of your service for the app uuid in the request
service = Service.create!(
plan: json_params[:plan],
region: json_params[:region],
heroku_uuid: json_params[:uuid] # The :resource_uuid used in all related requests
)
response = {
id: service.id, # Unique identifier from your system
config: { "SERVICE_URL" => service.url },
message: "Welcome to our service"
}
status 200
response.to_json
end
Example response
{
"config": {
"SERVICE_URL": "https://user:password@service.example.com/path-to-resource"
},
"id": "37136775-c6f6-4d29-aa6b-12d1dad4febb",
"message": "Welcome to our service!"
}
If your add-on takes a while to finish provisioning, see Asynchronous Provisioning to learn how to improve the provisioning experience for customers.
The deprovisioning request
When a customer removes your add-on, Heroku sends a DELETE
request to it. This request uses HTTP Basic Authentication just like the provisioning request.
Example request
# :id is the unique identifier from your system that you responded
# with at provision time.
DELETE /heroku/resources/:resource_uuid
Example implementation (Ruby)
delete '/heroku/resources/:resource_uuid' do
Service.find(params[:resource_uuid]).destroy
status 200
{status: "OK"}.to_json
end
Example response
Simply return a 200 OK
response (no body is necessary).
The plan change request (upgrade / downgrade)
When a customer changes their current add-on plan, Heroku sends a PUT
request to your add-on.
When your add-on receives a plan change request, it must:
- Upgrade or downgrade any features and settings accordingly
- Either automatically migrate any necessary state (like database contents, for example) or inform the customer how to do it manually with documentation
- Respond to Heroku with any new configuration values to use the new plan
Example request
# :id is the unique identifier from your system that you responded
# with at provision time.
PUT /heroku/resources/:resource_uuid
{
"plan": "another_plan",
...
}
Example implementation (Ruby)
put '/heroku/resources/:resource_uuid' do
service = Service.find(params[:resource_uuid])
service.change_plan(json_params[:plan])
result = {
config: { "SERVICE_URL" => service.url },
message: "Successfully changed from #{original_plan} to #{json_params[:plan]}"
}
result.to_json
end
Example response
{
"config": {
"SERVICE_URL": "https://user:password@service.example.com/new-url"
},
"message": "Successfully changed from original_plan to another_plan"
}
Single Sign On
Implementing a single sign-on (SSO) integration lets you provide a web UI control panel to your add-on customers. You can reach an in-depth guide to implementing SSO in the Add-on Single Sign-on tutorial.
Step 3: Deploy the add-on
Deploy your service somewhere that is publicly accessible. It does not have to be deployed to Heroku (but it certainly can be).
When you’ve deployed the add-on, modify your addon-manifest.json
file’s base_url
and sso_url
values within the production
attribute to point to your deployed app.
Finally, push your updated addon-manifest.json
file up to Heroku in order to register your add-on. You need to be logged in to the Heroku CLI with the account that accepted the Partner Terms of Service for the add-on.
$ heroku addons:admin:manifest:push
At this point, your add-on is available for you (and only you) to provision. You can manage its details in the Partner Portal and install it via the Heroku Dashboard or CLI.
Step 4: Creating a test/staging version of your add-on
We suggest creating a <your slug>-staging
version of your add-on to test SSO
and other add-on integration features. This <your slug>-staging
add-on should
point to staging versions of your integration infrastructure.
The staging version of your add-on will remain in alpha forever and cannot charge for provisioned plans.
Advanced integrations
There is far more that you can do with your integration to Heroku. Check out some of the articles below to explore more advanced integrations.